diff --git a/.travis.yml b/.travis.yml index 8d2afba..b80a5a6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,10 @@ language: go go: -- "1.12" + - "1.13" env: -- GO111MODULE=on + - GO111MODULE=on notifications: email: @@ -14,19 +14,19 @@ notifications: matrix: # It's ok if our code fails on unstable development versions of Go. allow_failures: - - go: develop + - go: develop # Don't wait for tip tests to finish. Mark the test run green if the # tests pass on the stable versions of Go. fast_finish: true cache: directories: - - $GOPATH/pkg - - $GOPATH/bin + - $GOPATH/pkg + - $GOPATH/bin before_install: -- go get golang.org/x/tools/cmd/cover -- go get github.com/mattn/goveralls + - go get golang.org/x/tools/cmd/cover + - go get github.com/mattn/goveralls script: -- go test -v ./... -covermode=count -coverprofile=coverage.out -- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci \ No newline at end of file + - go test -v ./... -covermode=count -coverprofile=coverage.out + - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci \ No newline at end of file diff --git a/client.go b/client.go index 8463d77..5514a17 100644 --- a/client.go +++ b/client.go @@ -1,11 +1,21 @@ package cloudconfigclient import ( + "encoding/json" + "fmt" "github.com/Piszmog/cloudconfigclient/net" - "github.com/pkg/errors" "net/http" ) +// NotFoundError is a special error that is used to propagate 404s. +type NotFoundError struct { +} + +// Error return the error message. +func (r NotFoundError) Error() string { + return "failed to find resource" +} + // ConfigClient contains the clients of the Config Servers. type ConfigClient struct { Clients []CloudClient @@ -26,5 +36,27 @@ type Client struct { func (client Client) Get(uriVariables ...string) (resp *http.Response, err error) { fullUrl := net.CreateUrl(client.configUri, uriVariables...) response, err := client.httpClient.Get(fullUrl) - return response, errors.Wrapf(err, "failed to retrieve from %s", fullUrl) + if err != nil { + return nil, fmt.Errorf("failed to retrieve from %s: %w", fullUrl, err) + } + return response, nil +} + +func getResource(client CloudClient, dest interface{}, uriVariables ...string) error { + resp, err := client.Get(uriVariables...) + if err != nil { + return fmt.Errorf("failed to retrieve application configurations: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode == 404 { + return &NotFoundError{} + } + if resp.StatusCode != 200 { + return fmt.Errorf("server responded with status code %d", resp.StatusCode) + } + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(dest); err != nil { + return fmt.Errorf("failed to decode response from url: %w", err) + } + return nil } diff --git a/configuration.go b/configuration.go index 49aee10..8f8a7f9 100644 --- a/configuration.go +++ b/configuration.go @@ -1,9 +1,9 @@ package cloudconfigclient import ( - "encoding/json" + "errors" + "fmt" "github.com/Piszmog/cloudconfigclient/net" - "github.com/pkg/errors" ) // Source is the application's source configurations. It con contain zero to n number of property sources. @@ -31,26 +31,17 @@ type Configuration interface { // GetConfiguration retrieves the configurations/property sources of an application based on the name of the application // and the profiles of the application. -func (configClient ConfigClient) GetConfiguration(applicationName string, profiles []string) (*Source, error) { - for _, client := range configClient.Clients { - resp, err := client.Get(applicationName, net.JoinProfiles(profiles)) - if resp != nil && resp.StatusCode == 404 { - continue +func (c ConfigClient) GetConfiguration(applicationName string, profiles []string) (Source, error) { + var source Source + for _, client := range c.Clients { + if err := getResource(client, &source, applicationName, net.JoinProfiles(profiles)); err != nil { + var notFoundError *NotFoundError + if errors.As(err, ¬FoundError) { + continue + } + return Source{}, err } - if err != nil { - return nil, errors.Wrap(err, "failed to retrieve application configurations") - } - if resp.StatusCode != 200 { - return nil, errors.Errorf("server responded with status code %d", resp.StatusCode) - } - configuration := &Source{} - decoder := json.NewDecoder(resp.Body) - err = decoder.Decode(configuration) - resp.Body.Close() - if err != nil { - return nil, errors.Wrap(err, "failed to decode response from url") - } - return configuration, nil + return source, nil } - return nil, errors.Errorf("failed to find configuration for application %s with profiles %s", applicationName, profiles) + return Source{}, fmt.Errorf("failed to find configuration for application %s with profiles %s", applicationName, profiles) } diff --git a/configuration_test.go b/configuration_test.go index 07df090..8b3bf05 100644 --- a/configuration_test.go +++ b/configuration_test.go @@ -25,55 +25,40 @@ const ( func TestConfigClient_GetConfiguration(t *testing.T) { configClient := createMockConfigClient(200, configurationSource, nil) - configuration, err := configClient.GetConfiguration("appName", []string{"profile"}) + _, err := configClient.GetConfiguration("appName", []string{"profile"}) if err != nil { t.Errorf("failed to retrieve configurations with error %v", err) } - if configuration == nil { - t.Error("failed to retrieve configurations") - } } func TestConfigClient_GetConfigurationWhen404(t *testing.T) { configClient := createMockConfigClient(404, "", nil) - configuration, err := configClient.GetConfiguration("appName", []string{"profile"}) + _, err := configClient.GetConfiguration("appName", []string{"profile"}) if err == nil { t.Error("expected an error to occur") } - if configuration != nil { - t.Error("retrieved configuration when not found") - } } func TestConfigClient_GetConfigurationWhenError(t *testing.T) { configClient := createMockConfigClient(500, "", errors.New("failed")) - configuration, err := configClient.GetConfiguration("appName", []string{"profile"}) + _, err := configClient.GetConfiguration("appName", []string{"profile"}) if err == nil { t.Error("expected an error to occur") } - if configuration != nil { - t.Error("retrieved configuration when not found") - } } func TestConfigClient_GetConfigurationWhenNoErrorBut500(t *testing.T) { configClient := createMockConfigClient(500, "", nil) - configuration, err := configClient.GetConfiguration("appName", []string{"profile"}) + _, err := configClient.GetConfiguration("appName", []string{"profile"}) if err == nil { t.Error("expected an error to occur") } - if configuration != nil { - t.Error("retrieved configuration when not found") - } } func TestConfigClient_GetConfigurationInvalidResponseBody(t *testing.T) { configClient := createMockConfigClient(200, "", nil) - configuration, err := configClient.GetConfiguration("appName", []string{"profile"}) + _, err := configClient.GetConfiguration("appName", []string{"profile"}) if err == nil { t.Error("expected an error to occur") } - if configuration != nil { - t.Error("retrieved configuration when not found") - } } diff --git a/go.mod b/go.mod index 35c6b61..e9205f3 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,11 @@ module github.com/Piszmog/cloudconfigclient -go 1.12 +go 1.13 require ( - github.com/Piszmog/cfservices v1.3.5 - github.com/Piszmog/httpclient v1.0.3 + github.com/Piszmog/cfservices v1.3.6 + github.com/Piszmog/httpclient v1.0.4 github.com/pkg/errors v0.8.1 - golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 - golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc + golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 + golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 ) diff --git a/go.sum b/go.sum index ea4cdc4..773e99e 100644 --- a/go.sum +++ b/go.sum @@ -1,18 +1,28 @@ -github.com/Piszmog/cfservices v1.3.5 h1:2IYneTEX90IhsSEA2xxSGf8AUgxcJrEyeJCoOI1nrKs= -github.com/Piszmog/cfservices v1.3.5/go.mod h1:44R6Fu07XQvBkK470cCNZRo3QKjoYkvYuJb4mvV69VY= -github.com/Piszmog/httpclient v1.0.3 h1:DBaEIXXqMoGkX7VYpUVxbmDJd2bmfaFJngm9ZEOlpTs= -github.com/Piszmog/httpclient v1.0.3/go.mod h1:Kl3+2ZVsjPLTownDvoPUW5ROhnF8i5PROzySDQ19/DQ= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/Piszmog/cfservices v1.3.6 h1:hFt4MQwRVXzMXKcuMiGbUb56j+ns4wwsCVckSy1POAU= +github.com/Piszmog/cfservices v1.3.6/go.mod h1:kqqfwtk5qUXqg/axf+8eECqFE3n06S/+9qWNPasF0v4= +github.com/Piszmog/httpclient v1.0.4 h1:FoI7KdLP1wJc4SwvwHk4xb7bt2d75gmeqablhMeSIVs= +github.com/Piszmog/httpclient v1.0.4/go.mod h1:w1G59xd8Dks8cucZVXuMv0MCl3n7Z/9Lq1Xm5YBFlaE= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 h1:mEsFm194MmS9vCwxFy+zwu0EU7ZkxxMD1iH++vmGdUY= -golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc h1:3ElrZeO6IBP+M8kgu5YFwRo92Gqr+zBg3aooYQ6ziqU= -golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6 h1:bjcUS9ztw9kFmmIxJInhon/0Is3p+EHBKNgquIzo1OI= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA= +golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/local.go b/local.go index 874b1b7..936382a 100644 --- a/local.go +++ b/local.go @@ -1,9 +1,9 @@ package cloudconfigclient import ( + "fmt" "github.com/Piszmog/cfservices" "github.com/Piszmog/httpclient" - "github.com/pkg/errors" "os" "strings" ) @@ -20,9 +20,9 @@ const ( func CreateLocalClientFromEnv() (*ConfigClient, error) { serviceCredentials, err := GetLocalCredentials() if err != nil { - return nil, errors.Wrap(err, "failed to create a local client") + return nil, fmt.Errorf("failed to create a local client: %w", err) } - baseUrls := make([]string, len(serviceCredentials.Credentials)) + baseUrls := make([]string, len(serviceCredentials.Credentials), len(serviceCredentials.Credentials)) for index, cred := range serviceCredentials.Credentials { baseUrls[index] = cred.Uri } @@ -33,7 +33,7 @@ func CreateLocalClientFromEnv() (*ConfigClient, error) { // // The ConfigClient's underlying http.Client is configured with timeouts and connection pools. func CreateLocalClient(baseUrls []string) (*ConfigClient, error) { - configClients := make([]CloudClient, len(baseUrls)) + configClients := make([]CloudClient, len(baseUrls), len(baseUrls)) for index, baseUrl := range baseUrls { configUri := baseUrl client := httpclient.CreateDefaultHttpClient() @@ -48,10 +48,10 @@ func CreateLocalClient(baseUrls []string) (*ConfigClient, error) { func GetLocalCredentials() (*cfservices.ServiceCredentials, error) { localUrls := os.Getenv(EnvironmentLocalConfigServerUrls) if len(localUrls) == 0 { - return nil, errors.Errorf("No local Config Server URLs provided in environment variable %s", EnvironmentLocalConfigServerUrls) + return nil, fmt.Errorf("no local Config Server URLs provided in environment variable %s", EnvironmentLocalConfigServerUrls) } urls := strings.Split(localUrls, ",") - creds := make([]cfservices.Credentials, len(urls)) + creds := make([]cfservices.Credentials, len(urls), len(urls)) for index, url := range urls { creds[index] = cfservices.Credentials{ Uri: url, diff --git a/net/oauth2.go b/net/oauth2.go index d858028..12904ae 100644 --- a/net/oauth2.go +++ b/net/oauth2.go @@ -1,8 +1,9 @@ package net import ( + "errors" + "fmt" "github.com/Piszmog/cfservices" - "github.com/pkg/errors" "golang.org/x/net/context" "golang.org/x/oauth2/clientcredentials" "net/http" @@ -12,7 +13,7 @@ import ( func CreateOAuth2Client(cred *cfservices.Credentials) (*http.Client, error) { config, err := CreateOAuth2Config(cred) if err != nil { - return nil, errors.Wrap(err, "failed to create oauth2 config") + return nil, fmt.Errorf("failed to create oauth2 config: %w", err) } return config.Client(context.Background()), nil } diff --git a/oauth2.go b/oauth2.go index 68f4bb1..6a36d4d 100644 --- a/oauth2.go +++ b/oauth2.go @@ -1,9 +1,9 @@ package cloudconfigclient import ( + "fmt" "github.com/Piszmog/cfservices" "github.com/Piszmog/cloudconfigclient/net" - "github.com/pkg/errors" ) const ( @@ -25,19 +25,19 @@ func CreateCloudClient() (*ConfigClient, error) { func CreateCloudClientForService(name string) (*ConfigClient, error) { serviceCredentials, err := GetCloudCredentials(name) if err != nil { - return nil, errors.Wrap(err, "failed to create cloud client") + return nil, fmt.Errorf("failed to create cloud client: %w", err) } return CreateOAuth2Client(serviceCredentials.Credentials) } // CreateOAuth2Client creates a ConfigClient to access Config Servers from an array of credentials. func CreateOAuth2Client(credentials []cfservices.Credentials) (*ConfigClient, error) { - configClients := make([]CloudClient, len(credentials)) + configClients := make([]CloudClient, len(credentials), len(credentials)) for index, cred := range credentials { configUri := cred.Uri client, err := net.CreateOAuth2Client(&cred) if err != nil { - return nil, errors.Wrapf(err, "failed to create oauth2 client for %s", configUri) + return nil, fmt.Errorf("failed to create oauth2 client for %s: %w", configUri, err) } configClients[index] = Client{configUri: configUri, httpClient: client} } @@ -48,7 +48,7 @@ func CreateOAuth2Client(credentials []cfservices.Credentials) (*ConfigClient, er func GetCloudCredentials(name string) (*cfservices.ServiceCredentials, error) { serviceCreds, err := cfservices.GetServiceCredentialsFromEnvironment(name) if err != nil { - return nil, errors.Wrapf(err, "failed to get credentials for the Config Server service %s", name) + return nil, fmt.Errorf("failed to get credentials for the Config Server service %s: %w", name, err) } return serviceCreds, nil } diff --git a/resource.go b/resource.go index 0456091..361a117 100644 --- a/resource.go +++ b/resource.go @@ -1,8 +1,8 @@ package cloudconfigclient import ( - "encoding/json" - "github.com/pkg/errors" + "errors" + "fmt" ) const ( @@ -19,29 +19,20 @@ type Resource interface { // GetFile retrieves the specified file from the provided directory from the Config Server's default branch. // // The file will be deserialize into the specified interface type. -func (configClient ConfigClient) GetFile(directory string, file string, interfaceType interface{}) error { +func (c ConfigClient) GetFile(directory string, file string, interfaceType interface{}) error { fileFound := false - for _, client := range configClient.Clients { - resp, err := client.Get(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true") - if resp != nil && resp.StatusCode == 404 { - continue - } - if err != nil { - return errors.Wrap(err, "failed to retrieve file") - } - if resp.StatusCode != 200 { - return errors.Errorf("server responded with status code %d", resp.StatusCode) - } - decoder := json.NewDecoder(resp.Body) - err = decoder.Decode(interfaceType) - resp.Body.Close() - if err != nil { - return errors.Wrapf(err, "failed to decode response") + for _, client := range c.Clients { + if err := getResource(client, interfaceType, defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true"); err != nil { + var notFoundError *NotFoundError + if errors.As(err, ¬FoundError) { + continue + } + return err } fileFound = true } if !fileFound { - return errors.Errorf("failed to find file %s in the Config Server", file) + return fmt.Errorf("failed to find file %s in the Config Server", file) } return nil } @@ -49,29 +40,20 @@ func (configClient ConfigClient) GetFile(directory string, file string, interfac // GetFileFromBranch retrieves the specified file from the provided branch in the provided directory. // // The file will be deserialize into the specified interface type. -func (configClient *ConfigClient) GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error { +func (c *ConfigClient) GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error { fileFound := false - for _, client := range configClient.Clients { - resp, err := client.Get(defaultApplicationName, defaultApplicationProfile, branch, directory, file) - if resp != nil && resp.StatusCode == 404 { - continue - } - if err != nil { - return errors.Wrapf(err, "failed to retrieve file") - } - if resp.StatusCode != 200 { - return errors.Errorf("server responded with status code %d", resp.StatusCode) - } - decoder := json.NewDecoder(resp.Body) - err = decoder.Decode(interfaceType) - resp.Body.Close() - if err != nil { - return errors.Wrapf(err, "failed to decode response") + for _, client := range c.Clients { + if err := getResource(client, interfaceType, defaultApplicationName, defaultApplicationProfile, branch, directory, file); err != nil { + var notFoundError *NotFoundError + if errors.As(err, ¬FoundError) { + continue + } + return err } fileFound = true } if !fileFound { - return errors.Errorf("failed to find file %s in the Config Server", file) + return fmt.Errorf("failed to find file %s in the Config Server", file) } return nil }