mirror of
https://gitlab.com/psuapp/psu.git
synced 2024-08-30 18:12:34 +00:00
Split Portainer client source file into several function-related ones
This commit is contained in:
parent
f745392e41
commit
5d95af3681
32
client/auth.go
Normal file
32
client/auth.go
Normal file
@ -0,0 +1,32 @@
|
||||
package client
|
||||
|
||||
import "net/http"
|
||||
|
||||
// AuthenticateUserRequest represents the body of a request to POST /auth
|
||||
type AuthenticateUserRequest struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// AuthenticateUserResponse represents the body of a response for a request to POST /auth
|
||||
type AuthenticateUserResponse struct {
|
||||
Jwt string
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) Auth() (token string, err error) {
|
||||
reqBody := AuthenticateUserRequest{
|
||||
Username: n.user,
|
||||
Password: n.password,
|
||||
}
|
||||
|
||||
respBody := AuthenticateUserResponse{}
|
||||
|
||||
err = n.doJSON("auth", http.MethodPost, http.Header{}, &reqBody, &respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
token = respBody.Jwt
|
||||
|
||||
return
|
||||
}
|
48
client/auth_test.go
Normal file
48
client/auth_test.go
Normal file
@ -0,0 +1,48 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClientAuthenticates(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var body map[string]interface{}
|
||||
err := readRequestBodyAsJSON(req, &body)
|
||||
|
||||
assert.Equal(t, req.Method, http.MethodPost)
|
||||
assert.Equal(t, req.RequestURI, "/api/auth")
|
||||
assert.NotNil(t, req.Header["Content-Type"])
|
||||
assert.NotNil(t, req.Header["Content-Type"][0])
|
||||
assert.Equal(t, req.Header["Content-Type"][0], "application/json")
|
||||
assert.NotNil(t, req.Header["User-Agent"])
|
||||
assert.NotNil(t, req.Header["User-Agent"][0])
|
||||
assert.Equal(t, req.Header["User-Agent"][0], "GE007")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, body["Username"])
|
||||
assert.Equal(t, body["Username"], "admin")
|
||||
assert.NotNil(t, body["Password"])
|
||||
assert.Equal(t, body["Password"], "a")
|
||||
|
||||
writeResponseBodyAsJSON(w, map[string]interface{}{
|
||||
"jwt": "somerandomtoken",
|
||||
})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
apiURL, _ := url.Parse(ts.URL + "/api/")
|
||||
|
||||
customClient := NewClient(ts.Client(), Config{
|
||||
URL: apiURL,
|
||||
User: "admin",
|
||||
Password: "a",
|
||||
UserAgent: "GE007",
|
||||
})
|
||||
token, err := customClient.Auth()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, token, "somerandomtoken")
|
||||
}
|
159
client/client.go
159
client/client.go
@ -3,53 +3,13 @@ package client
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// StackListFilter represents a filter for a stack list
|
||||
type StackListFilter struct {
|
||||
SwarmID string `json:"SwarmId,omitempty"`
|
||||
EndpointID portainer.EndpointID `json:"EndpointId,omitempty"`
|
||||
}
|
||||
|
||||
// StackListOptions represents options passed to PortainerClient.StackList()
|
||||
type StackListOptions struct {
|
||||
Filter StackListFilter
|
||||
}
|
||||
|
||||
// StackCreateSwarmOptions represents options passed to PortainerClient.StackCreateSwarm()
|
||||
type StackCreateSwarmOptions struct {
|
||||
StackName string
|
||||
EnvironmentVariables []portainer.Pair
|
||||
StackFileContent string
|
||||
SwarmClusterID string
|
||||
EndpointID portainer.EndpointID
|
||||
}
|
||||
|
||||
// StackCreateComposeOptions represents options passed to PortainerClient.StackCreateCompose()
|
||||
type StackCreateComposeOptions struct {
|
||||
StackName string
|
||||
EnvironmentVariables []portainer.Pair
|
||||
StackFileContent string
|
||||
EndpointID portainer.EndpointID
|
||||
}
|
||||
|
||||
// StackUpdateOptions represents options passed to PortainerClient.StackUpdate()
|
||||
type StackUpdateOptions struct {
|
||||
Stack portainer.Stack
|
||||
EnvironmentVariables []portainer.Pair
|
||||
StackFileContent string
|
||||
Prune bool
|
||||
EndpointID portainer.EndpointID
|
||||
}
|
||||
|
||||
// Config represents a Portainer client configuration
|
||||
type Config struct {
|
||||
URL *url.URL
|
||||
@ -113,27 +73,6 @@ type portainerClientImp struct {
|
||||
afterResponseHooks []func(resp *http.Response) (err error)
|
||||
}
|
||||
|
||||
// Check if an http.Response object has errors
|
||||
func checkResponseForErrors(resp *http.Response) error {
|
||||
if 300 <= resp.StatusCode {
|
||||
// Guess it's a GenericError
|
||||
respBody := GenericError{}
|
||||
err := json.NewDecoder(resp.Body).Decode(&respBody)
|
||||
if err != nil {
|
||||
// It's not a GenericError
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return errors.New(string(bodyBytes))
|
||||
}
|
||||
return &respBody
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do an http request
|
||||
func (n *portainerClientImp) do(uri, method string, requestBody io.Reader, headers http.Header) (resp *http.Response, err error) {
|
||||
requestURL, err := n.url.Parse(uri)
|
||||
@ -234,104 +173,6 @@ func (n *portainerClientImp) AfterResponse(hook func(resp *http.Response) (err e
|
||||
n.afterResponseHooks = append(n.afterResponseHooks, hook)
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) Auth() (token string, err error) {
|
||||
reqBody := AuthenticateUserRequest{
|
||||
Username: n.user,
|
||||
Password: n.password,
|
||||
}
|
||||
|
||||
respBody := AuthenticateUserResponse{}
|
||||
|
||||
err = n.doJSON("auth", http.MethodPost, http.Header{}, &reqBody, &respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
token = respBody.Jwt
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) EndpointList() (endpoints []portainer.Endpoint, err error) {
|
||||
err = n.doJSONWithToken("endpoints", http.MethodGet, http.Header{}, nil, &endpoints)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) EndpointGroupList() (endpointGroups []portainer.EndpointGroup, err error) {
|
||||
err = n.doJSONWithToken("endpoint_groups", http.MethodGet, http.Header{}, nil, &endpointGroups)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackList(options StackListOptions) (stacks []portainer.Stack, err error) {
|
||||
filterJSONBytes, _ := json.Marshal(options.Filter)
|
||||
filterJSONString := string(filterJSONBytes)
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks?filters=%s", filterJSONString), http.MethodGet, http.Header{}, nil, &stacks)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackCreateSwarm(options StackCreateSwarmOptions) (stack portainer.Stack, err error) {
|
||||
reqBody := StackCreateRequest{
|
||||
Name: options.StackName,
|
||||
Env: options.EnvironmentVariables,
|
||||
SwarmID: options.SwarmClusterID,
|
||||
StackFileContent: options.StackFileContent,
|
||||
}
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%v", 1, "string", options.EndpointID), http.MethodPost, http.Header{}, &reqBody, &stack)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackCreateCompose(options StackCreateComposeOptions) (stack portainer.Stack, err error) {
|
||||
reqBody := StackCreateRequest{
|
||||
Name: options.StackName,
|
||||
Env: options.EnvironmentVariables,
|
||||
StackFileContent: options.StackFileContent,
|
||||
}
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%v", 2, "string", options.EndpointID), http.MethodPost, http.Header{}, &reqBody, &stack)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackUpdate(options StackUpdateOptions) (err error) {
|
||||
reqBody := StackUpdateRequest{
|
||||
Env: options.EnvironmentVariables,
|
||||
StackFileContent: options.StackFileContent,
|
||||
Prune: options.Prune,
|
||||
}
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks/%v?endpointId=%v", options.Stack.ID, options.EndpointID), http.MethodPut, http.Header{}, &reqBody, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackDelete(stackID portainer.StackID) (err error) {
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks/%d", stackID), http.MethodDelete, http.Header{}, nil, nil)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackFileInspect(stackID portainer.StackID) (content string, err error) {
|
||||
var respBody StackFileInspectResponse
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks/%v/file", stackID), http.MethodGet, http.Header{}, nil, &respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content = respBody.StackFileContent
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) EndpointDockerInfo(endpointID portainer.EndpointID) (info map[string]interface{}, err error) {
|
||||
err = n.doJSONWithToken(fmt.Sprintf("endpoints/%v/docker/info", endpointID), http.MethodGet, http.Header{}, nil, &info)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) Status() (status portainer.Status, err error) {
|
||||
err = n.doJSONWithToken("status", http.MethodGet, http.Header{}, nil, &status)
|
||||
return
|
||||
}
|
||||
|
||||
// NewClient creates a new Portainer API client
|
||||
func NewClient(httpClient *http.Client, config Config) PortainerClient {
|
||||
return &portainerClientImp{
|
||||
|
@ -1,7 +1,6 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -36,44 +35,6 @@ func TestNewClient(t *testing.T) {
|
||||
assert.NotNil(t, validClient)
|
||||
}
|
||||
|
||||
func TestClientAuthenticates(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var body map[string]interface{}
|
||||
err := readRequestBodyAsJSON(req, &body)
|
||||
|
||||
assert.Equal(t, req.Method, http.MethodPost)
|
||||
assert.Equal(t, req.RequestURI, "/api/auth")
|
||||
assert.NotNil(t, req.Header["Content-Type"])
|
||||
assert.NotNil(t, req.Header["Content-Type"][0])
|
||||
assert.Equal(t, req.Header["Content-Type"][0], "application/json")
|
||||
assert.NotNil(t, req.Header["User-Agent"])
|
||||
assert.NotNil(t, req.Header["User-Agent"][0])
|
||||
assert.Equal(t, req.Header["User-Agent"][0], "GE007")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, body["Username"])
|
||||
assert.Equal(t, body["Username"], "admin")
|
||||
assert.NotNil(t, body["Password"])
|
||||
assert.Equal(t, body["Password"], "a")
|
||||
|
||||
writeResponseBodyAsJSON(w, map[string]interface{}{
|
||||
"jwt": "somerandomtoken",
|
||||
})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
apiURL, _ := url.Parse(ts.URL + "/api/")
|
||||
|
||||
customClient := NewClient(ts.Client(), Config{
|
||||
URL: apiURL,
|
||||
User: "admin",
|
||||
Password: "a",
|
||||
UserAgent: "GE007",
|
||||
})
|
||||
token, err := customClient.Auth()
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, token, "somerandomtoken")
|
||||
}
|
||||
|
||||
func Test_portainerClientImp_do(t *testing.T) {
|
||||
type fields struct {
|
||||
user string
|
||||
@ -184,50 +145,3 @@ func Test_portainerClientImp_do(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkResponseForErrors(t *testing.T) {
|
||||
type args struct {
|
||||
resp *http.Response
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "generic error",
|
||||
args: args{
|
||||
resp: func() (resp *http.Response) {
|
||||
resp = &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(map[string]interface{}{
|
||||
"Err": "Error",
|
||||
"Details": "Not found",
|
||||
})
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "non generic error",
|
||||
args: args{
|
||||
resp: func() (resp *http.Response) {
|
||||
resp = &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte("Err"))),
|
||||
}
|
||||
return
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.wantErr, checkResponseForErrors(tt.args.resp) != nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
12
client/endpointGroup_list.go
Normal file
12
client/endpointGroup_list.go
Normal file
@ -0,0 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (n *portainerClientImp) EndpointGroupList() (endpointGroups []portainer.EndpointGroup, err error) {
|
||||
err = n.doJSONWithToken("endpoint_groups", http.MethodGet, http.Header{}, nil, &endpointGroups)
|
||||
return
|
||||
}
|
13
client/endpoint_docker_info.go
Normal file
13
client/endpoint_docker_info.go
Normal file
@ -0,0 +1,13 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (n *portainerClientImp) EndpointDockerInfo(endpointID portainer.EndpointID) (info map[string]interface{}, err error) {
|
||||
err = n.doJSONWithToken(fmt.Sprintf("endpoints/%v/docker/info", endpointID), http.MethodGet, http.Header{}, nil, &info)
|
||||
return
|
||||
}
|
12
client/endpoint_list.go
Normal file
12
client/endpoint_list.go
Normal file
@ -0,0 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (n *portainerClientImp) EndpointList() (endpoints []portainer.Endpoint, err error) {
|
||||
err = n.doJSONWithToken("endpoints", http.MethodGet, http.Header{}, nil, &endpoints)
|
||||
return
|
||||
}
|
16
client/errors.go
Normal file
16
client/errors.go
Normal file
@ -0,0 +1,16 @@
|
||||
package client
|
||||
|
||||
import "fmt"
|
||||
|
||||
// GenericError represents the body of a generic error returned by the Portainer API
|
||||
type GenericError struct {
|
||||
Err string
|
||||
Details string
|
||||
}
|
||||
|
||||
func (e *GenericError) Error() string {
|
||||
if e.Details != "" {
|
||||
return fmt.Sprintf("%s: %s", e.Err, e.Details)
|
||||
}
|
||||
return fmt.Sprintf("%s", e.Err)
|
||||
}
|
@ -3,48 +3,9 @@ package client
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetTranslatedStackType(t *testing.T) {
|
||||
type args struct {
|
||||
t portainer.StackType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "swarm stack type",
|
||||
args: args{
|
||||
t: portainer.DockerSwarmStack,
|
||||
},
|
||||
want: "swarm",
|
||||
},
|
||||
{
|
||||
name: "compose stack type",
|
||||
args: args{
|
||||
t: portainer.DockerComposeStack,
|
||||
},
|
||||
want: "compose",
|
||||
},
|
||||
{
|
||||
name: "unknown stack type",
|
||||
args: args{
|
||||
t: 100,
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, GetTranslatedStackType(tt.args.t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenericError_Error(t *testing.T) {
|
||||
type fields struct {
|
||||
Err string
|
26
client/stackFile_inspect.go
Normal file
26
client/stackFile_inspect.go
Normal file
@ -0,0 +1,26 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// StackFileInspectResponse represents the body of a response for a request to GET /stack/{id}/file
|
||||
type StackFileInspectResponse struct {
|
||||
StackFileContent string
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackFileInspect(stackID portainer.StackID) (content string, err error) {
|
||||
var respBody StackFileInspectResponse
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks/%v/file", stackID), http.MethodGet, http.Header{}, nil, &respBody)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
content = respBody.StackFileContent
|
||||
|
||||
return
|
||||
}
|
56
client/stack_create.go
Normal file
56
client/stack_create.go
Normal file
@ -0,0 +1,56 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// StackCreateComposeOptions represents options passed to PortainerClient.StackCreateCompose()
|
||||
type StackCreateComposeOptions struct {
|
||||
StackName string
|
||||
EnvironmentVariables []portainer.Pair
|
||||
StackFileContent string
|
||||
EndpointID portainer.EndpointID
|
||||
}
|
||||
|
||||
// StackCreateRequest represents the body of a request to POST /stacks
|
||||
type StackCreateRequest struct {
|
||||
Name string
|
||||
SwarmID string
|
||||
StackFileContent string
|
||||
Env []portainer.Pair `json:",omitempty"`
|
||||
}
|
||||
|
||||
// StackCreateSwarmOptions represents options passed to PortainerClient.StackCreateSwarm()
|
||||
type StackCreateSwarmOptions struct {
|
||||
StackName string
|
||||
EnvironmentVariables []portainer.Pair
|
||||
StackFileContent string
|
||||
SwarmClusterID string
|
||||
EndpointID portainer.EndpointID
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackCreateCompose(options StackCreateComposeOptions) (stack portainer.Stack, err error) {
|
||||
reqBody := StackCreateRequest{
|
||||
Name: options.StackName,
|
||||
Env: options.EnvironmentVariables,
|
||||
StackFileContent: options.StackFileContent,
|
||||
}
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%v", 2, "string", options.EndpointID), http.MethodPost, http.Header{}, &reqBody, &stack)
|
||||
return
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackCreateSwarm(options StackCreateSwarmOptions) (stack portainer.Stack, err error) {
|
||||
reqBody := StackCreateRequest{
|
||||
Name: options.StackName,
|
||||
Env: options.EnvironmentVariables,
|
||||
SwarmID: options.SwarmClusterID,
|
||||
StackFileContent: options.StackFileContent,
|
||||
}
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks?type=%v&method=%s&endpointId=%v", 1, "string", options.EndpointID), http.MethodPost, http.Header{}, &reqBody, &stack)
|
||||
return
|
||||
}
|
13
client/stack_delete.go
Normal file
13
client/stack_delete.go
Normal file
@ -0,0 +1,13 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (n *portainerClientImp) StackDelete(stackID portainer.StackID) (err error) {
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks/%d", stackID), http.MethodDelete, http.Header{}, nil, nil)
|
||||
return
|
||||
}
|
28
client/stack_list.go
Normal file
28
client/stack_list.go
Normal file
@ -0,0 +1,28 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// StackListFilter represents a filter for a stack list
|
||||
type StackListFilter struct {
|
||||
SwarmID string `json:"SwarmId,omitempty"`
|
||||
EndpointID portainer.EndpointID `json:"EndpointId,omitempty"`
|
||||
}
|
||||
|
||||
// StackListOptions represents options passed to PortainerClient.StackList()
|
||||
type StackListOptions struct {
|
||||
Filter StackListFilter
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackList(options StackListOptions) (stacks []portainer.Stack, err error) {
|
||||
filterJSONBytes, _ := json.Marshal(options.Filter)
|
||||
filterJSONString := string(filterJSONBytes)
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks?filters=%s", filterJSONString), http.MethodGet, http.Header{}, nil, &stacks)
|
||||
return
|
||||
}
|
35
client/stack_update.go
Normal file
35
client/stack_update.go
Normal file
@ -0,0 +1,35 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// StackUpdateOptions represents options passed to PortainerClient.StackUpdate()
|
||||
type StackUpdateOptions struct {
|
||||
Stack portainer.Stack
|
||||
EnvironmentVariables []portainer.Pair
|
||||
StackFileContent string
|
||||
Prune bool
|
||||
EndpointID portainer.EndpointID
|
||||
}
|
||||
|
||||
// StackUpdateRequest represents the body of a request to PUT /stacks/{id}
|
||||
type StackUpdateRequest struct {
|
||||
StackFileContent string
|
||||
Env []portainer.Pair `json:",omitempty"`
|
||||
Prune bool
|
||||
}
|
||||
|
||||
func (n *portainerClientImp) StackUpdate(options StackUpdateOptions) (err error) {
|
||||
reqBody := StackUpdateRequest{
|
||||
Env: options.EnvironmentVariables,
|
||||
StackFileContent: options.StackFileContent,
|
||||
Prune: options.Prune,
|
||||
}
|
||||
|
||||
err = n.doJSONWithToken(fmt.Sprintf("stacks/%v?endpointId=%v", options.Stack.ID, options.EndpointID), http.MethodPut, http.Header{}, &reqBody, nil)
|
||||
return
|
||||
}
|
12
client/status.go
Normal file
12
client/status.go
Normal file
@ -0,0 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
func (n *portainerClientImp) Status() (status portainer.Status, err error) {
|
||||
err = n.doJSONWithToken("status", http.MethodGet, http.Header{}, nil, &status)
|
||||
return
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// GetTranslatedStackType returns a stack's Type field (int) translated to it's human readable form (string)
|
||||
func GetTranslatedStackType(t portainer.StackType) string {
|
||||
switch t {
|
||||
case portainer.DockerSwarmStack:
|
||||
return "swarm"
|
||||
case portainer.DockerComposeStack:
|
||||
return "compose"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// StackCreateRequest represents the body of a request to POST /stacks
|
||||
type StackCreateRequest struct {
|
||||
Name string
|
||||
SwarmID string
|
||||
StackFileContent string
|
||||
Env []portainer.Pair `json:",omitempty"`
|
||||
}
|
||||
|
||||
// StackUpdateRequest represents the body of a request to PUT /stacks/{id}
|
||||
type StackUpdateRequest struct {
|
||||
StackFileContent string
|
||||
Env []portainer.Pair `json:",omitempty"`
|
||||
Prune bool
|
||||
}
|
||||
|
||||
// StackFileInspectResponse represents the body of a response for a request to GET /stack/{id}/file
|
||||
type StackFileInspectResponse struct {
|
||||
StackFileContent string
|
||||
}
|
||||
|
||||
// GenericError represents the body of a generic error returned by the Portainer API
|
||||
type GenericError struct {
|
||||
Err string
|
||||
Details string
|
||||
}
|
||||
|
||||
func (e *GenericError) Error() string {
|
||||
if e.Details != "" {
|
||||
return fmt.Sprintf("%s: %s", e.Err, e.Details)
|
||||
}
|
||||
return fmt.Sprintf("%s", e.Err)
|
||||
}
|
||||
|
||||
// AuthenticateUserRequest represents the body of a request to POST /auth
|
||||
type AuthenticateUserRequest struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// AuthenticateUserResponse represents the body of a response for a request to POST /auth
|
||||
type AuthenticateUserResponse struct {
|
||||
Jwt string
|
||||
}
|
44
client/utils.go
Normal file
44
client/utils.go
Normal file
@ -0,0 +1,44 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
)
|
||||
|
||||
// GetTranslatedStackType returns a stack's Type field (int) translated to it's human readable form (string)
|
||||
func GetTranslatedStackType(t portainer.StackType) string {
|
||||
switch t {
|
||||
case portainer.DockerSwarmStack:
|
||||
return "swarm"
|
||||
case portainer.DockerComposeStack:
|
||||
return "compose"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// Check if an http.Response object has errors
|
||||
func checkResponseForErrors(resp *http.Response) error {
|
||||
if 300 <= resp.StatusCode {
|
||||
// Guess it's a GenericError
|
||||
respBody := GenericError{}
|
||||
err := json.NewDecoder(resp.Body).Decode(&respBody)
|
||||
if err != nil {
|
||||
// It's not a GenericError
|
||||
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return errors.New(string(bodyBytes))
|
||||
}
|
||||
return &respBody
|
||||
}
|
||||
return nil
|
||||
}
|
97
client/utils_test.go
Normal file
97
client/utils_test.go
Normal file
@ -0,0 +1,97 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetTranslatedStackType(t *testing.T) {
|
||||
type args struct {
|
||||
t portainer.StackType
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "swarm stack type",
|
||||
args: args{
|
||||
t: portainer.DockerSwarmStack,
|
||||
},
|
||||
want: "swarm",
|
||||
},
|
||||
{
|
||||
name: "compose stack type",
|
||||
args: args{
|
||||
t: portainer.DockerComposeStack,
|
||||
},
|
||||
want: "compose",
|
||||
},
|
||||
{
|
||||
name: "unknown stack type",
|
||||
args: args{
|
||||
t: 100,
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, GetTranslatedStackType(tt.args.t))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_checkResponseForErrors(t *testing.T) {
|
||||
type args struct {
|
||||
resp *http.Response
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "generic error",
|
||||
args: args{
|
||||
resp: func() (resp *http.Response) {
|
||||
resp = &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
}
|
||||
bodyBytes, _ := json.Marshal(map[string]interface{}{
|
||||
"Err": "Error",
|
||||
"Details": "Not found",
|
||||
})
|
||||
resp.Body = ioutil.NopCloser(bytes.NewReader(bodyBytes))
|
||||
return
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "non generic error",
|
||||
args: args{
|
||||
resp: func() (resp *http.Response) {
|
||||
resp = &http.Response{
|
||||
StatusCode: http.StatusNotFound,
|
||||
Body: ioutil.NopCloser(bytes.NewReader([]byte("Err"))),
|
||||
}
|
||||
return
|
||||
}(),
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.wantErr, checkResponseForErrors(tt.args.resp) != nil)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user