infra

package module
v0.0.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 18, 2023 License: Apache-2.0 Imports: 18 Imported by: 0

README

infra Godoc

Cloud server infrastructure that is used to easily coordinate with Google Cloud Platform with helpers for interacting with:

  • Google Cloud DNS
  • Google Compute Engine
  • Google Cloud Storage

With applications such as:

  • Setting up frontend servers fully connected to Google Cloud DNS, and proxying traffic to backends for example, if the program below is run, it'll add Google Cloud DNS entries automatically, making the respective CNAMES, A records etc, and thus visiting the domain name or aliases will resolve and send traffic to the provided IPV4 addresses.
package main

import (
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	infraClient, err := infra.NewDefaultClient()
	if err != nil {
		log.Fatal(err)
	}

	setupInfo, err := infraClient.FullSetup(&infra.Setup{
		Project: "sample-961732",
		Zone:    "us-central1-c",

		ProjectDescription: "full-setup",
		MachineName:        "full-setup-sample",

		DomainName:   "edison.orijtech.com",
		ProxyAddress: "http://10.128.0.5/",
		Aliases:      []string{"www.edison.orijtech.com", "el.orijtech.com"},

		IPV4Addresses: []string{"37.162.3.87"},
	})

	if err != nil {
		log.Fatal(err)
	}
	log.Printf("SetupResponse: %#v\n", setupInfo)
}
  • Creating a Google Compute Engine instance
package main

import (
	"fmt"
	"log"
	"encoding/json"

	"github.com/orijtech/infra"
)

func main() {
	client, err := infra.NewDefaultClient()
	if err != nil {
		log.Fatal(err)
	}
	instance, err := client.CreateInstance(&infra.InstanceRequest{
		Description: "Git server",

		Project: "sample-998172",
		Zone:    "us-central1-c",
		Name:    "git-server",

		NetworkInterface: infra.BasicExternalNATNetworkInterface,
	})
	if err != nil {
		log.Fatal(err)
	}
	blob, _ := json.MarshalIndent(instance, "", "  ")
	fmt.Printf("Retrieved instance: %s\n", blob)
}
  • Add record sets to Google Cloud DNS
package main

import (
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	client, err := infra.NewDefaultClient()
	if err != nil {
		log.Fatal(err)
	}
	addRes, err := client.AddRecordSets(&infra.UpdateRequest{
		Project: "sample-981058",
		Zone:    "us-central1-c",

		Records: []*infra.Record{
			{
				Type: infra.AName, DNSName: "git.orijtech.com.",
				IPV4Addresses: []string{"108.11.144.83"},
			},

			{Type: infra.CName, DNSName: "www.git.orijtech.com.", CanonicalName: "git.orijtech.com."},
			{Type: infra.CName, DNSName: "g.orijtech.com.", CanonicalName: "git.orijtech.com."},
		},
	})
	if err != nil {
		log.Fatalf("%+v", err)
	}

	fmt.Printf("addRes: %+v\n", addRes)
}
  • Uploading to Google Cloud Storage
package main

import (
	"fmt"
	"io"
	"log"
	"strings"

	"github.com/orijtech/infra"
)

func main() {
	infraClient, err := infra.NewDefaultClient()
	if err != nil {
		log.Fatal(err)
	}

	outParams := &infra.UploadParams{
		Reader: func() io.Reader { return strings.NewReader("This is an upload") },
		Name:   "foo",
		Bucket: "bucket",
		Public: true,
	}

	obj, err := infraClient.UploadWithParams(outParams)
	if err != nil {
		log.Fatalf("uploadWithParams: %v", err)
	}
	fmt.Printf("The URL: %s\n", infra.ObjectURL(obj))
	fmt.Printf("Size: %d\n", obj.Size)
}

Documentation

Overview

Example (Client_AddRecordSets)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	addRes, err := client.AddRecordSets(ctx, &infra.UpdateRequest{
		Project: "sample-981058",
		Zone:    "us-central1-c",

		Records: []*infra.Record{
			{
				Type: infra.AName, DNSName: "git.orijtech.com.",
				IPV4Addresses: []string{"130.211.187.103"},
			},

			{Type: infra.CName, DNSName: "www.git.orijtech.com.", CanonicalName: "git.orijtech.com."},
			{Type: infra.CName, DNSName: "g.orijtech.com.", CanonicalName: "git.orijtech.com."},
		},
	})
	if err != nil {
		log.Fatalf("%+v", err)
	}

	fmt.Printf("addRes: %+v\n", addRes)
}
Output:

