-
Notifications
You must be signed in to change notification settings - Fork 170
/
crane_docker_client.go
196 lines (161 loc) · 5.83 KB
/
crane_docker_client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package dockerclient
import (
"fmt"
"net/http"
"net/url"
"path/filepath"
"github.com/Dataman-Cloud/crane/src/utils/config"
"github.com/Dataman-Cloud/crane/src/utils/cranerror"
"github.com/Dataman-Cloud/crane/src/utils/httpclient"
docker "github.com/Dataman-Cloud/go-dockerclient"
log "github.com/Sirupsen/logrus"
"golang.org/x/net/context"
)
type CraneDockerClient struct {
DockerClientInterface
// client connect to swarm cluster manager
// TODO swarm cluster has multiple manager and can be changed at runtime
// make sure swarmManager could connect to next if first one failed
swarmManager *docker.Client
// http client shared both for cluster connection & client connection
sharedHttpClient *httpclient.Client
swarmManagerHttpEndpoint string
config *config.Config
}
// initialize crane docker client
func NewCraneDockerClient(config *config.Config) (*CraneDockerClient, error) {
var err error
if config == nil {
return nil, fmt.Errorf("config can not be nil")
}
swarmManagerEntry := config.DockerEntryScheme + "://" + config.SwarmManagerIP + ":" + config.DockerEntryPort
client := &CraneDockerClient{
config: config,
swarmManagerHttpEndpoint: swarmManagerEntry,
}
if config.DockerTlsVerify {
client.swarmManager, err = NewGoDockerClientTls(swarmManagerEntry, config)
client.sharedHttpClient, err = NewHttpClientTls(config)
} else {
client.swarmManager, err = docker.NewVersionedClient(swarmManagerEntry, config.DockerApiVersion)
client.sharedHttpClient, err = NewHttpClient()
}
if err != nil {
log.Error("Unable to connect to docker daemon . Ensure docker is running endpoint ", swarmManagerEntry, "err: ", err)
return nil, err
}
if client.swarmManager == nil {
return nil, fmt.Errorf("create swarmManager client failed")
}
err = client.swarmManager.Ping()
if err != nil {
log.Error("Unable to ping docker daemon. Ensure docker is running endpoint ", swarmManagerEntry, "err: ", err)
return nil, err
}
return client, nil
}
// return swarm docker client
func (client *CraneDockerClient) SwarmManager() *docker.Client {
return client.swarmManager
}
// create a daemon docker client base on host id stored in ctx
func (client *CraneDockerClient) createNodeClient(nodeUrl *url.URL) (*docker.Client, error) {
var swarmNode *docker.Client
var err error
endpoint := nodeUrl.String()
if nodeUrl.Scheme == "https" {
swarmNode, err = NewGoDockerClientTls(endpoint, client.config)
} else {
//swarmNode, err = docker.NewVersionedClient(endpoint, API_VERSION)
swarmNode, err = docker.NewClient(endpoint)
}
return swarmNode, err
}
// create node client: got endpoint by node label
// verify if node id matches the endpoint against docker info
func (client *CraneDockerClient) SwarmNode(ctx context.Context) (*docker.Client, error) {
nodeId, ok := ctx.Value("node_id").(string)
if !ok {
return nil, &cranerror.CraneError{
Code: CodeConnToNodeError,
Err: &cranerror.NodeConnError{
ID: nodeId,
Err: fmt.Errorf("parse node id failed"),
},
}
}
nodeUrl, err := client.GetDaemonUrlById(nodeId)
if err != nil {
return nil, err
}
if err := client.VerifyNodeEndpoint(nodeId, nodeUrl); err != nil {
return nil, err
}
nodeClient, err := client.createNodeClient(nodeUrl)
if err != nil {
return nil, &cranerror.CraneError{
Code: CodeConnToNodeError,
Err: &cranerror.NodeConnError{ID: nodeId, Err: err},
}
}
return nodeClient, nil
}
func (client *CraneDockerClient) VerifyNodeEndpoint(nodeId string, nodeUrl *url.URL) error {
if nodeUrl == nil {
return &cranerror.CraneError{
Code: CodeGetNodeEndpointError,
Err: &cranerror.NodeConnError{ID: nodeId, Err: fmt.Errorf("verify endpoint failed: empty node url")},
}
}
returnedNodeId, err := client.getNodeIdByUrl(nodeUrl)
if err != nil {
return &cranerror.CraneError{
Code: CodeVerifyNodeEnpointFailed,
Err: &cranerror.NodeConnError{ID: nodeId, Endpoint: nodeUrl.String(), Err: fmt.Errorf("verify endpoint failed: %s", err.Error())},
}
}
if nodeId != returnedNodeId {
return &cranerror.CraneError{
Code: CodeNodeEndpointIpMatchError,
Err: &cranerror.NodeConnError{ID: nodeId, Endpoint: nodeUrl.String(), Err: fmt.Errorf("node id not matched endpoint")},
}
}
return nil
}
func NewGoDockerClientTls(endpoint string, config *config.Config) (*docker.Client, error) {
tlsCaCert, tlsCert, tlsKey := SharedClientCertFiles(config)
return docker.NewVersionedTLSClient(endpoint, tlsCert, tlsKey, tlsCaCert, config.DockerApiVersion)
}
func NewHttpClient() (*httpclient.Client, error) {
httpClient := &http.Client{Timeout: defaultHttpRequestTimeout}
return httpclient.NewClient(httpClient, nil)
}
func NewHttpClientTls(config *config.Config) (*httpclient.Client, error) {
tlsCaCert, tlsCert, tlsKey := SharedClientCertFiles(config)
httpClient := &http.Client{Timeout: defaultHttpRequestTimeout}
return httpclient.NewTLSClient(tlsCaCert, tlsCert, tlsKey, httpClient, nil)
}
func SharedClientCertFiles(config *config.Config) (string, string, string) {
tlsCaCert := filepath.Join(config.DockerCertPath, "ca.pem")
tlsCert := filepath.Join(config.DockerCertPath, "cert.pem")
tlsKey := filepath.Join(config.DockerCertPath, "key.pem")
return tlsCaCert, tlsCert, tlsKey
}
func ToCraneError(err error) error {
var detailError error
switch err.(type) {
case *docker.NoSuchContainer:
detailError = cranerror.NewError(CodeContainerInvalid, err.Error())
case *docker.NoSuchNetwork:
detailError = cranerror.NewError(CodeNetworkInvalid, err.Error())
case *docker.NoSuchNetworkOrContainer:
detailError = cranerror.NewError(CodeNetworkOrContainerInvalid, err.Error())
case *docker.ContainerAlreadyRunning:
detailError = cranerror.NewError(CodeContainerAlreadyRunning, err.Error())
case *docker.ContainerNotRunning:
detailError = cranerror.NewError(CodeContainerNotRunning, err.Error())
default:
detailError = err
}
return detailError
}