diff --git a/pkg/api/cloud/db/add.go b/pkg/api/cloud/db/add.go new file mode 100644 index 0000000..c4804e4 --- /dev/null +++ b/pkg/api/cloud/db/add.go @@ -0,0 +1,36 @@ +package db + +import ( + "context" + "net/url" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Add creates a new cloud database. +func (s *Client) Add(ctx context.Context, request AddRequest) (response AddResponse, err error) { + uri := "cloud/db/add.json" + keys := []string{ + "server_name", + "mysql_host", + "database", + "container", + } + + values := url.Values{} + values.Add("server_name", request.ServerName) + values.Add("mysql_host", request.MySQLHost) + values.Add("database", request.Database) + values.Add("container", request.Container) + + req, err := s.client.NewRequest("POST", uri, utils.Encode(values, keys)) + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/delete.go b/pkg/api/cloud/db/delete.go new file mode 100644 index 0000000..af808ba --- /dev/null +++ b/pkg/api/cloud/db/delete.go @@ -0,0 +1,34 @@ +package db + +import ( + "context" + "net/url" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Delete deletes a cloud database. +func (s *Client) Delete(ctx context.Context, request DeleteRequest) (response DeleteResponse, err error) { + uri := "cloud/db/delete.json" + keys := []string{ + "server_name", + "mysql_host", + "database", + } + + values := url.Values{} + values.Add("server_name", request.ServerName) + values.Add("mysql_host", request.MySQLHost) + values.Add("database", request.Database) + + req, err := s.client.NewRequest("POST", uri, utils.Encode(values, keys)) + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/doc.go b/pkg/api/cloud/db/doc.go new file mode 100644 index 0000000..ec26352 --- /dev/null +++ b/pkg/api/cloud/db/doc.go @@ -0,0 +1,2 @@ +// Package db provides access to the `/cloud/db` API endpoint. +package db diff --git a/pkg/api/cloud/db/get.go b/pkg/api/cloud/db/get.go new file mode 100644 index 0000000..3090932 --- /dev/null +++ b/pkg/api/cloud/db/get.go @@ -0,0 +1,39 @@ +package db + +import ( + "context" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Get fetches a cloud db. +func (s *Client) Get(ctx context.Context, request GetRequest) (response GetResponse, err error) { + uri := "cloud/db/get.json" + keys := []string{ + "apikey", + "client_id", + "server_name", + "mysql_host", + "database", + } + + req, err := s.client.NewRequest("GET", uri, "") + if err != nil { + return response, err + } + + v := req.URL.Query() + v.Add("api_key", s.client.APIKey) + v.Add("client_id", s.client.ClientID) + v.Add("server_name", request.ServerName) + v.Add("mysql_host", request.MySQLHost) + v.Add("database", request.Database) + + req.URL.RawQuery = utils.Encode(v, keys) + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/list.go b/pkg/api/cloud/db/list.go new file mode 100644 index 0000000..d5aa228 --- /dev/null +++ b/pkg/api/cloud/db/list.go @@ -0,0 +1,28 @@ +package db + +import ( + "context" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// List returns a list of cloud databases, specific to the customer. +func (s *Client) List(ctx context.Context, opt ListOptions) (response ListResponse, err error) { + uri := "cloud/db/list_all.json" + + path, err := utils.AddOptions(uri, opt) + if err != nil { + return response, err + } + + req, err := s.client.NewRequest("GET", path, "") + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/models.go b/pkg/api/cloud/db/models.go new file mode 100644 index 0000000..2ddf704 --- /dev/null +++ b/pkg/api/cloud/db/models.go @@ -0,0 +1,19 @@ +package db + +import ( + "github.com/sitehostnz/gosh/pkg/api" +) + +type ( + // Client is a Service to work with API Cloud/DB. + Client struct { + client *api.Client + } +) + +// New is an initialisation function. +func New(c *api.Client) *Client { + return &Client{ + client: c, + } +} diff --git a/pkg/api/cloud/db/request.go b/pkg/api/cloud/db/request.go new file mode 100644 index 0000000..819aa63 --- /dev/null +++ b/pkg/api/cloud/db/request.go @@ -0,0 +1,45 @@ +package db + +type ( + // AddRequest adds/creates a new database on the given cloud server. + AddRequest struct { + ServerName string `url:"server_name"` + MySQLHost string `url:"mysql_host"` + Database string `url:"database"` + Container string `url:"container"` + } + + // DeleteRequest a request to delete the database. + DeleteRequest struct { + ServerName string `url:"server_name"` + MySQLHost string `url:"mysql_host"` + Database string `url:"database"` + } + + // UpdateRequest is a request to the update endpoint, it only changes the backup container. + UpdateRequest struct { + ServerName string `url:"server_name"` + MySQLHost string `url:"mysql_host"` + Database string `url:"database"` + Container string `url:"params[container]'"` + } + + // ListOptions are options for filtering/listing databases. + ListOptions struct { + ServerName string `url:"filters[server_name],omitempty"` + MySQLHost string `url:"filters[mysql_host],omitempty"` + Database string `url:"filters[db_name],omitempty"` + + SortBy string `url:"filters[sort_by],omitempty"` + SortDir string `url:"filters[sort_dir],omitempty"` + PageSize int `url:"filters[page_size],omitempty"` + PageNumber int `url:"filters[page_number],omitempty"` + } + + // GetRequest is for getting a single database. + GetRequest struct { + ServerName string `json:"server_name"` + MySQLHost string `json:"mysql_host"` + Database string `json:"database"` + } +) diff --git a/pkg/api/cloud/db/response.go b/pkg/api/cloud/db/response.go new file mode 100644 index 0000000..7677525 --- /dev/null +++ b/pkg/api/cloud/db/response.go @@ -0,0 +1,41 @@ +package db + +import "github.com/sitehostnz/gosh/pkg/models" + +type ( + // AddResponse is the return for adding a cloud database. + AddResponse struct { + Return struct { + JobID string `json:"job_id"` + } `json:"return"` + models.APIResponse + } + + // UpdateResponse is the response from updating a database. + UpdateResponse struct { + models.APIResponse + } + + // DeleteResponse is the return from deleting. + DeleteResponse struct { + Return struct { + JobID string `json:"job_id"` + } `json:"return"` + models.APIResponse + } + + // GetResponse is the return value from a get request. + GetResponse struct { + Database models.Database `json:"return"` + models.APIResponse + } + + // ListResponse is the returned response from the List endpoint. + ListResponse struct { + Return struct { + Databases []models.Database `json:"data"` + models.Pagination + } `json:"return"` + models.APIResponse + } +) diff --git a/pkg/api/cloud/db/update.go b/pkg/api/cloud/db/update.go new file mode 100644 index 0000000..2daf6bd --- /dev/null +++ b/pkg/api/cloud/db/update.go @@ -0,0 +1,36 @@ +package db + +import ( + "context" + "net/url" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Update updates the database's backup location. +func (s *Client) Update(ctx context.Context, request UpdateRequest) (response UpdateResponse, err error) { + uri := "cloud/db/update.json" + keys := []string{ + "server_name", + "mysql_host", + "database", + "params[container]", + } + + values := url.Values{} + values.Add("server_name", request.ServerName) + values.Add("mysql_host", request.MySQLHost) + values.Add("database", request.Database) + values.Add("params[container]", request.Container) + + req, err := s.client.NewRequest("POST", uri, utils.Encode(values, keys)) + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/user/add.go b/pkg/api/cloud/db/user/add.go new file mode 100644 index 0000000..c83ecf0 --- /dev/null +++ b/pkg/api/cloud/db/user/add.go @@ -0,0 +1,41 @@ +package user + +import ( + "context" + "net/url" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Add creates a new for cloud database user. +func (s *Client) Add(ctx context.Context, request AddRequest) (response AddResponse, err error) { + uri := "cloud/db/user/add.json" + keys := []string{ + "server_name", + "mysql_host", + "username", + "password", + "database", + "grants[]", + } + + values := url.Values{} + values.Add("server_name", request.ServerName) + values.Add("mysql_host", request.MySQLHost) + values.Add("username", request.Username) + values.Add("password", request.Password) + values.Add("database", request.Database) + + // database=testdb2, grants[0]=select, grants[1]=insert, grants[2]=update, grants[3]=delete, grants[4]=create, grants[5]=drop, grants[6]=alter, grants[7]=index, grants[8]=create view, grants[9]=show view, grants[10]=lock tables, grants[11]=create temporary tables + + req, err := s.client.NewRequest("POST", uri, utils.Encode(values, keys)) + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/user/delete.go b/pkg/api/cloud/db/user/delete.go new file mode 100644 index 0000000..7afbf2b --- /dev/null +++ b/pkg/api/cloud/db/user/delete.go @@ -0,0 +1,34 @@ +package user + +import ( + "context" + "net/url" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Delete deletes a cloud user database. +func (s *Client) Delete(ctx context.Context, request DeleteRequest) (response DeleteResponse, err error) { + uri := "cloud/db/user/delete.json" + keys := []string{ + "server_name", + "mysql_host", + "username", + } + + values := url.Values{} + values.Add("server_name", request.ServerName) + values.Add("mysql_host", request.MySQLHost) + values.Add("username", request.Username) + + req, err := s.client.NewRequest("POST", uri, utils.Encode(values, keys)) + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/user/doc.go b/pkg/api/cloud/db/user/doc.go new file mode 100644 index 0000000..214dabc --- /dev/null +++ b/pkg/api/cloud/db/user/doc.go @@ -0,0 +1,2 @@ +// Package user provides access to the `/cloud/db/user` API endpoint. +package user diff --git a/pkg/api/cloud/db/user/get.go b/pkg/api/cloud/db/user/get.go new file mode 100644 index 0000000..82506c7 --- /dev/null +++ b/pkg/api/cloud/db/user/get.go @@ -0,0 +1,39 @@ +package user + +import ( + "context" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// Get fetches a cloud user database. +func (s *Client) Get(ctx context.Context, request GetRequest) (response GetResponse, err error) { + uri := "cloud/db/user/get.json" + keys := []string{ + "apikey", + "client_id", + "server_name", + "mysql_host", + "username", + } + + req, err := s.client.NewRequest("GET", uri, "") + if err != nil { + return response, err + } + + v := req.URL.Query() + v.Add("api_key", s.client.APIKey) + v.Add("client_id", s.client.ClientID) + v.Add("server_name", request.ServerName) + v.Add("mysql_host", request.MySQLHost) + v.Add("username", request.Username) + + req.URL.RawQuery = utils.Encode(v, keys) + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/user/list_all.go b/pkg/api/cloud/db/user/list_all.go new file mode 100644 index 0000000..bc49354 --- /dev/null +++ b/pkg/api/cloud/db/user/list_all.go @@ -0,0 +1,28 @@ +package user + +import ( + "context" + + "github.com/sitehostnz/gosh/pkg/utils" +) + +// List returns a list of cloud databases users, specific to the customer. +func (s *Client) List(ctx context.Context, opt ListOptions) (response ListResponse, err error) { + uri := "cloud/db/user/list_all.json" + + path, err := utils.AddOptions(uri, opt) + if err != nil { + return response, err + } + + req, err := s.client.NewRequest("GET", path, "") + if err != nil { + return response, err + } + + if err := s.client.Do(ctx, req, &response); err != nil { + return response, err + } + + return response, nil +} diff --git a/pkg/api/cloud/db/user/models.go b/pkg/api/cloud/db/user/models.go new file mode 100644 index 0000000..ae03349 --- /dev/null +++ b/pkg/api/cloud/db/user/models.go @@ -0,0 +1,19 @@ +package user + +import ( + "github.com/sitehostnz/gosh/pkg/api" +) + +type ( + // Client is a Service to work with API Cloud/DB/User. + Client struct { + client *api.Client + } +) + +// New is an initialisation function. +func New(c *api.Client) *Client { + return &Client{ + client: c, + } +} diff --git a/pkg/api/cloud/db/user/request.go b/pkg/api/cloud/db/user/request.go new file mode 100644 index 0000000..c16bfda --- /dev/null +++ b/pkg/api/cloud/db/user/request.go @@ -0,0 +1,46 @@ +package user + +type ( + // AddRequest adds/creates a new database user. + AddRequest struct { + ServerName string `url:"server_name"` + MySQLHost string `url:"mysql_host"` + Username string `url:"username"` + Password string `url:"password"` + Database string `url:"database"` + Grants []string `url:"grants"` + } + + // DeleteRequest a request to delete the database user. + DeleteRequest struct { + ServerName string `url:"server_name"` + MySQLHost string `url:"mysql_host"` + Username string `url:"username"` + } + + // ListOptions are options for filtering/listing database users. + ListOptions struct { + ServerName string `url:"filters[server_name],omitempty"` + MySQLHost string `url:"filters[mysql_host],omitempty"` + Username string `url:"filters[username],omitempty"` + SortBy string `url:"filters[sort_by],omitempty"` + SortDir string `url:"filters[sort_dir],omitempty"` + PageSize int `url:"filters[page_size],omitempty"` + PageNumber int `url:"filters[page_number],omitempty"` + } + + // UpdateRequest is a request to the update endpoint, it only changes the backup container. + UpdateRequest struct { + ServerName string `url:"server_name"` + MySQLHost string `url:"mysql_host"` + Database string `url:"database"` + Container string `url:"params[container]'"` + } + + // GetRequest is for getting a single database user. + GetRequest struct { + ServerName string `json:"server_name"` + MySQLHost string `json:"mysql_host"` + Username string `json:"username"` + } +) diff --git a/pkg/api/cloud/db/user/response.go b/pkg/api/cloud/db/user/response.go new file mode 100644 index 0000000..0f0ddcf --- /dev/null +++ b/pkg/api/cloud/db/user/response.go @@ -0,0 +1,41 @@ +package user + +import "github.com/sitehostnz/gosh/pkg/models" + +type ( + // AddResponse is the return for adding a cloud database user. + AddResponse struct { + Return struct { + JobID string `json:"job_id"` + } `json:"return"` + models.APIResponse + } + + // UpdateResponse is the response from updating a database user. + UpdateResponse struct { + models.APIResponse + } + + // DeleteResponse is the return from deleting database user. + DeleteResponse struct { + Return struct { + JobID string `json:"job_id"` + } `json:"return"` + models.APIResponse + } + + // GetResponse is the return value from a get request. + GetResponse struct { + Database models.Database `json:"return"` + models.APIResponse + } + + // ListResponse is the returned response from the List endpoint. + ListResponse struct { + Return struct { + Databases []models.Database `json:"data"` + models.Pagination + } `json:"return"` + models.APIResponse + } +) diff --git a/pkg/api/cloud/db/user/update.go b/pkg/api/cloud/db/user/update.go new file mode 100644 index 0000000..a00006b --- /dev/null +++ b/pkg/api/cloud/db/user/update.go @@ -0,0 +1 @@ +package user diff --git a/pkg/api/cloud/stack/add.go b/pkg/api/cloud/stack/add.go index c1058e8..655429f 100644 --- a/pkg/api/cloud/stack/add.go +++ b/pkg/api/cloud/stack/add.go @@ -6,8 +6,10 @@ import ( "net/url" "strconv" + "github.com/sitehostnz/gosh/pkg/api/cloud/stack/image" "github.com/sitehostnz/gosh/pkg/models" "github.com/sitehostnz/gosh/pkg/utils" + "gopkg.in/yaml.v2" ) // Add creates a new cloud stack. @@ -72,7 +74,7 @@ func (s *Client) AddWithImage(ctx context.Context, request AddRequestWithImage) values.Add("enable_ssl", strconv.Itoa(request.EnableSSL)) // Generate Docker Compose file - dockerCompose, err := utils.GenerateDockerCompose(ctx, s.client, models.GenerateDockerComposeRequest{ + dockerCompose, err := s.GenerateDockerCompose(ctx, models.GenerateDockerComposeRequest{ Name: request.Name, Label: request.Label, ImageCode: request.ImageCode, @@ -104,3 +106,87 @@ func (s *Client) AddWithImage(ctx context.Context, request AddRequestWithImage) return response, nil } + +// GenerateDockerCompose generates a docker compose file for a stack. +func (s *Client) GenerateDockerCompose(ctx context.Context, request models.GenerateDockerComposeRequest) (dockerCompose string, err error) { + // Get the image + i := image.New(s.client) + image, err := i.GetImageByCode(ctx, image.GetRequest{Code: request.ImageCode}) + if err != nil { + return dockerCompose, err + } + + // registry url (staging) + registryPath := "registry-staging.sitehost.co.nz" + imageLastVersion := image.Versions[len(image.Versions)-1].Version + + // create volumes and ports + volumes := []string{} + for folder, volume := range image.Labels.NzSitehostImageVolumes.Volumes { + volumes = append(volumes, "/data/docker0/"+image.Labels.NzSitehostImageType+"/"+request.Name+"/"+folder+":"+volume.Dest+":"+volume.Mode) + } + + ports := []string{} + + for port, portInfo := range image.Labels.NzSitehostImagePorts { + if portInfo.Exposed { + ports = append(ports, port+"/"+portInfo.Protocol) + } + } + + compose, err := buildDockerCompose(models.BuildDockerCompose{ + Name: request.Name, + Label: request.Label, + Image: registryPath + "/" + image.Code + ":" + imageLastVersion, + Type: image.Labels.NzSitehostImageType, + Ports: ports, + Volumes: volumes, + }) + if err != nil { + return dockerCompose, err + } + + return compose, nil +} + +func buildDockerCompose(request models.BuildDockerCompose) (dockerCompose string, err error) { + // Create docker compose file + compose := models.DockerCompose{ + Version: "2.1", + Services: map[string]models.Service{ + request.Name: { + ContainerName: request.Name, + Environment: []string{ + "VIRTUAL_HOST=" + request.Label, + "CERT_NAME=" + request.Label, + }, + Expose: request.Ports, + Image: request.Image, + Labels: []string{ + "nz.sitehost.container.website.vhosts=" + request.Label, + "nz.sitehost.container.image_update=True", + "nz.sitehost.container.label=" + request.Label, + "nz.sitehost.container.type=" + request.Type, + "nz.sitehost.container.monitored=True", + "nz.sitehost.container.backup_disable=False", + }, + Restart: "unless-stopped", + Volumes: request.Volumes, + }, + }, + Networks: models.Networks{ + Default: models.DefaultNetwork{ + External: models.ExternalNetwork{ + Name: "infra_default", + }, + }, + }, + } + + composeYaml, err := yaml.Marshal(&compose) + if err != nil { + return dockerCompose, err + } + + return string(composeYaml), nil +} diff --git a/pkg/models/db.go b/pkg/models/db.go new file mode 100644 index 0000000..4abc641 --- /dev/null +++ b/pkg/models/db.go @@ -0,0 +1,36 @@ +package models + +type ( + // Grant represents database grants. + Grant struct { + DBName string `json:"db_name"` + Username string `json:"username"` + Host string `json:"host"` + MySQLHost string `json:"mysql_host"` + Grants []string `json:"grants"` + Pending interface{} `json:"pending"` + IsMissing string `json:"is_missing"` + DateAdded string `json:"date_added"` + DateUpdated string `json:"date_updated"` + } + + // Database represents a database on a cloud server, some properties may or may not be populated depending on how we call. + Database struct { + ID string `json:"id"` + DBName string `json:"db_name"` + MySQLHost string `json:"mysql_host"` + Size interface{} `json:"size"` + ClientID string `json:"client_id"` + ServerID string `json:"server_id"` + Pending string `json:"pending"` + IsMissing string `json:"is_missing"` + DateAdded string `json:"date_added"` + DateUpdated string `json:"date_updated"` + ServerName string `json:"server_name"` + ServerLabel string `json:"server_label"` + ServerIP string `json:"server_ip"` + ServerOwner bool `json:"server_owner"` + Container string `json:"container"` + Grants []Grant `json:"grants"` + } +) diff --git a/pkg/utils/docker.go b/pkg/utils/docker.go deleted file mode 100644 index 62f6830..0000000 --- a/pkg/utils/docker.go +++ /dev/null @@ -1,94 +0,0 @@ -package utils - -import ( - "context" - - "github.com/sitehostnz/gosh/pkg/api" - "github.com/sitehostnz/gosh/pkg/api/cloud/image" - "github.com/sitehostnz/gosh/pkg/models" - "gopkg.in/yaml.v2" -) - -// GenerateDockerCompose generates a docker compose file for a stack. -func GenerateDockerCompose(ctx context.Context, client *api.Client, request models.GenerateDockerComposeRequest) (dockerCompose string, err error) { - // Get the image - i := image.New(client) - image, err := i.GetImageByCode(ctx, image.GetRequest{Code: request.ImageCode}) - if err != nil { - return dockerCompose, err - } - - // registry url (staging) - registryPath := "registry-staging.sitehost.co.nz" - imageLastVersion := image.Versions[len(image.Versions)-1].Version - - // create volumes and ports - volumes := []string{} - for folder, volume := range image.Labels.NzSitehostImageVolumes.Volumes { - volumes = append(volumes, "/data/docker0/"+image.Labels.NzSitehostImageType+"/"+request.Name+"/"+folder+":"+volume.Dest+":"+volume.Mode) - } - - ports := []string{} - - for port, portInfo := range image.Labels.NzSitehostImagePorts { - if portInfo.Exposed { - ports = append(ports, port+"/"+portInfo.Protocol) - } - } - - compose, err := buildDockerCompose(models.BuildDockerCompose{ - Name: request.Name, - Label: request.Label, - Image: registryPath + "/" + image.Code + ":" + imageLastVersion, - Type: image.Labels.NzSitehostImageType, - Ports: ports, - Volumes: volumes, - }) - if err != nil { - return dockerCompose, err - } - - return compose, nil -} - -func buildDockerCompose(request models.BuildDockerCompose) (dockerCompose string, err error) { - // Create docker compose file - compose := models.DockerCompose{ - Version: "2.1", - Services: map[string]models.Service{ - request.Name: { - ContainerName: request.Name, - Environment: []string{ - "VIRTUAL_HOST=" + request.Label, - "CERT_NAME=" + request.Label, - }, - Expose: request.Ports, - Image: request.Image, - Labels: []string{ - "nz.sitehost.container.website.vhosts=" + request.Label, - "nz.sitehost.container.image_update=True", - "nz.sitehost.container.label=" + request.Label, - "nz.sitehost.container.type=" + request.Type, - "nz.sitehost.container.monitored=True", - "nz.sitehost.container.backup_disable=False", - }, - Restart: "unless-stopped", - Volumes: request.Volumes, - }, - }, - Networks: models.Networks{ - Default: models.DefaultNetwork{ - External: models.ExternalNetwork{ - Name: "infra_default", - }, - }, - }, - } - - composeYaml, err := yaml.Marshal(&compose) - if err != nil { - return dockerCompose, err - } - - return string(composeYaml), nil -}