Example (Client_CreateInstance)
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	instance, err := client.CreateInstance(ctx, &infra.InstanceRequest{
		Description: "Git server",

		Project: "sample-981058",
		Zone:    "us-central1-c",
		Name:    "git-server",

		NetworkInterface: infra.BasicExternalNATNetworkInterface,
	})
	if err != nil {
		log.Fatal(err)
	}
	blob, _ := json.MarshalIndent(instance, "", "  ")
	fmt.Printf("Retrieved instance: %s\n", blob)
}
Output:

Example (Client_DeleteRecordSets)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	delRes, err := client.DeleteRecordSets(ctx, &infra.UpdateRequest{
		Project: "sample-981058",
		Zone:    "us-central1-c",

		Records: []*infra.Record{
			{
				Type: infra.AName, DNSName: "flick.orijtech.com.",
				IPV4Addresses: []string{"37.45.3.107"},
			},
			{Type: infra.CName, DNSName: "el.orijtech.com.", CanonicalName: "edison.orijtech.com."},
			{Type: infra.CName, DNSName: "tset.orijtech.com.", CanonicalName: "edison.orijtech.com."},
			{Type: infra.CName, DNSName: "f.orijtech.com.", CanonicalName: "fullsetup.orijtech.com."},
			{Type: infra.CName, DNSName: "fli.orijtech.com.", CanonicalName: "fullsetup.orijtech.com."},
			{Type: infra.CName, DNSName: "flic.orijtech.com.", CanonicalName: "fullsetup.orijtech.com."},
		},
	})
	if err != nil {
		log.Fatalf("%+v", err)
	}

	fmt.Printf("delRes: %+v\n", delRes)
}
Output:

Example (Client_Download)
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"os"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	infraClient, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	body, err := infraClient.Download(ctx, "archomp", "demos/gears.gif")
	if err != nil {
		log.Fatalf("Download: %v", err)
	}
	defer body.Close()

	f, err := os.Create("gears.gif")
	if err != nil {
		log.Fatalf("Creating bar.png: %v", err)
	}
	defer f.Close()

	n, err := io.Copy(f, body)
	if err != nil {
		log.Fatalf("io.Copy err: %v", err)
	}
	fmt.Printf("Wrote %d bytes to disk!\n", n)
}
Output:

Example (Client_FindInstance)
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	instance, err := client.FindInstance(ctx, &infra.InstanceRequest{
		Project: "sample-981058",
		Zone:    "us-central1-c",
		Name:    "archomp",
	})
	if err != nil {
		log.Fatal(err)
	}
	blob, _ := json.MarshalIndent(instance, "", "  ")
	fmt.Printf("Retrieved instance: %s\n", blob)
}
Output:

Example (Client_FullSetup)
package main

import (
	"context"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	infraClient, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	setupInfo, err := infraClient.FullSetup(ctx, &infra.Setup{
		Project: "sample-981058",
		Zone:    "us-central1-c",

		ProjectDescription: "full-setup",
		MachineName:        "full-setup-sample",

		DomainName:   "edison.orijtech.com",
		ProxyAddress: "http://10.128.0.5/",
		Aliases:      []string{"www.edison.orijtech.com", "el.orijtech.com"},

		IPV4Addresses: []string{"37.45.3.107"},
	})

	if err != nil {
		log.Fatal(err)
	}
	log.Printf("SetupResponse: %#v\n", setupInfo)
}
Output:

Example (Client_ListDNSRecordSets)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	ires, err := client.ListDNSRecordSets(ctx, &infra.RecordSetRequest{
		Project: "sample-981058",
		Zone:    "us-central1-c",

		DomainName: "orijtech.com",
	})
	if err != nil {
		log.Fatalf("%+v", err)
	}

	for page := range ires.Pages {
		if err := page.Err; err != nil {
			log.Printf("PageNumber: #%d err: %v", page.PageNumber, err)
			continue
		}
		for i, rset := range page.RecordSets {
			fmt.Printf("#%d: Name: %q TTL: %d Type: %v Rrdatas: %#v\n",
				i, rset.Name, rset.Ttl, rset.Type, rset.Rrdatas)
		}
	}
}
Output:

Example (Client_ListInstances)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	ires, err := client.ListInstances(ctx, &infra.InstancesRequest{
		Project: "sample-981058",
		Zone:    "us-central1-c",
	})
	if err != nil {
		log.Fatal(err)
	}

	for page := range ires.Pages {
		if err := page.Err; err != nil {
			log.Printf("PageNumber: #%d err: %v", page.PageNumber, err)
			continue
		}
		for i, instance := range page.Instances {
			fmt.Printf("#%d: ID: %d Name: %q MachineType: %#v CPUPlatform: %v Status: %v Disks: %#v\n",
				i, instance.Id, instance.Name, instance.MachineType, instance.CpuPlatform, instance.Status, instance.Disks)
		}
	}
}
Output:

