-
Notifications
You must be signed in to change notification settings - Fork 7
/
Copy pathclient.go
172 lines (155 loc) · 6 KB
/
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
package cloudconfigclient
import (
"context"
"encoding/base64"
"errors"
"fmt"
"net/http"
"os"
"strings"
"github.com/Piszmog/cfservices"
"golang.org/x/oauth2/clientcredentials"
)
const (
// ConfigServerName the service name of the Config Server in PCF.
ConfigServerName = "p-config-server"
// EnvironmentLocalConfigServerUrls is an environment variable for setting base URLs for local Config Servers.
EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS"
// SpringCloudConfigServerName the service name of the Spring Cloud Config Server in PCF.
SpringCloudConfigServerName = "p.config-server"
)
// Client contains the clients of the Config Servers.
type Client struct {
clients []*HTTPClient
}
// New creates a new Client based on the provided options. A Client can be configured to communicate with
// a local Config Server, an OAuth2 Server, and Config Servers in Cloud Foundry.
//
// At least one option must be provided.
func New(options ...Option) (*Client, error) {
var clients []*HTTPClient
if len(options) == 0 {
return nil, errors.New("at least one option must be provided")
}
for _, option := range options {
if err := option(&clients); err != nil {
return nil, err
}
}
return &Client{clients: clients}, nil
}
// Option creates a slice of httpClients per Config Server instance.
type Option func(*[]*HTTPClient) error
// LocalEnv creates a clients for a locally running Config Servers. The URLs to the Config Servers are acquired from the
// environment variable 'CONFIG_SERVER_URLS'.
func LocalEnv(client *http.Client) Option {
return func(clients *[]*HTTPClient) error {
httpClients, err := newLocalClientFromEnv(client)
if err != nil {
return err
}
*clients = append(*clients, httpClients...)
return nil
}
}
func newLocalClientFromEnv(client *http.Client) ([]*HTTPClient, error) {
localUrls := os.Getenv(EnvironmentLocalConfigServerUrls)
if len(localUrls) == 0 {
return nil, fmt.Errorf("no local Config Server URLs provided in environment variable %s", EnvironmentLocalConfigServerUrls)
}
return newSimpleClient(client, "", strings.Split(localUrls, ",")), nil
}
// Local creates a clients for a locally running Config Servers.
func Local(client *http.Client, urls ...string) Option {
return func(clients *[]*HTTPClient) error {
*clients = append(*clients, newSimpleClient(client, "", urls)...)
return nil
}
}
// Basic creates a clients for a Config Server based on the provided basic authentication information.
func Basic(client *http.Client, username, password string, urls ...string) Option {
return func(clients *[]*HTTPClient) error {
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(username+":"+password))
*clients = append(*clients, newSimpleClient(client, auth, urls)...)
return nil
}
}
func newSimpleClient(client *http.Client, auth string, urls []string) []*HTTPClient {
clients := make([]*HTTPClient, len(urls))
for index, baseURL := range urls {
clients[index] = &HTTPClient{BaseURL: baseURL, Client: client, Authorization: auth}
}
return clients
}
// DefaultCFService creates a clients for each Config Servers the application is bounded to in Cloud Foundry. The
// environment variable 'VCAP_SERVICES' provides a JSON that contains an entry with the key 'p-config-server' (v2.x)
// or 'p.config-server' (v3.x).
//
// The service 'p.config-server' is search for first. If not found, 'p-config-server' is searched for.
func DefaultCFService() Option {
return func(clients *[]*HTTPClient) error {
services, err := cfservices.GetServices()
if err != nil {
return fmt.Errorf("failed to parse 'VCAP_SERVICES': %w", err)
}
httpClients, err := newCloudClientForService(SpringCloudConfigServerName, services)
if err != nil {
if errors.Is(err, cfservices.MissingServiceError) {
httpClients, err = newCloudClientForService(ConfigServerName, services)
if err != nil {
if errors.Is(err, cfservices.MissingServiceError) {
return fmt.Errorf("neither %s or %s exist in environment variable 'VCAP_SERVICES'",
ConfigServerName, SpringCloudConfigServerName)
}
return err
}
} else {
return err
}
}
*clients = append(*clients, httpClients...)
return nil
}
}
// CFService creates a clients for each Config Servers the application is bounded to in Cloud Foundry. The environment
// variable 'VCAP_SERVICES' provides a JSON. The JSON should contain the entry matching the specified name. This
// entry and used to build an OAuth Client.
func CFService(service string) Option {
return func(clients *[]*HTTPClient) error {
services, err := cfservices.GetServices()
if err != nil {
return fmt.Errorf("failed to parse 'VCAP_SERVICES': %w", err)
}
httpClients, err := newCloudClientForService(service, services)
if err != nil {
return err
}
*clients = append(*clients, httpClients...)
return nil
}
}
func newCloudClientForService(name string, services map[string][]cfservices.Service) ([]*HTTPClient, error) {
creds, err := cfservices.GetServiceCredentials(services, name)
if err != nil {
return nil, fmt.Errorf("failed to create cloud Client: %w", err)
}
clients := make([]*HTTPClient, len(creds.Credentials))
for i, cred := range creds.Credentials {
clients[i] = &HTTPClient{BaseURL: cred.Uri, Client: newOAuth2Client(cred.ClientId, cred.ClientSecret, cred.AccessTokenUri)}
}
return clients, nil
}
// OAuth2 creates a Client for a Config Server based on the provided OAuth2.0 information.
func OAuth2(baseURL string, clientID string, secret string, tokenURI string) Option {
return func(clients *[]*HTTPClient) error {
*clients = append(*clients, &HTTPClient{BaseURL: baseURL, Client: newOAuth2Client(clientID, secret, tokenURI)})
return nil
}
}
func newOAuth2Client(clientID string, secret string, tokenURI string) *http.Client {
config := newOAuth2Config(clientID, secret, tokenURI)
return config.Client(context.Background())
}
func newOAuth2Config(clientID string, secret string, tokenURI string) *clientcredentials.Config {
return &clientcredentials.Config{ClientID: clientID, ClientSecret: secret, TokenURL: tokenURI}
}