Golang client NIC.ru
Hello! I hasten to share some development. Golang client for NIC.ru API.

We have a close integration with nic.ru, this has happened historically, I’m sure many people use nic.ru DNS services, maybe not as actively as we do, but anyway, this is important for us, we previously had a development in Python, but lately we have been actively moving to Go, so we had to support this business. I will be glad if it will make life easier / save time for someone.
Initial data
Initially, there is such a package for python https://pypi.org/project/nic-api/
It was written by our colleague some time ago, I don’t claim that it works, but I don’t claim the opposite either, we didn’t have the opportunity to check it, since there was no need to use it, nevertheless, it’s impossible not to mention it.
The API format is described herethere is little fun there, everything is in xml, but you don’t have to lose heart, you have to work with what they give.
structures
After reading the documentation, we got the following structures
Request
This is the general structure for creating entries
type Request struct {
XMLName xml.Name `xml:"request"`
Text string `xml:",chardata"`
RrList *RrList `xml:"rr-list"`
}
RrList
A structure describing a list of entries
type RrList struct {
Text string `xml:",chardata"`
Rr []*RR `xml:"rr"`
}
RR
DNS record structure
type RR struct {
Text string `xml:",chardata"`
ID string `xml:"id,attr,omitempty"`
Name string `xml:"name"`
IdnName string `xml:"idn-name,omitempty"`
Ttl string `xml:"ttl"`
Type string `xml:"type"`
Soa *Soa `xml:"soa"`
Ns *Ns `xml:"ns"`
A *A `xml:"a"`
Txt *Txt `xml:"txt"`
Cname *Cname `xml:"cname"`
}
A
A structure that contains the IPv4 address of the A-record. So far, we do not have IPv6, we have not even delved into how to live with this matter in NIC.ru, but the main feature here is that, compared to CNAME, there are no attributes, the value is stored as text
type A string
func (a *A) String() string {
return string(*a)
}
CNAME
The structure that stores the cascading name, everything is taken from the API, IdnName can be something like “our-favorite-site.rf”, while Name will store a value that is understandable to all nodes on the Internet, in this case:
xn-----6kccd6bhcmmf0dp7e6b4b.xn--p1ai
type Cname struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
IdnName string `xml:"idn-name,omitempty"`
}
Service
The structure reflecting the NIC.ru service, it reflects the tariff, the number of domains, etc., service information.
type Service struct {
Text string `xml:",chardata"`
Admin string `xml:"admin,attr"`
DomainsLimit string `xml:"domains-limit,attr"`
DomainsNum string `xml:"domains-num,attr"`
Enable string `xml:"enable,attr"`
HasPrimary string `xml:"has-primary,attr"`
Name string `xml:"name,attr"`
Payer string `xml:"payer,attr"`
Tariff string `xml:"tariff,attr"`
RrLimit string `xml:"rr-limit,attr"`
RrNum string `xml:"rr-num,attr"`
}
SOA
SOA record structure
type Soa struct {
Text string `xml:",chardata"`
Mname struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
IdnName string `xml:"idn-name,omitempty"`
} `xml:"mname"`
Rname struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
IdnName string `xml:"idn-name,omitempty"`
} `xml:"rname"`
Serial string `xml:"serial"`
Refresh string `xml:"refresh"`
Retry string `xml:"retry"`
Expire string `xml:"expire"`
Minimum string `xml:"minimum"`
}
NS
NS record structure
type Ns struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
IdnName string `xml:"idn-name,omitempty"`
}
TXT
TXT record structure
type Ns struct {
Text string `xml:",chardata"`
Name string `xml:"name"`
IdnName string `xml:"idn-name,omitempty"`
}
zone
Zone structure
type Zone struct {
Text string `xml:",chardata"`
Admin string `xml:"admin,attr"`
Enable string `xml:"enable,attr"`
HasChanges string `xml:"has-changes,attr"`
HasPrimary string `xml:"has-primary,attr"`
ID string `xml:"id,attr"`
IdnName string `xml:"idn-name,attr"`
Name string `xml:"name,attr"`
Payer string `xml:"payer,attr"`
Service string `xml:"service,attr"`
Rr []*RR `xml:"rr"`
}
revision
Revision structure
type Revision struct {
Text string `xml:",chardata"`
Date string `xml:"date,attr"`
Ip string `xml:"ip,attr"`
Number string `xml:"number,attr"`
}
error
The structure of the error in the response from the API
type Error struct {
Text string `xml:",chardata"`
Code string `xml:"code,attr"`
}
Response
API response structure
type Response struct {
XMLName xml.Name `xml:"response"`
Text string `xml:",chardata"`
Status string `xml:"status"`
Errors struct {
Text string `xml:",chardata"`
Error *error `xml:"error"`
} `xml:"errors"`
Data struct {
Text string `xml:",chardata"`
Service []*Service
Zone []*Zone `xml:"zone"`
Address []*A `xml:"address"`
Revision []*Revision `xml:"revision"`
} `xml:"data"`
}
How to use
The client is here https://github.com/maetx777/nic.ru-golang-client.git, ready to use. AT examples describes how to create and delete A/CNAME records, as well as how to download the entire zone.
Creating a utility
Next, I will describe a little how to create a console utility for managing the apish, the code is uploaded to the repository.
cmd/nicru/main.go
create file cmd/nicru/main.go:
package main
import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func main() {
cmd := &cobra.Command{
Use: `nicru`,
Short: `утилита для управления записями в DNS NIC.ru`,
}
if err := cmd.Execute(); err != nil {
logrus.Infoln(err.Error())
}
}
This file will be fed to the assembly of the utility, here we see the default command, when we call it, we will get nothing, the code will exit and that’s it, unless an error occurs, otherwise, the logrus module will show us Fatal, and the utility will exit with a non-zero code.
cmd/nicru/config.go
first, we initialize the config structure, which can be used in different commands:
package main
import api "github.com/maetx777/nic.ru-golang-client/client"
var config = &api.Config{
Credentials: &api.Credentials{
OAuth2: &api.OAuth2Creds{
ClientID: "",
SecretID: "",
},
Username: "",
Password: "",
},
ZoneName: "",
DnsServiceName: "",
CachePath: "",
}
cmd/nicru/add-a.go
Let’s add a command to the utility to create an A-record.
package main
import (
"fmt"
api "github.com/maetx777/nic.ru-golang-client/client"
"github.com/spf13/cobra"
)
func addACmd() *cobra.Command {
var (
names []string //здесь будем хранить список записей, которые нужно создать
target string //здесь хранится ipv4-адрес, на которые будет ссылаться каждое имя, описанное выше
ttl int //TTL для каждой записи
)
cmd := &cobra.Command{
Use: `add-a`,
Short: `добавление A-записей`,
Long: `oauth2 ключи нужно получить в ЛК nic.ru - https://www.nic.ru/manager/oauth.cgi?step=oauth.app_register
имя DNS-сервиса можно посмотреть по адресу https://www.nic.ru/manager/services.cgi?step=srv.my_dns&view.order_by=domain
`,
Run: func(cmd *cobra.Command, args []string) {
//инициализируем клиента
client := api.NewClient(config)
if response, err := client.AddA(names, target, ttl); err != nil {
//обрабатываем ошибку
fmt.Printf("Add A-record error: %s\n", err.Error())
return
} else {
//печатаем результат
for _, record := range response.Data.Zone[0].Rr {
fmt.Printf("Added A-record: %s -> %s\n", record.Name, record.A.String())
}
}
//после создания записей, нужно закоммитить результат, чтобы DNS уехал в "прод"
if _, err := client.CommitZone(); err != nil {
fmt.Printf("Commit error: %s\n", err.Error())
} else {
fmt.Printf("Zone committed\n")
}
},
}
//создаём флаги для заполнения конфига
cmd.PersistentFlags().StringVar(&config.Credentials.OAuth2.ClientID, `oauth2-client-id`, ``, `oauth2 client id`)
cmd.PersistentFlags().StringVar(&config.Credentials.OAuth2.SecretID, `oauth2-secret-id`, ``, `oauth2 secret id`)
cmd.PersistentFlags().StringVar(&config.Credentials.Username, `username`, ``, `логин от ЛК nic.ru (******/NIC-D)`)
cmd.PersistentFlags().StringVar(&config.Credentials.Password, `password`, ``, `пароль от nic.ru`)
cmd.PersistentFlags().StringVar(&config.ZoneName, `zone-name`, `example.com`, `имя DNS-зоны`)
cmd.PersistentFlags().StringVar(&config.DnsServiceName, `service-name`, `EXAMPLE`, `имя DNS-сервиса`)
cmd.PersistentFlags().StringVar(&config.CachePath, `cache-path`, `/tmp/.nic.ru.token`, `путь до файла, где будет храниться авторизация от API`)
//создаём флаги для указания имён создаваемых записей, ipv4-таргета и TTL
cmd.PersistentFlags().StringSliceVar(&names, `names`, []string{}, `имена, которые нужно создать`)
cmd.PersistentFlags().StringVar(&target, `target`, ``, `куда нужно отправить имена (например, 1.2.3.4)`)
cmd.PersistentFlags().IntVar(&ttl, `ttl`, 600, `TTL для созданным записей`)
return cmd
}
It remains to include the command in main.go:
package main
import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
func main() {
cmd := &cobra.Command{
Use: `nicru`,
Short: `утилита для управления записями в DNS NIC.ru`,
}
cmd.AddCommand(addACmd()) // подключаем команду add-a
if err := cmd.Execute(); err != nil {
logrus.Infoln(err.Error())
}
}
We collect the utility:
go build ./cmd/nicru
Testing the launch of the utility
$ ./nicru --help
утилита для управления записями в DNS NIC.ru
Usage:
nicru [command]
Available Commands:
add-a добавление A-записей
completion Generate the autocompletion script for the specified shell
help Help about any command
Flags:
-h, --help help for nicru
Use "nicru [command] --help" for more information about a command.
We look at the help of the add-a command
$ ./nicru add-a --help
oauth2 ключи нужно получить в ЛК nic.ru - https://www.nic.ru/manager/oauth.cgi?step=oauth.app_register
имя DNS-сервиса можно посмотреть по адресу https://www.nic.ru/manager/services.cgi?step=srv.my_dns&view.order_by=domain
Usage:
nicru add-a [flags]
Flags:
--cache-path string путь до файла, где будет храниться авторизация от API (default "/tmp/.nic.ru.token")
-h, --help help for add-a
--names strings имена, которые нужно создать
--oauth2-client-id string oauth2 client id
--oauth2-secret-id string oauth2 secret id
--password string пароль от nic.ru
--service-name string имя DNS-сервиса (default "EXAMPLE")
--target string куда нужно отправить имена (например, 1.2.3.4)
--ttl int TTL для созданным записей (default 600)
--username string логин от ЛК nic.ru (******/NIC-D)
--zone-name string имя DNS-зоны (default "example.com")
Testing record creation
$ ./nicru add-a --oauth2-client-id *** --oauth2-secret-id *** --username ***/NIC-D --password xJbXi199u8QFZo8Bm --service-name EXAMPLE --zone-name example.com --names foo123 --target 127.0.0.1 --ttl 3600
Added A-record: foo123 -> 127.0.0.1
Zone committed
The record has been successfully created, in a similar way, you can refine the creation of CNAME, the removal of A / CNAME, and other functions implemented in the module.
What can be done to make it easier
In order not to bother with submitting oauth2-creds, as well as login and password, we use vault in our version of the utility, right in the cmd package we have a function that, if there is a ~/.vault-token file, goes to vault, takes everything credits are needed, and then substitutes them into the command as a default, so we do not need to specify the entire footcloth with authorization each time, but, if necessary, we can override this matter at each start.
Conclusion
Initially, there was an idea to share with everyone some, in my opinion, developments, we have to write a lot, sometimes we find ready-made solutions, sometimes not, and then we write our bicycles.
There are no comments in the code yet, due to the fact that today the karma has dropped sharply due to the comment in the post. Given the rapid deterioration of karma, soon I will no longer be able to write anything here, so this is the final post.
We will definitely develop the client, comment everything, develop the readme and use it ourselves directly from the github
I wish you all peace and good! Ready to answer any questions.