Example (Client_ListZones)
package main

import (
	"context"
	"fmt"
	"log"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	client, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}
	zres, err := client.ListZones(ctx, &infra.ZoneRequest{
		Project: "sample-981058",
	})
	if err != nil {
		log.Fatal(err)
	}

	for page := range zres.Pages {
		if err := page.Err; err != nil {
			log.Printf("PageNumber: #%d err: %v", page.PageNumber, err)
			continue
		}
		for i, zone := range page.Zones {
			fmt.Printf("#%d: zone: %#v\n", i, zone)
		}
	}
}
Output:

Example (Client_Upload)
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"strings"

	"github.com/orijtech/infra"
)

func main() {
	ctx := context.Background()
	infraClient, err := infra.NewDefaultClient(ctx)
	if err != nil {
		log.Fatal(err)
	}

	outParams := &infra.UploadParams{
		Reader: func() io.Reader { return strings.NewReader("This is an upload") },
		Name:   "foo",
		Bucket: "bucket",
		Public: true,
	}

	obj, err := infraClient.UploadWithParams(ctx, outParams)
	if err != nil {
		log.Fatalf("uploadWithParams: %v", err)
	}
	fmt.Printf("The URL: %s\n", infra.ObjectURL(obj))
	fmt.Printf("Size: %d\n", obj.Size)
}
Output:

Index

Examples

Constants

This section is empty.

Variables

View Source
var (
	BasicAttachedDisk = &compute.AttachedDisk{
		AutoDelete: true,

		Boot: true,
		Type: "PERSISTENT",
		Mode: "READ_WRITE",

		InitializeParams: &compute.AttachedDiskInitializeParams{
			DiskSizeGb:  10,
			SourceImage: "projects/debian-cloud/global/images/family/debian-8",
		},
	}
)
View Source
var (
	BasicExternalNATNetworkInterface = &compute.NetworkInterface{
		AccessConfigs: []*compute.AccessConfig{
			{
				Name: "External NAT",
				Type: "ONE_TO_ONE_NAT",
			},
		},
	}
)

Functions

func ObjectURL

func ObjectURL(obj *storage.Object) string

Types

type BucketCheck

type BucketCheck struct {
	Project string `json:"project"`
	Bucket  string `json:"bucket"`
	Public  bool   `json:"public"`
}

type Client

type Client struct {
	// contains filtered or unexported fields
}

func NewDefaultClient

func NewDefaultClient(ctx context.Context, scopes ...string) (*Client, error)

func NewWithHTTPClient

func NewWithHTTPClient(hc *http.Client) (*Client, error)

func (*Client) AddRecordSets

func (c *Client) AddRecordSets(ctx context.Context, areq *UpdateRequest) (*dns.Change, error)

func (*Client) CreateInstance

func (c *Client) CreateInstance(ctx context.Context, ireq *InstanceRequest) (*compute.Instance, error)

func (*Client) DeleteRecordSets

func (c *Client) DeleteRecordSets(ctx context.Context, dreq *UpdateRequest) (*dns.Change, error)

func (*Client) Download

func (c *Client) Download(ctx context.Context, bucket, path string) (io.ReadCloser, error)

func (*Client) EnsureBucketExists

func (c *Client) EnsureBucketExists(ctx context.Context, bc *BucketCheck) (*storage.Bucket, error)

func (*Client) FindInstance

func (c *Client) FindInstance(ctx context.Context, ireq *InstanceRequest) (*compute.Instance, error)

func (*Client) FullSetup

func (c *Client) FullSetup(ctx context.Context, req *Setup) (*SetupResponse, error)

func (*Client) ListDNSRecordSets

func (c *Client) ListDNSRecordSets(ctx context.Context, rreq *RecordSetRequest) (*RecordSetPagesResponse, error)

func (*Client) ListInstances

func (c *Client) ListInstances(ctx context.Context, req *InstancesRequest) (*InstancePagesResponse, error)

func (*Client) ListZones

func (c *Client) ListZones(ctx context.Context, req *ZoneRequest) (*ZonePagesResponse, error)

func (*Client) Object

func (c *Client) Object(ctx context.Context, bucket, path string) (*storage.Object, error)

func (*Client) UpdateRecordSets

func (c *Client) UpdateRecordSets(ctx context.Context, ureq *UpdateRequest) (*dns.Change, error)

func (*Client) UploadWithParams

func (c *Client) UploadWithParams(ctx context.Context, params *UploadParams) (*storage.Object, error)

type InstancePage

type InstancePage struct {
	Err        error
	PageNumber int64               `json:"page_number"`
	Instances  []*compute.Instance `json:"instances,omitempty"`
}

type InstancePagesResponse

type InstancePagesResponse struct {
	Pages  <-chan *InstancePage
	Cancel func() error
}

type InstanceRequest

type InstanceRequest struct {
	Zone        string       `json:"zone,omitempty"`
	Project     string       `json:"project,omitempty"`
	Description string       `json:"description,omitempty"`
	Name        string       `json:"name,omitempty"`
	MachineType *MachineType `json:"machine_type,omitempty"`

	// CanForwardIP allows an instance to send and receive packets with
	// non-matching destination or source IPs. It is required if you
	// plan to use the instance to forward routes.
	// Description obtained from:
	// https://godoc.org/pkg/google.golang.org/api/compute/v1/#Instance.CanIpForward
	CanForwardIP bool `json:"can_forward_ip,omitempty"`

	Disks []*compute.AttachedDisk `json:"attached_disks,omitempty"`

	// NetworkInterface specifies how this interface is configured to interact with
	// other network services, such as connecting to the internet.
	// Description obtained from:
	// https://godoc.org/google.golang.org/api/compute/v1#Instance.NetworkInterfaces
	NetworkInterface *compute.NetworkInterface `json:"network_interface"`

	// The metadata key/value pairs assigned to the instance.
	// This includes custom metadata and predefined keys.
	// Description obtained from:
	// https://godoc.org/google.golang.org/api/compute/v1#Instance.Metadata
	Metadata *compute.Metadata `json:"metadata"`

	// ServiceAccounts: A list of service accounts, with their specified
	// scopes, authorized for this instance. Only one service account per VM
	// instance is supported.
	//
	// Service accounts generate access tokens that can be accessed through
	// the metadata server and used to authenticate applications on the
	// instance. See Service Accounts for more information.
	// Description obtained from:
	// https://godoc.org/google.golang.org/api/compute/v1#Instance.ServiceAccounts
	ServiceAccounts []*compute.ServiceAccount `json:"service_accounts,omitempty"`

	// NullFields is a list of field names (e.g. "CanIpForward") to include
	// in API requests with the JSON null value. By default, fields with
	// empty values are omitted from API requests. However, any field with
	// an empty field appearing in NullFields will be sent to the server as
	// null. It is an error if a field in this list has a non-empty value.
	// This may be used to include null fields in PATCH requests.
	// Description obtained from:
	// https://godoc.org/google.golang.org/api/compute/v1#Instance.NullFields
	NullFields []string `json:"null_fields"`

	// BlockUntilCompletion when set signifies that the instance request
	// should wait until full completion of creation of an instance.
	BlockUntilCompletion bool `json:"block_until_completion"`
}

type InstancesRequest

type InstancesRequest struct {
	Project string `json:"project"`

	OrderBy string `json:"order_by"`
	Filter  string `json:"filter"`

	MaxPages       int64 `json:"max_pages"`
	ResultsPerPage int64 `json:"results_per_page"`

	Zone string `json:"zone"`
}

func (*InstancesRequest) Validate

func (ireq *InstancesRequest) Validate() error

type MachineType

type MachineType struct {
	// CPUCount should be either 1 or an even number upto 32.
	CPUCount int `json:"cpu_count"`

	// MemoryMBs must be a multiple of 256MB.
	MemoryMBs int `json:"memory_mbs"`

	Type StandardType `json:"type"`
}

func (*MachineType) Validate

func (mt *MachineType) Validate() error

type Record

type Record struct {
	DNSName string     `json:"dns_name"`
	TTL     int64      `json:"ttl"`
	Type    RecordType `json:"type"`

	IPV4Addresses []string `json:"ipv4_addresses"`
	IPV6Addresses []string `json:"ipv6_addresses"`
	CanonicalName string   `json:"canonical_name"`

	NameServers []string `json:"name_servers"`

	CertificateAuthorityAuthorizations []string `json:"certificate_authority_authorizations"`

	PreferenceAndMailServers []string `json:"preference_and_mail_servers"`

	SPFData []string `json:"spf_data"`
	SRVData []string `json:"srv_data"`

	TXTRecords []string `json:"txt_records"`
}

func (*Record) Validate

func (r *Record) Validate() error

type RecordSetPage

type RecordSetPage struct {
	Err        error
	PageNumber int64 `json:"page_number"`

	RecordSets []*dns.ResourceRecordSet `json:"record_sets,omitempty"`
}

type RecordSetPagesResponse

type RecordSetPagesResponse struct {
	Pages  <-chan *RecordSetPage
	Cancel func() error
}

type RecordSetRequest

type RecordSetRequest struct {
	Project string `json:"project"`
	Zone    string `json:"zone"`

	// DomainName if set restricts listing to only
	// return records with this fully qualified domain name.
	DomainName string `json:"domain_name"`

	MaxPages       int64 `json:"max_pages"`
	ResultsPerPage int64 `json:"results_per_page"`
}

func (*RecordSetRequest) Validate

func (rreq *RecordSetRequest) Validate() error

type RecordType

type RecordType string
const (
	AAAName RecordType = "AAA"
	AName   RecordType = "A"
	CName   RecordType = "CNAME"
	CAA     RecordType = "CAA"
	MX      RecordType = "MX"
	NS      RecordType = "NS"
	SPF     RecordType = "SPF"
	SRV     RecordType = "SRV"
	TXT     RecordType = "TXT"
)

type Setup

type Setup struct {
	Project string `json:"project"`
	Zone    string `json:"zone"`

	ProjectDescription string `json:"project_description"`
	MachineName        string `json:"machine_name"`

	DomainName    string   `json:"domainname"`
	IPV4Addresses []string `json:"ipv4_addresses"`
	Aliases       []string `json:"aliases"`
	ProxyAddress  string   `json:"proxy_address"`

	Environ    []string `json:"environ"`
	TargetGOOS string   `json:"target_goos"`
}

func (*Setup) Validate

func (req *Setup) Validate() error

type SetupResponse

type SetupResponse struct {
	BinaryURL string   `json:"binary_url"`
	Domains   []string `json:"domains"`

	DNSAdditions []*dns.ResourceRecordSet `json:"dns_additions"`

	NonHTTPSRedirectURL string `json:"non_https_redirect_url"`
}

type StandardType

type StandardType string
const (

	// Standard machine type with 1 virtual CPU and 3.75 GB of memory.
	// Maximum number of persistent disks: 16 (32 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard1 StandardType = "n1-standard-1"

	// Standard machine type with 2 virtual CPU and 7.50 GB of memory.
	// Maximum number of persistent disks: 16 (64 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard2 StandardType = "n1-standard-2"

	// Standard machine type with 4 virtual CPU and 15 GB of memory.
	// Maximum number of persistent disks: 16 (64 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard4 StandardType = "n1-standard-4"

	// Standard machine type with 8 virtual CPU and 30 GB of memory.
	// Maximum number of persistent disks: 16 (64 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard8 StandardType = "n1-standard-8"

	// Standard machine type with 16 virtual CPU and 60 GB of memory.
	// Maximum number of persistent disks: 16 (128 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard16 StandardType = "n1-standard-16"

	// Standard machine type with 32 virtual CPU and 120 GB of memory.
	// Maximum number of persistent disks: 16 (128 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard32 StandardType = "n1-standard-32"

	// Standard machine type with 64 virtual CPU and 240 GB of memory.
	// Maximum number of persistent disks: 16 (128 in Beta).
	// Maximum total of persistent disk size: 64 TB.
	N1Standard64 StandardType = "n1-standard-64"
)

type UpdateRequest

type UpdateRequest struct {
	Zone    string `json:"zone"`
	Project string `json:"project"`

	Records []*Record `json:"records"`

	Additions []*Record `json:"additions"`
	Deletions []*Record `json:"deletions"`
}

type UploadParams

type UploadParams struct {
	Project string `json:"project"`
	Public  bool   `json:"public"`
	Bucket  string `json:"bucket"`
	Name    string `json:"path"`

	Reader func() io.Reader `json:"-"`
}

func (*UploadParams) Validate

func (params *UploadParams) Validate() error

type ZonePage

type ZonePage struct {
	Err        error
	PageNumber int64           `json:"page_number"`
	Zones      []*compute.Zone `json:"zones,omitempty"`
}

type ZonePagesResponse

type ZonePagesResponse struct {
	Pages  <-chan *ZonePage
	Cancel func() error
}

type ZoneRequest

type ZoneRequest struct {
	Project string `json:"project"`

	OrderBy string `json:"order_by"`
	Filter  string `json:"filter"`

	MaxPages       int64 `json:"max_pages"`
	ResultsPerPage int64 `json:"results_per_page"`
}

func (*ZoneRequest) Validate

func (zreq *ZoneRequest) Validate() error

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL