From 405dad7f3a04de9b14371cd381004bff3b6ffdf3 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sun, 5 Aug 2018 17:27:13 -0600 Subject: [PATCH 01/22] add travis --- .travis.yml | 30 ++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 31 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9943a2e --- /dev/null +++ b/.travis.yml @@ -0,0 +1,30 @@ +language: go + +go: +- "1.10" + +notifications: + email: + on_success: never + on_failure: always + +matrix: + # It's ok if our code fails on unstable development versions of Go. + allow_failures: + - 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 + +before_install: +- go get -u golang.org/x/vgo + +install: +- vgo mod -vendor + +script: +- go test -v \ No newline at end of file diff --git a/README.md b/README.md index 058a88f..8c2cfbf 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Go Config Server Client +[![Build Status](https://travis-ci.org/Piszmog/cloudconfigclient.svg?branch=develop)](https://travis-ci.org/Piszmog/cloudconfigclient) [![GitHub release](https://img.shields.io/github/release/Piszmog/cloudconfigclient.svg)](https://github.com/Piszmog/cloudconfigclient/releases/latest) Go library for Spring Config Server. Inspired by the Java library [Cloud Config Client](https://github.com/Piszmog/cloud-config-client) From 4534e3e17d2453a8c4cd94fd15556e1206abfd5d Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sun, 5 Aug 2018 20:38:26 -0600 Subject: [PATCH 02/22] add license --- .travis.yml | 2 +- CODE_OF_CONDUCT.md | 46 +++++++++ ISSUE_TEMPLATE.md | 10 ++ LICENSE | 201 +++++++++++++++++++++++++++++++++++++++ PULL_REQUEST_TEMPLATE.md | 10 ++ README.md | 3 +- configclient.go | 13 ++- 7 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 ISSUE_TEMPLATE.md create mode 100644 LICENSE create mode 100644 PULL_REQUEST_TEMPLATE.md diff --git a/.travis.yml b/.travis.yml index 9943a2e..d44e551 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,4 +27,4 @@ install: - vgo mod -vendor script: -- go test -v \ No newline at end of file +- go test -v ./... \ No newline at end of file diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..ea1931e --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at piszmogcode@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..937c25f --- /dev/null +++ b/ISSUE_TEMPLATE.md @@ -0,0 +1,10 @@ +### Issue Description + + +### Steps to Reproduce + + +### Expected Behavior + + +### Acceptance Criteria diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d1a79fe --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Piszmog + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..a21cec2 --- /dev/null +++ b/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,10 @@ +### Changes Description + + +### Associated Issues + + +### Affected Files + + +### Unit Tests Changed or Added diff --git a/README.md b/README.md index 8c2cfbf..7593553 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Go Config Server Client [![Build Status](https://travis-ci.org/Piszmog/cloudconfigclient.svg?branch=develop)](https://travis-ci.org/Piszmog/cloudconfigclient) +[![Go Report Card](https://goreportcard.com/badge/github.com/Piszmog/cloudconfigclient)](https://goreportcard.com/report/github.com/Piszmog/cloudconfigclient) [![GitHub release](https://img.shields.io/github/release/Piszmog/cloudconfigclient.svg)](https://github.com/Piszmog/cloudconfigclient/releases/latest) Go library for Spring Config Server. Inspired by the Java library [Cloud Config Client](https://github.com/Piszmog/cloud-config-client) @@ -48,4 +49,4 @@ To use the library to retrieve configurations, use a client of `configuration.Co invoke the method `GetConfiguration(applicationName string, profiles []string)`. The return will be the struct representation of the configuration JSON - `model.Configuration`. -## Resources \ No newline at end of file +## Resources diff --git a/configclient.go b/configclient.go index 4c16f0c..41e5a96 100644 --- a/configclient.go +++ b/configclient.go @@ -11,6 +11,12 @@ import ( "strings" ) +const ( + DefaultConfigServerName = "p-config-server" + EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" +) + +// just for testing type File struct { Example Example `json:"example"` } @@ -19,6 +25,7 @@ type Example struct { Field3 string `json:"field3"` } +// just for testing -- remove after library built out func main() { serviceCreds, err := GetLocalCredentials() if err != nil { @@ -45,9 +52,9 @@ func main() { } func GetLocalCredentials() (*credentials.ServiceCredentials, error) { - localUrls := os.Getenv("CONFIG_SERVER_URLS") + localUrls := os.Getenv(EnvironmentLocalConfigServerUrls) if len(localUrls) == 0 { - return nil, errors.Errorf("No local Config Server URLs provided in environment variable %s", "CONFIG_SERVER_URLS") + return nil, errors.Errorf("No local Config Server URLs provided in environment variable %s", EnvironmentLocalConfigServerUrls) } urls := strings.Split(localUrls, ",") var creds []credentials.Credentials @@ -60,7 +67,7 @@ func GetLocalCredentials() (*credentials.ServiceCredentials, error) { } func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { - return GetCloudCredentials("p-config-server") + return GetCloudCredentials(DefaultConfigServerName) } func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { From 455705d5b5331b31d5e49625310727990eeb49b3 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Tue, 7 Aug 2018 19:04:01 -0600 Subject: [PATCH 03/22] add coveralls --- .travis.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index d44e551..a04ccb7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,12 @@ cache: before_install: - go get -u golang.org/x/vgo +- go get golang.org/x/tools/cmd/cover +- go get github.com/mattn/goveralls install: -- vgo mod -vendor +- vgo mod vendor script: -- go test -v ./... \ 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 From 606c661c7ca4ef9650f78ea41810a86cb17b248a Mon Sep 17 00:00:00 2001 From: Piszmog Date: Tue, 7 Aug 2018 19:07:15 -0600 Subject: [PATCH 04/22] add code coverage --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 7593553..028bd6a 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Go Config Server Client [![Build Status](https://travis-ci.org/Piszmog/cloudconfigclient.svg?branch=develop)](https://travis-ci.org/Piszmog/cloudconfigclient) +[![Coverage Status](https://coveralls.io/repos/github/Piszmog/cloudconfigclient/badge.svg?branch=develop)](https://coveralls.io/github/Piszmog/cloudconfigclient?branch=develop) [![Go Report Card](https://goreportcard.com/badge/github.com/Piszmog/cloudconfigclient)](https://goreportcard.com/report/github.com/Piszmog/cloudconfigclient) [![GitHub release](https://img.shields.io/github/release/Piszmog/cloudconfigclient.svg)](https://github.com/Piszmog/cloudconfigclient/releases/latest) From 7f20da4f348ead483b06332047e915df046f0d26 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Tue, 7 Aug 2018 19:08:28 -0600 Subject: [PATCH 05/22] add license badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 028bd6a..f63a6aa 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ [![Coverage Status](https://coveralls.io/repos/github/Piszmog/cloudconfigclient/badge.svg?branch=develop)](https://coveralls.io/github/Piszmog/cloudconfigclient?branch=develop) [![Go Report Card](https://goreportcard.com/badge/github.com/Piszmog/cloudconfigclient)](https://goreportcard.com/report/github.com/Piszmog/cloudconfigclient) [![GitHub release](https://img.shields.io/github/release/Piszmog/cloudconfigclient.svg)](https://github.com/Piszmog/cloudconfigclient/releases/latest) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) Go library for Spring Config Server. Inspired by the Java library [Cloud Config Client](https://github.com/Piszmog/cloud-config-client) From 7afc57cb1058355720cd0d836987bb84dda6805d Mon Sep 17 00:00:00 2001 From: Piszmog Date: Tue, 7 Aug 2018 19:16:31 -0600 Subject: [PATCH 06/22] Update LICENSE --- LICENSE | 222 ++++++-------------------------------------------------- 1 file changed, 21 insertions(+), 201 deletions(-) diff --git a/LICENSE b/LICENSE index d1a79fe..f8bc7de 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,21 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2017 Piszmog - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +MIT License + +Copyright (c) 2018 Piszmog + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. From 3e9583cee8164fe07a49b52b445efa2e5f69a546 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Tue, 7 Aug 2018 19:17:26 -0600 Subject: [PATCH 07/22] add license badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f63a6aa..ff7d837 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Coverage Status](https://coveralls.io/repos/github/Piszmog/cloudconfigclient/badge.svg?branch=develop)](https://coveralls.io/github/Piszmog/cloudconfigclient?branch=develop) [![Go Report Card](https://goreportcard.com/badge/github.com/Piszmog/cloudconfigclient)](https://goreportcard.com/report/github.com/Piszmog/cloudconfigclient) [![GitHub release](https://img.shields.io/github/release/Piszmog/cloudconfigclient.svg)](https://github.com/Piszmog/cloudconfigclient/releases/latest) -[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) Go library for Spring Config Server. Inspired by the Java library [Cloud Config Client](https://github.com/Piszmog/cloud-config-client) From 4c452cfbc8c792575f0cf61d95a1b3b58d1e2a21 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Tue, 7 Aug 2018 20:42:48 -0600 Subject: [PATCH 08/22] add tests and start playing with oauth2 --- configclient.go | 77 ++++++++++++++++++----------- configuration/configuration_test.go | 12 +++++ go.mod | 7 ++- net/net_test.go | 7 +++ resource/resource_test.go | 10 ++++ 5 files changed, 84 insertions(+), 29 deletions(-) create mode 100644 configuration/configuration_test.go create mode 100644 resource/resource_test.go diff --git a/configclient.go b/configclient.go index 41e5a96..e8ac815 100644 --- a/configclient.go +++ b/configclient.go @@ -1,12 +1,14 @@ package main import ( + "context" "fmt" "github.com/Piszmog/cfservices" "github.com/Piszmog/cfservices/credentials" "github.com/Piszmog/cloudconfigclient/configuration" "github.com/Piszmog/cloudconfigclient/resource" "github.com/pkg/errors" + "golang.org/x/oauth2" "os" "strings" ) @@ -16,6 +18,34 @@ const ( EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" ) +func GetLocalCredentials() (*credentials.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) + } + urls := strings.Split(localUrls, ",") + var creds []credentials.Credentials + for _, url := range urls { + creds = append(creds, credentials.Credentials{ + Uri: url, + }) + } + return &credentials.ServiceCredentials{Credentials: creds}, nil +} + +func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { + return GetCloudCredentials(DefaultConfigServerName) +} + +func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { + vcapServices := cfservices.LoadFromEnvironment() + serviceCreds, err := cfservices.GetServiceCredentials(name, vcapServices) + if err != nil { + return nil, errors.Wrap(err, "failed to get credentials for the Config Server") + } + return serviceCreds, nil +} + // just for testing type File struct { Example Example `json:"example"` @@ -25,6 +55,25 @@ type Example struct { Field3 string `json:"field3"` } +func oathu2Example() { + config := &oauth2.Config{ + ClientID: "", + ClientSecret: "", + Endpoint: oauth2.Endpoint{ + AuthURL: "", + TokenURL: "", + }, + } + token, _ := config.Exchange(context.Background(), "") + source := config.TokenSource(context.Background(), token) + newToken, _ := source.Token() + if newToken.AccessToken != token.AccessToken { + println("save new token") + } + client := oauth2.NewClient(context.Background(), source) + client.Get("") +} + // just for testing -- remove after library built out func main() { serviceCreds, err := GetLocalCredentials() @@ -50,31 +99,3 @@ func main() { } fmt.Printf("%+v", configurations) } - -func GetLocalCredentials() (*credentials.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) - } - urls := strings.Split(localUrls, ",") - var creds []credentials.Credentials - for _, url := range urls { - creds = append(creds, credentials.Credentials{ - Uri: url, - }) - } - return &credentials.ServiceCredentials{Credentials: creds}, nil -} - -func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { - return GetCloudCredentials(DefaultConfigServerName) -} - -func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { - vcapServices := cfservices.LoadFromEnvironment() - serviceCreds, err := cfservices.GetServiceCredentials(name, vcapServices) - if err != nil { - return nil, errors.Wrap(err, "failed to get credentials for the Config Server") - } - return serviceCreds, nil -} diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go new file mode 100644 index 0000000..44e4bdf --- /dev/null +++ b/configuration/configuration_test.go @@ -0,0 +1,12 @@ +package configuration + +import ( + "testing" +) + +func TestCreateClient(t *testing.T) { + client := CreateClient("http://localhost:8080") + if client == nil && client.BaseUrls[0] == "http://localhost:8080" { + t.Errorf("failed to create configuration client") + } +} diff --git a/go.mod b/go.mod index d2b70c3..edce44f 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,8 @@ module github.com/Piszmog/cloudconfigclient -require github.com/Piszmog/cfservices v1.1.0 +require ( + github.com/Piszmog/cfservices v1.1.0 + github.com/pkg/errors v0.8.0 + golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 // indirect + golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc // indirect +) diff --git a/net/net_test.go b/net/net_test.go index 1364889..79d4f07 100644 --- a/net/net_test.go +++ b/net/net_test.go @@ -25,3 +25,10 @@ func TestJoinProfiles(t *testing.T) { t.Errorf("profiles were not append") } } + +func TestCreateDefaultHttpClient(t *testing.T) { + client := CreateDefaultHttpClient() + if client == nil { + t.Errorf("failed to create configuration client") + } +} diff --git a/resource/resource_test.go b/resource/resource_test.go new file mode 100644 index 0000000..8cc77b3 --- /dev/null +++ b/resource/resource_test.go @@ -0,0 +1,10 @@ +package resource + +import "testing" + +func TestCreateClient(t *testing.T) { + client := CreateClient("http://localhost:8080") + if client == nil && client.BaseUrls[0] == "http://localhost:8080" { + t.Errorf("failed to create configuration client") + } +} From 038870c3a8c52a6978db65c0070e9687755fa225 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Wed, 8 Aug 2018 22:03:34 -0600 Subject: [PATCH 09/22] add oauth client and update config and resource clients to us the configClient --- client/client.go | 29 +++++++++++++++++++++ client/local.go | 39 +++++++++++++++++++++++++++++ client/oauth2.go | 36 ++++++++++++++++++++++++++ configuration/configuration.go | 34 +++++++++++-------------- configuration/configuration_test.go | 12 --------- {model => configuration}/model.go | 4 +-- net/oauth2.go | 21 ++++++++++++++++ resource/resource.go | 27 ++++++++------------ resource/resource_test.go | 10 -------- 9 files changed, 152 insertions(+), 60 deletions(-) create mode 100644 client/client.go create mode 100644 client/local.go create mode 100644 client/oauth2.go delete mode 100644 configuration/configuration_test.go rename {model => configuration}/model.go (90%) create mode 100644 net/oauth2.go delete mode 100644 resource/resource_test.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..d34beec --- /dev/null +++ b/client/client.go @@ -0,0 +1,29 @@ +package client + +import ( + "github.com/Piszmog/cloudconfigclient/net" + "net/http" +) + +type CloudClient interface { + GetFullUrl(uriVariables ...string) string + Get(uriVariables ...string) (resp *http.Response, err error) +} + +type ConfigClient struct { + Clients []Client +} + +type Client struct { + configUri string + httpClient *http.Client +} + +func (client *Client) GetFullUrl(uriVariables ...string) string { + return net.CreateUrl(client.configUri, uriVariables...) +} + +func (client *Client) Get(uriVariables ...string) (resp *http.Response, err error) { + fullUrl := net.CreateUrl(client.configUri, uriVariables...) + return client.httpClient.Get(fullUrl) +} diff --git a/client/local.go b/client/local.go new file mode 100644 index 0000000..bb2388f --- /dev/null +++ b/client/local.go @@ -0,0 +1,39 @@ +package client + +import ( + "github.com/Piszmog/cfservices/credentials" + "github.com/Piszmog/cloudconfigclient/net" + "github.com/pkg/errors" + "os" + "strings" +) + +const ( + EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" +) + +func CreateLocalClient() ConfigClient { + serviceCredentials, _ := GetLocalCredentials() + configClients := make([]Client, len(serviceCredentials.Credentials)) + for index, cred := range serviceCredentials.Credentials { + configUri := cred.Uri + client := net.CreateDefaultHttpClient() + configClients[index] = Client{configUri: configUri, httpClient: client} + } + return ConfigClient{Clients: configClients} +} + +func GetLocalCredentials() (*credentials.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) + } + urls := strings.Split(localUrls, ",") + var creds []credentials.Credentials + for _, url := range urls { + creds = append(creds, credentials.Credentials{ + Uri: url, + }) + } + return &credentials.ServiceCredentials{Credentials: creds}, nil +} diff --git a/client/oauth2.go b/client/oauth2.go new file mode 100644 index 0000000..43e23ee --- /dev/null +++ b/client/oauth2.go @@ -0,0 +1,36 @@ +package client + +import ( + "github.com/Piszmog/cfservices" + "github.com/Piszmog/cfservices/credentials" + "github.com/Piszmog/cloudconfigclient/net" + "github.com/pkg/errors" +) + +const ( + DefaultConfigServerName = "p-config-server" +) + +func CreateCloudClient() ConfigClient { + serviceCredentials, _ := GetCloudCredentialsByDefaultName() + configClients := make([]Client, len(serviceCredentials.Credentials)) + for index, cred := range serviceCredentials.Credentials { + configUri := cred.Uri + client := net.CreateOAuth2Client(cred) + configClients[index] = Client{configUri: configUri, httpClient: client} + } + return ConfigClient{Clients: configClients} +} + +func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { + return GetCloudCredentials(DefaultConfigServerName) +} + +func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { + vcapServices := cfservices.LoadFromEnvironment() + serviceCreds, err := cfservices.GetServiceCredentials(name, vcapServices) + if err != nil { + return nil, errors.Wrap(err, "failed to get credentials for the Config Server") + } + return serviceCreds, nil +} diff --git a/configuration/configuration.go b/configuration/configuration.go index 0228796..ca76ae3 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -2,47 +2,41 @@ package configuration import ( "encoding/json" - "github.com/Piszmog/cloudconfigclient/model" + "github.com/Piszmog/cloudconfigclient/client" "github.com/Piszmog/cloudconfigclient/net" "github.com/pkg/errors" - "net/http" ) type Configuration interface { - GetConfiguration(applicationName string, profiles []string) (*model.Configuration, error) + GetConfiguration(applicationName string, profiles []string) (*Source, error) } type Client struct { - HttpClient *http.Client - BaseUrls []string + configClient client.ConfigClient } -func CreateClient(urls ...string) *Client { - return &Client{ - HttpClient: net.CreateDefaultHttpClient(), - BaseUrls: urls, - } -} - -func (client *Client) GetConfiguration(applicationName string, profiles []string) (*model.Configuration, error) { - for _, baseUrl := range client.BaseUrls { - fullUrl := net.CreateUrl(baseUrl, applicationName, net.JoinProfiles(profiles)) - resp, err := client.HttpClient.Get(fullUrl) +func (client *Client) GetConfiguration(applicationName string, profiles []string) (*Source, error) { + for _, configClient := range client.configClient.Clients { + resp, err := configClient.Get(applicationName, net.JoinProfiles(profiles)) if resp != nil && resp.StatusCode == 404 { continue } if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve application configurations from %s", fullUrl) + return nil, errors.Wrapf(err, "failed to retrieve application configurations from %s", + configClient.GetFullUrl(applicationName, net.JoinProfiles(profiles))) } if resp.StatusCode != 200 { - return nil, errors.Errorf("server responded with status code %d from url %s", resp.StatusCode, fullUrl) + return nil, errors.Errorf("server responded with status code %d from url %s", + resp.StatusCode, + configClient.GetFullUrl(applicationName, net.JoinProfiles(profiles))) } - configuration := &model.Configuration{} + configuration := &Source{} decoder := json.NewDecoder(resp.Body) err = decoder.Decode(configuration) resp.Body.Close() if err != nil { - return nil, errors.Wrapf(err, "failed to decode response from url %s", fullUrl) + return nil, errors.Wrapf(err, "failed to decode response from url %s", + configClient.GetFullUrl(applicationName, net.JoinProfiles(profiles))) } return configuration, nil } diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go deleted file mode 100644 index 44e4bdf..0000000 --- a/configuration/configuration_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package configuration - -import ( - "testing" -) - -func TestCreateClient(t *testing.T) { - client := CreateClient("http://localhost:8080") - if client == nil && client.BaseUrls[0] == "http://localhost:8080" { - t.Errorf("failed to create configuration client") - } -} diff --git a/model/model.go b/configuration/model.go similarity index 90% rename from model/model.go rename to configuration/model.go index 58f7411..9560412 100644 --- a/model/model.go +++ b/configuration/model.go @@ -1,6 +1,6 @@ -package model +package configuration -type Configuration struct { +type Source struct { Name string `json:"name"` Profiles []string `json:"profiles"` Label string `json:"label"` diff --git a/net/oauth2.go b/net/oauth2.go new file mode 100644 index 0000000..c201286 --- /dev/null +++ b/net/oauth2.go @@ -0,0 +1,21 @@ +package net + +import ( + "github.com/Piszmog/cfservices/credentials" + "golang.org/x/net/context" + "golang.org/x/oauth2/clientcredentials" + "net/http" +) + +func CreateOAuth2Client(cred credentials.Credentials) *http.Client { + config := CreateOauth2Config(&cred) + return config.Client(context.Background()) +} + +func CreateOauth2Config(cred *credentials.Credentials) *clientcredentials.Config { + return &clientcredentials.Config{ + ClientID: cred.ClientId, + ClientSecret: cred.ClientSecret, + TokenURL: cred.AccessTokenUri, + } +} diff --git a/resource/resource.go b/resource/resource.go index a29f43f..1b795fc 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -2,9 +2,9 @@ package resource import ( "encoding/json" + "github.com/Piszmog/cloudconfigclient/client" "github.com/Piszmog/cloudconfigclient/net" "github.com/pkg/errors" - "net/http" ) const ( @@ -18,36 +18,31 @@ type Resource interface { } type Client struct { - HttpClient *http.Client - BaseUrls []string -} - -func CreateClient(urls ...string) *Client { - return &Client{ - HttpClient: net.CreateDefaultHttpClient(), - BaseUrls: urls, - } + configClient client.ConfigClient } func (client *Client) GetFile(directory string, file string, interfaceType interface{}) error { fileFound := false - for _, baseUrl := range client.BaseUrls { - fullUrl := net.CreateUrl(baseUrl, defaultApplicationName, defaultApplicationProfile, directory, file) + "?useDefaultLabel=true" - resp, err := client.HttpClient.Get(fullUrl) + for _, configClient := range client.configClient.Clients { + resp, err := configClient.Get(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true") if resp != nil && resp.StatusCode == 404 { continue } if err != nil { - return errors.Wrapf(err, "failed to retrieve file from %s", fullUrl) + return errors.Wrapf(err, "failed to retrieve file from %s", + configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true")) } if resp.StatusCode != 200 { - return errors.Errorf("server responded with status code %d from url %s", resp.StatusCode, fullUrl) + return errors.Errorf("server responded with status code %d from url %s", + resp.StatusCode, + configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true")) } decoder := json.NewDecoder(resp.Body) err = decoder.Decode(interfaceType) resp.Body.Close() if err != nil { - return errors.Wrapf(err, "failed to decode response from url %s", fullUrl) + return errors.Wrapf(err, "failed to decode response from url %s", + configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true")) } fileFound = true } diff --git a/resource/resource_test.go b/resource/resource_test.go deleted file mode 100644 index 8cc77b3..0000000 --- a/resource/resource_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package resource - -import "testing" - -func TestCreateClient(t *testing.T) { - client := CreateClient("http://localhost:8080") - if client == nil && client.BaseUrls[0] == "http://localhost:8080" { - t.Errorf("failed to create configuration client") - } -} From a839fe0e18f4b5af161cd631d9fe253d6fd22642 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Thu, 9 Aug 2018 20:54:29 -0600 Subject: [PATCH 10/22] add methods to create clients --- client/local.go | 6 +- client/oauth2.go | 15 ++++- configclient.go | 101 --------------------------------- configuration/configuration.go | 6 +- main.go | 34 +++++++++++ net/net.go | 2 +- resource/resource.go | 21 ++++--- 7 files changed, 69 insertions(+), 116 deletions(-) delete mode 100644 configclient.go create mode 100644 main.go diff --git a/client/local.go b/client/local.go index bb2388f..cd25daf 100644 --- a/client/local.go +++ b/client/local.go @@ -9,7 +9,7 @@ import ( ) const ( - EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" + environmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" ) func CreateLocalClient() ConfigClient { @@ -24,9 +24,9 @@ func CreateLocalClient() ConfigClient { } func GetLocalCredentials() (*credentials.ServiceCredentials, error) { - localUrls := os.Getenv(EnvironmentLocalConfigServerUrls) + 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, errors.Errorf("No local Config Server URLs provided in environment variable %s", environmentLocalConfigServerUrls) } urls := strings.Split(localUrls, ",") var creds []credentials.Credentials diff --git a/client/oauth2.go b/client/oauth2.go index 43e23ee..8c42d4c 100644 --- a/client/oauth2.go +++ b/client/oauth2.go @@ -8,7 +8,7 @@ import ( ) const ( - DefaultConfigServerName = "p-config-server" + defaultConfigServerName = "p-config-server" ) func CreateCloudClient() ConfigClient { @@ -22,8 +22,19 @@ func CreateCloudClient() ConfigClient { return ConfigClient{Clients: configClients} } +func CreateCloudClientForService(name string) ConfigClient { + serviceCredentials, _ := GetCloudCredentials(name) + configClients := make([]Client, len(serviceCredentials.Credentials)) + for index, cred := range serviceCredentials.Credentials { + configUri := cred.Uri + client := net.CreateOAuth2Client(cred) + configClients[index] = Client{configUri: configUri, httpClient: client} + } + return ConfigClient{Clients: configClients} +} + func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { - return GetCloudCredentials(DefaultConfigServerName) + return GetCloudCredentials(defaultConfigServerName) } func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { diff --git a/configclient.go b/configclient.go deleted file mode 100644 index e8ac815..0000000 --- a/configclient.go +++ /dev/null @@ -1,101 +0,0 @@ -package main - -import ( - "context" - "fmt" - "github.com/Piszmog/cfservices" - "github.com/Piszmog/cfservices/credentials" - "github.com/Piszmog/cloudconfigclient/configuration" - "github.com/Piszmog/cloudconfigclient/resource" - "github.com/pkg/errors" - "golang.org/x/oauth2" - "os" - "strings" -) - -const ( - DefaultConfigServerName = "p-config-server" - EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" -) - -func GetLocalCredentials() (*credentials.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) - } - urls := strings.Split(localUrls, ",") - var creds []credentials.Credentials - for _, url := range urls { - creds = append(creds, credentials.Credentials{ - Uri: url, - }) - } - return &credentials.ServiceCredentials{Credentials: creds}, nil -} - -func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { - return GetCloudCredentials(DefaultConfigServerName) -} - -func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { - vcapServices := cfservices.LoadFromEnvironment() - serviceCreds, err := cfservices.GetServiceCredentials(name, vcapServices) - if err != nil { - return nil, errors.Wrap(err, "failed to get credentials for the Config Server") - } - return serviceCreds, nil -} - -// just for testing -type File struct { - Example Example `json:"example"` -} - -type Example struct { - Field3 string `json:"field3"` -} - -func oathu2Example() { - config := &oauth2.Config{ - ClientID: "", - ClientSecret: "", - Endpoint: oauth2.Endpoint{ - AuthURL: "", - TokenURL: "", - }, - } - token, _ := config.Exchange(context.Background(), "") - source := config.TokenSource(context.Background(), token) - newToken, _ := source.Token() - if newToken.AccessToken != token.AccessToken { - println("save new token") - } - client := oauth2.NewClient(context.Background(), source) - client.Get("") -} - -// just for testing -- remove after library built out -func main() { - serviceCreds, err := GetLocalCredentials() - if err != nil { - panic(err) - } - var urls []string - for _, cred := range serviceCreds.Credentials { - urls = append(urls, cred.Uri) - } - file := &File{} - resourceClient := resource.CreateClient(urls...) - err = resourceClient.GetFileFromBranch("develop", "temp", "temp1.json", file) - if err != nil { - panic(err) - } - fmt.Printf("%+v\n", file) - - configClient := configuration.CreateClient(urls...) - configurations, err := configClient.GetConfiguration("exampleapp", []string{"dev"}) - if err != nil { - panic(err) - } - fmt.Printf("%+v", configurations) -} diff --git a/configuration/configuration.go b/configuration/configuration.go index ca76ae3..d35be5a 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -12,7 +12,11 @@ type Configuration interface { } type Client struct { - configClient client.ConfigClient + configClient *client.ConfigClient +} + +func CreateconfigurationClient(configClient *client.ConfigClient) *Client { + return &Client{configClient: configClient} } func (client *Client) GetConfiguration(applicationName string, profiles []string) (*Source, error) { diff --git a/main.go b/main.go new file mode 100644 index 0000000..c28ec2b --- /dev/null +++ b/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "github.com/Piszmog/cloudconfigclient/client" + "github.com/Piszmog/cloudconfigclient/configuration" + "github.com/Piszmog/cloudconfigclient/resource" +) + +type File struct { + Example Example `json:"example"` +} + +type Example struct { + Field3 string `json:"field3"` +} + +func main() { + localClient := client.CreateLocalClient() + resourceClient := resource.CreateResourceClient(&localClient) + var file File + err := resourceClient.GetFile("temp", "temp1.json", &file) + if err != nil { + panic(err) + } + fmt.Printf("%+v", file) + + configurationClient := configuration.CreateconfigurationClient(&localClient) + config, err := configurationClient.GetConfiguration("application", []string{"dev"}) + if err != nil { + panic(err) + } + fmt.Printf("%+v", config) +} diff --git a/net/net.go b/net/net.go index e6d5628..b2d962b 100644 --- a/net/net.go +++ b/net/net.go @@ -20,7 +20,7 @@ func JoinProfiles(profiles []string) string { } func CreateDefaultHttpClient() *http.Client { - return CreateHttpClient(2*time.Second, 30*time.Second, 2*time.Second, 90*time.Second) + return CreateHttpClient(5*time.Second, 30*time.Second, 5*time.Second, 90*time.Second) } func CreateHttpClient(timeout time.Duration, keepAlive time.Duration, tlsHandshakeTimeout time.Duration, idleConnection time.Duration) *http.Client { diff --git a/resource/resource.go b/resource/resource.go index 1b795fc..713f38a 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -3,7 +3,6 @@ package resource import ( "encoding/json" "github.com/Piszmog/cloudconfigclient/client" - "github.com/Piszmog/cloudconfigclient/net" "github.com/pkg/errors" ) @@ -18,7 +17,11 @@ type Resource interface { } type Client struct { - configClient client.ConfigClient + configClient *client.ConfigClient +} + +func CreateResourceClient(configClient *client.ConfigClient) *Client { + return &Client{configClient: configClient} } func (client *Client) GetFile(directory string, file string, interfaceType interface{}) error { @@ -54,23 +57,25 @@ func (client *Client) GetFile(directory string, file string, interfaceType inter func (client *Client) GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error { fileFound := false - for _, baseUrl := range client.BaseUrls { - fullUrl := net.CreateUrl(baseUrl, defaultApplicationName, defaultApplicationProfile, branch, directory, file) - resp, err := client.HttpClient.Get(fullUrl) + for _, configClient := range client.configClient.Clients { + resp, err := configClient.Get(defaultApplicationName, defaultApplicationProfile, branch, directory, file) if resp != nil && resp.StatusCode == 404 { continue } if err != nil { - return errors.Wrapf(err, "failed to retrieve file from %s", fullUrl) + return errors.Wrapf(err, "failed to retrieve file from %s", + configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, branch, directory, file)) } if resp.StatusCode != 200 { - return errors.Errorf("server responded with status code %d from url %s", resp.StatusCode, fullUrl) + return errors.Errorf("server responded with status code %d from url %s", resp.StatusCode, + configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, branch, directory, file)) } decoder := json.NewDecoder(resp.Body) err = decoder.Decode(interfaceType) resp.Body.Close() if err != nil { - return errors.Wrapf(err, "failed to decode response from url %s", fullUrl) + return errors.Wrapf(err, "failed to decode response from url %s", + configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, branch, directory, file)) } fileFound = true } From f2de4655d5b27b7360af68030102decbb36320a4 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Thu, 9 Aug 2018 20:55:00 -0600 Subject: [PATCH 11/22] refactor function name --- configuration/configuration.go | 2 +- main.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configuration/configuration.go b/configuration/configuration.go index d35be5a..6d06dd9 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -15,7 +15,7 @@ type Client struct { configClient *client.ConfigClient } -func CreateconfigurationClient(configClient *client.ConfigClient) *Client { +func CreateConfigurationClient(configClient *client.ConfigClient) *Client { return &Client{configClient: configClient} } diff --git a/main.go b/main.go index c28ec2b..d43da0e 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ func main() { } fmt.Printf("%+v", file) - configurationClient := configuration.CreateconfigurationClient(&localClient) + configurationClient := configuration.CreateConfigurationClient(&localClient) config, err := configurationClient.GetConfiguration("application", []string{"dev"}) if err != nil { panic(err) From aa5dd169c3e62eee57ad91bdcc0dc359b9e0fd52 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Thu, 9 Aug 2018 21:43:54 -0600 Subject: [PATCH 12/22] test using oauth client --- .gitignore | 1 - client/client.go | 17 +++++++---------- client/local.go | 19 +++++++++++-------- client/oauth2.go | 29 ++++++++++------------------- configuration/configuration.go | 10 +++------- go.mod | 8 +++++--- go.sum | 10 ++++++++++ main.go | 14 +++++++++----- net/oauth2.go | 6 +++--- resource/resource.go | 23 ++++++++--------------- 10 files changed, 66 insertions(+), 71 deletions(-) create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 996ec87..9bc1e13 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,3 @@ *.ipr /vendor -go.sum diff --git a/client/client.go b/client/client.go index d34beec..5ced8a7 100644 --- a/client/client.go +++ b/client/client.go @@ -2,28 +2,25 @@ package client import ( "github.com/Piszmog/cloudconfigclient/net" + "github.com/pkg/errors" "net/http" ) -type CloudClient interface { - GetFullUrl(uriVariables ...string) string - Get(uriVariables ...string) (resp *http.Response, err error) -} - type ConfigClient struct { Clients []Client } +type CloudClient interface { + Get(uriVariables ...string) (resp *http.Response, err error) +} + type Client struct { configUri string httpClient *http.Client } -func (client *Client) GetFullUrl(uriVariables ...string) string { - return net.CreateUrl(client.configUri, uriVariables...) -} - func (client *Client) Get(uriVariables ...string) (resp *http.Response, err error) { fullUrl := net.CreateUrl(client.configUri, uriVariables...) - return client.httpClient.Get(fullUrl) + response, err := client.httpClient.Get(fullUrl) + return response, errors.Wrapf(err, "failed to retrieve from %s", fullUrl) } diff --git a/client/local.go b/client/local.go index cd25daf..6bb52e2 100644 --- a/client/local.go +++ b/client/local.go @@ -1,7 +1,7 @@ package client import ( - "github.com/Piszmog/cfservices/credentials" + "github.com/Piszmog/cfservices" "github.com/Piszmog/cloudconfigclient/net" "github.com/pkg/errors" "os" @@ -12,28 +12,31 @@ const ( environmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" ) -func CreateLocalClient() ConfigClient { - serviceCredentials, _ := GetLocalCredentials() +func CreateLocalClient() (*ConfigClient, error) { + serviceCredentials, err := GetLocalCredentials() + if err != nil { + return nil, errors.Wrap(err, "failed to create a local client") + } configClients := make([]Client, len(serviceCredentials.Credentials)) for index, cred := range serviceCredentials.Credentials { configUri := cred.Uri client := net.CreateDefaultHttpClient() configClients[index] = Client{configUri: configUri, httpClient: client} } - return ConfigClient{Clients: configClients} + return &ConfigClient{Clients: configClients}, nil } -func GetLocalCredentials() (*credentials.ServiceCredentials, 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) } urls := strings.Split(localUrls, ",") - var creds []credentials.Credentials + var creds []cfservices.Credentials for _, url := range urls { - creds = append(creds, credentials.Credentials{ + creds = append(creds, cfservices.Credentials{ Uri: url, }) } - return &credentials.ServiceCredentials{Credentials: creds}, nil + return &cfservices.ServiceCredentials{Credentials: creds}, nil } diff --git a/client/oauth2.go b/client/oauth2.go index 8c42d4c..f606a9b 100644 --- a/client/oauth2.go +++ b/client/oauth2.go @@ -2,7 +2,6 @@ package client import ( "github.com/Piszmog/cfservices" - "github.com/Piszmog/cfservices/credentials" "github.com/Piszmog/cloudconfigclient/net" "github.com/pkg/errors" ) @@ -11,37 +10,29 @@ const ( defaultConfigServerName = "p-config-server" ) -func CreateCloudClient() ConfigClient { - serviceCredentials, _ := GetCloudCredentialsByDefaultName() - configClients := make([]Client, len(serviceCredentials.Credentials)) - for index, cred := range serviceCredentials.Credentials { - configUri := cred.Uri - client := net.CreateOAuth2Client(cred) - configClients[index] = Client{configUri: configUri, httpClient: client} - } - return ConfigClient{Clients: configClients} +func CreateCloudClient() (*ConfigClient, error) { + return CreateCloudClientForService(defaultConfigServerName) } -func CreateCloudClientForService(name string) ConfigClient { - serviceCredentials, _ := GetCloudCredentials(name) +func CreateCloudClientForService(name string) (*ConfigClient, error) { + serviceCredentials, err := GetCloudCredentials(defaultConfigServerName) + if err != nil { + return nil, errors.Wrap(err, "failed to create cloud client") + } configClients := make([]Client, len(serviceCredentials.Credentials)) for index, cred := range serviceCredentials.Credentials { configUri := cred.Uri client := net.CreateOAuth2Client(cred) configClients[index] = Client{configUri: configUri, httpClient: client} } - return ConfigClient{Clients: configClients} -} - -func GetCloudCredentialsByDefaultName() (*credentials.ServiceCredentials, error) { - return GetCloudCredentials(defaultConfigServerName) + return &ConfigClient{Clients: configClients}, nil } -func GetCloudCredentials(name string) (*credentials.ServiceCredentials, error) { +func GetCloudCredentials(name string) (*cfservices.ServiceCredentials, error) { vcapServices := cfservices.LoadFromEnvironment() serviceCreds, err := cfservices.GetServiceCredentials(name, vcapServices) if err != nil { - return nil, errors.Wrap(err, "failed to get credentials for the Config Server") + return nil, errors.Wrapf(err, "failed to get credentials for the Config Server service %s", name) } return serviceCreds, nil } diff --git a/configuration/configuration.go b/configuration/configuration.go index 6d06dd9..8ea11b0 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -26,21 +26,17 @@ func (client *Client) GetConfiguration(applicationName string, profiles []string continue } if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve application configurations from %s", - configClient.GetFullUrl(applicationName, net.JoinProfiles(profiles))) + return nil, errors.Wrapf(err, "failed to retrieve application configurations") } if resp.StatusCode != 200 { - return nil, errors.Errorf("server responded with status code %d from url %s", - resp.StatusCode, - configClient.GetFullUrl(applicationName, net.JoinProfiles(profiles))) + 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.Wrapf(err, "failed to decode response from url %s", - configClient.GetFullUrl(applicationName, net.JoinProfiles(profiles))) + return nil, errors.Wrapf(err, "failed to decode response from url") } return configuration, nil } diff --git a/go.mod b/go.mod index edce44f..51ffe29 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,10 @@ module github.com/Piszmog/cloudconfigclient require ( - github.com/Piszmog/cfservices v1.1.0 + github.com/Piszmog/cfservices v1.2.0 + github.com/golang/protobuf v1.1.0 // indirect github.com/pkg/errors v0.8.0 - golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 // indirect - golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc // indirect + golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24 + golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc + google.golang.org/appengine v1.1.0 // indirect ) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..05f5cff --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/Piszmog/cfservices v1.2.0 h1:qro2rdtFs7ifYrmxEY/dwTj84p0Qq7FlNE8Ucajkh44= +github.com/Piszmog/cfservices v1.2.0/go.mod h1:uxLe5gKNnODE9Bt1aJd5WjaeTg8/Qr6Dibd1wFgfAwk= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= diff --git a/main.go b/main.go index d43da0e..8837685 100644 --- a/main.go +++ b/main.go @@ -16,16 +16,20 @@ type Example struct { } func main() { - localClient := client.CreateLocalClient() - resourceClient := resource.CreateResourceClient(&localClient) + //configClient := client.CreateLocalClient() + configClient, err := client.CreateCloudClient() + if err != nil { + panic(err) + } + resourceClient := resource.CreateResourceClient(configClient) var file File - err := resourceClient.GetFile("temp", "temp1.json", &file) + err = resourceClient.GetFile("temp", "temp1.json", &file) if err != nil { panic(err) } - fmt.Printf("%+v", file) + fmt.Printf("%+v\n", file) - configurationClient := configuration.CreateConfigurationClient(&localClient) + configurationClient := configuration.CreateConfigurationClient(configClient) config, err := configurationClient.GetConfiguration("application", []string{"dev"}) if err != nil { panic(err) diff --git a/net/oauth2.go b/net/oauth2.go index c201286..4e4ff7a 100644 --- a/net/oauth2.go +++ b/net/oauth2.go @@ -1,18 +1,18 @@ package net import ( - "github.com/Piszmog/cfservices/credentials" + "github.com/Piszmog/cfservices" "golang.org/x/net/context" "golang.org/x/oauth2/clientcredentials" "net/http" ) -func CreateOAuth2Client(cred credentials.Credentials) *http.Client { +func CreateOAuth2Client(cred cfservices.Credentials) *http.Client { config := CreateOauth2Config(&cred) return config.Client(context.Background()) } -func CreateOauth2Config(cred *credentials.Credentials) *clientcredentials.Config { +func CreateOauth2Config(cred *cfservices.Credentials) *clientcredentials.Config { return &clientcredentials.Config{ ClientID: cred.ClientId, ClientSecret: cred.ClientSecret, diff --git a/resource/resource.go b/resource/resource.go index 713f38a..4a2726c 100644 --- a/resource/resource.go +++ b/resource/resource.go @@ -12,8 +12,8 @@ const ( ) type Resource interface { - GetFile(directory string, file string, interfaceType *interface{}) error - GetFileFromBranch(branch string, directory string, file string, interfaceType *interface{}) error + GetFile(directory string, file string, interfaceType interface{}) error + GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error } type Client struct { @@ -32,20 +32,16 @@ func (client *Client) GetFile(directory string, file string, interfaceType inter continue } if err != nil { - return errors.Wrapf(err, "failed to retrieve file from %s", - configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true")) + return errors.Wrapf(err, "failed to retrieve file") } if resp.StatusCode != 200 { - return errors.Errorf("server responded with status code %d from url %s", - resp.StatusCode, - configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true")) + 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 from url %s", - configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true")) + return errors.Wrapf(err, "failed to decode response") } fileFound = true } @@ -63,19 +59,16 @@ func (client *Client) GetFileFromBranch(branch string, directory string, file st continue } if err != nil { - return errors.Wrapf(err, "failed to retrieve file from %s", - configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, branch, directory, file)) + return errors.Wrapf(err, "failed to retrieve file") } if resp.StatusCode != 200 { - return errors.Errorf("server responded with status code %d from url %s", resp.StatusCode, - configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, branch, directory, file)) + 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 from url %s", - configClient.GetFullUrl(defaultApplicationName, defaultApplicationProfile, branch, directory, file)) + return errors.Wrapf(err, "failed to decode response") } fileFound = true } From 7712cf1a719340464ce2c9fa9be0c4ed2b48ba17 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 09:52:35 -0600 Subject: [PATCH 13/22] refactor packages to make sense and start adding tests --- client/client_test.go | 48 +++++++++++++++++++ {configuration => client}/configuration.go | 27 ++++++----- client/configuration_test.go | 1 + client/local_test.go | 1 + client/oauth2.go | 7 ++- client/oauth2_test.go | 1 + {resource => client}/resource.go | 23 +++------ client/resource_test.go | 1 + configuration/model.go | 15 ------ main.go | 10 ++-- net/oauth2.go | 17 +++++-- net/oauth2_test.go | 56 ++++++++++++++++++++++ 12 files changed, 151 insertions(+), 56 deletions(-) create mode 100644 client/client_test.go rename {configuration => client}/configuration.go (55%) create mode 100644 client/configuration_test.go create mode 100644 client/local_test.go create mode 100644 client/oauth2_test.go rename {resource => client}/resource.go (65%) create mode 100644 client/resource_test.go delete mode 100644 configuration/model.go create mode 100644 net/oauth2_test.go diff --git a/client/client_test.go b/client/client_test.go new file mode 100644 index 0000000..f63e75a --- /dev/null +++ b/client/client_test.go @@ -0,0 +1,48 @@ +package client + +import ( + "bytes" + "io/ioutil" + "net/http" + "testing" +) + +type RoundTripFunc func(req *http.Request) *http.Response + +func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { + return f(req), nil +} + +func CreateMockClient(fn RoundTripFunc) *http.Client { + return &http.Client{ + Transport: RoundTripFunc(fn), + } +} + +func TestClient_Get(t *testing.T) { + httpClient := CreateMockClient(func(req *http.Request) *http.Response { + return &http.Response{ + StatusCode: 200, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBufferString(`OK`)), + // Must be set to non-nil value or it panics + Header: make(http.Header), + } + }) + client := &Client{ + configUri: "http://localhost:8080", + httpClient: httpClient, + } + resp, err := client.Get("some", "path") + if err != nil { + t.Errorf("failed to call the mock server with error %v", err) + } + if resp == nil { + t.Errorf("expected a response body") + } + defer resp.Body.Close() + byteBody, _ := ioutil.ReadAll(resp.Body) + if string(byteBody) != "OK" { + t.Error("failed to read body") + } +} diff --git a/configuration/configuration.go b/client/configuration.go similarity index 55% rename from configuration/configuration.go rename to client/configuration.go index 8ea11b0..b6ef2f2 100644 --- a/configuration/configuration.go +++ b/client/configuration.go @@ -1,27 +1,32 @@ -package configuration +package client import ( "encoding/json" - "github.com/Piszmog/cloudconfigclient/client" "github.com/Piszmog/cloudconfigclient/net" "github.com/pkg/errors" ) -type Configuration interface { - GetConfiguration(applicationName string, profiles []string) (*Source, error) +type Source struct { + Name string `json:"name"` + Profiles []string `json:"profiles"` + Label string `json:"label"` + Version string `json:"version"` + State string `json:"state"` + PropertySources []PropertySource `json:"propertySources"` } -type Client struct { - configClient *client.ConfigClient +type PropertySource struct { + Name string `json:"name"` + Source map[string]interface{} `json:"source"` } -func CreateConfigurationClient(configClient *client.ConfigClient) *Client { - return &Client{configClient: configClient} +type Configuration interface { + GetConfiguration(applicationName string, profiles []string) (*Source, error) } -func (client *Client) GetConfiguration(applicationName string, profiles []string) (*Source, error) { - for _, configClient := range client.configClient.Clients { - resp, err := configClient.Get(applicationName, net.JoinProfiles(profiles)) +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 } diff --git a/client/configuration_test.go b/client/configuration_test.go new file mode 100644 index 0000000..da13c8e --- /dev/null +++ b/client/configuration_test.go @@ -0,0 +1 @@ +package client diff --git a/client/local_test.go b/client/local_test.go new file mode 100644 index 0000000..da13c8e --- /dev/null +++ b/client/local_test.go @@ -0,0 +1 @@ +package client diff --git a/client/oauth2.go b/client/oauth2.go index f606a9b..b74ec20 100644 --- a/client/oauth2.go +++ b/client/oauth2.go @@ -15,14 +15,17 @@ func CreateCloudClient() (*ConfigClient, error) { } func CreateCloudClientForService(name string) (*ConfigClient, error) { - serviceCredentials, err := GetCloudCredentials(defaultConfigServerName) + serviceCredentials, err := GetCloudCredentials(name) if err != nil { return nil, errors.Wrap(err, "failed to create cloud client") } configClients := make([]Client, len(serviceCredentials.Credentials)) for index, cred := range serviceCredentials.Credentials { configUri := cred.Uri - client := net.CreateOAuth2Client(cred) + client, err := net.CreateOAuth2Client(&cred) + if err != nil { + return nil, errors.Wrapf(err, "failed to create oauth2 client for %s", configUri) + } configClients[index] = Client{configUri: configUri, httpClient: client} } return &ConfigClient{Clients: configClients}, nil diff --git a/client/oauth2_test.go b/client/oauth2_test.go new file mode 100644 index 0000000..da13c8e --- /dev/null +++ b/client/oauth2_test.go @@ -0,0 +1 @@ +package client diff --git a/resource/resource.go b/client/resource.go similarity index 65% rename from resource/resource.go rename to client/resource.go index 4a2726c..2f665cf 100644 --- a/resource/resource.go +++ b/client/resource.go @@ -1,8 +1,7 @@ -package resource +package client import ( "encoding/json" - "github.com/Piszmog/cloudconfigclient/client" "github.com/pkg/errors" ) @@ -16,18 +15,10 @@ type Resource interface { GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error } -type Client struct { - configClient *client.ConfigClient -} - -func CreateResourceClient(configClient *client.ConfigClient) *Client { - return &Client{configClient: configClient} -} - -func (client *Client) GetFile(directory string, file string, interfaceType interface{}) error { +func (configClient *ConfigClient) GetFile(directory string, file string, interfaceType interface{}) error { fileFound := false - for _, configClient := range client.configClient.Clients { - resp, err := configClient.Get(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true") + for _, client := range configClient.Clients { + resp, err := client.Get(defaultApplicationName, defaultApplicationProfile, directory, file+"?useDefaultLabel=true") if resp != nil && resp.StatusCode == 404 { continue } @@ -51,10 +42,10 @@ func (client *Client) GetFile(directory string, file string, interfaceType inter return nil } -func (client *Client) GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error { +func (configClient *ConfigClient) GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error { fileFound := false - for _, configClient := range client.configClient.Clients { - resp, err := configClient.Get(defaultApplicationName, defaultApplicationProfile, branch, directory, file) + for _, client := range configClient.Clients { + resp, err := client.Get(defaultApplicationName, defaultApplicationProfile, branch, directory, file) if resp != nil && resp.StatusCode == 404 { continue } diff --git a/client/resource_test.go b/client/resource_test.go new file mode 100644 index 0000000..da13c8e --- /dev/null +++ b/client/resource_test.go @@ -0,0 +1 @@ +package client diff --git a/configuration/model.go b/configuration/model.go deleted file mode 100644 index 9560412..0000000 --- a/configuration/model.go +++ /dev/null @@ -1,15 +0,0 @@ -package configuration - -type Source struct { - Name string `json:"name"` - Profiles []string `json:"profiles"` - Label string `json:"label"` - Version string `json:"version"` - State string `json:"state"` - PropertySources []PropertySource `json:"propertySources"` -} - -type PropertySource struct { - Name string `json:"name"` - Source map[string]string `json:"source"` -} diff --git a/main.go b/main.go index 8837685..6e7f545 100644 --- a/main.go +++ b/main.go @@ -3,8 +3,6 @@ package main import ( "fmt" "github.com/Piszmog/cloudconfigclient/client" - "github.com/Piszmog/cloudconfigclient/configuration" - "github.com/Piszmog/cloudconfigclient/resource" ) type File struct { @@ -16,21 +14,19 @@ type Example struct { } func main() { - //configClient := client.CreateLocalClient() + //configClient, err := client.CreateLocalClient() configClient, err := client.CreateCloudClient() if err != nil { panic(err) } - resourceClient := resource.CreateResourceClient(configClient) var file File - err = resourceClient.GetFile("temp", "temp1.json", &file) + err = configClient.GetFile("temp", "temp1.json", &file) if err != nil { panic(err) } fmt.Printf("%+v\n", file) - configurationClient := configuration.CreateConfigurationClient(configClient) - config, err := configurationClient.GetConfiguration("application", []string{"dev"}) + config, err := configClient.GetConfiguration("testapp", []string{"dev"}) if err != nil { panic(err) } diff --git a/net/oauth2.go b/net/oauth2.go index 4e4ff7a..71b4c1f 100644 --- a/net/oauth2.go +++ b/net/oauth2.go @@ -2,20 +2,27 @@ package net import ( "github.com/Piszmog/cfservices" + "github.com/pkg/errors" "golang.org/x/net/context" "golang.org/x/oauth2/clientcredentials" "net/http" ) -func CreateOAuth2Client(cred cfservices.Credentials) *http.Client { - config := CreateOauth2Config(&cred) - return config.Client(context.Background()) +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 config.Client(context.Background()), nil } -func CreateOauth2Config(cred *cfservices.Credentials) *clientcredentials.Config { +func CreateOAuth2Config(cred *cfservices.Credentials) (*clientcredentials.Config, error) { + if cred == nil { + return nil, errors.New("cannot create oauth2 config when credentials are nil") + } return &clientcredentials.Config{ ClientID: cred.ClientId, ClientSecret: cred.ClientSecret, TokenURL: cred.AccessTokenUri, - } + }, nil } diff --git a/net/oauth2_test.go b/net/oauth2_test.go new file mode 100644 index 0000000..f0c603c --- /dev/null +++ b/net/oauth2_test.go @@ -0,0 +1,56 @@ +package net + +import ( + "github.com/Piszmog/cfservices" + "testing" +) + +func TestCreateOAuth2Client(t *testing.T) { + credentials := &cfservices.Credentials{ + AccessTokenUri: "tokenUri", + ClientSecret: "clientSecret", + ClientId: "clientId", + } + client, err := CreateOAuth2Client(credentials) + if err != nil { + t.Errorf("failed to create oauth2 client with error %v", err) + } + if client == nil { + t.Error("no oauth2 client returned") + } +} + +func TestCreateOAuth2ClientWhenCredentialsAreNil(t *testing.T) { + client, err := CreateOAuth2Client(nil) + if err == nil { + t.Error("expected an error when no credentials are passed") + } + if client != nil { + t.Error("able to create an oauth2 client with nil credentials") + } +} + +func TestCreateOauth2Config(t *testing.T) { + credentials := &cfservices.Credentials{ + AccessTokenUri: "tokenUri", + ClientSecret: "clientSecret", + ClientId: "clientId", + } + config, err := CreateOAuth2Config(credentials) + if err != nil { + t.Errorf("failed to create oauth2 with errpr %v", err) + } + if config == nil { + t.Error("failed to create oauth2 config") + } +} + +func TestCreateOauth2ConfigWhenCredentialsNil(t *testing.T) { + config, err := CreateOAuth2Config(nil) + if err == nil { + t.Error("expected an error when passing nil credentials when creating oauth2 config") + } + if config != nil { + t.Error("is able to create oauth2 config when credentials are nil") + } +} From ba6273224490cd5ae90335c3a66e424c2d509df7 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 12:09:01 -0600 Subject: [PATCH 14/22] add tests and update confligClient to use the cloudClient interface --- client/client.go | 4 +-- client/client_test.go | 28 ++++++++++++++-- client/configuration.go | 2 +- client/configuration_test.go | 64 ++++++++++++++++++++++++++++++++++++ client/local.go | 8 ++--- client/local_test.go | 54 ++++++++++++++++++++++++++++++ client/oauth2.go | 2 +- client/resource.go | 2 +- 8 files changed, 153 insertions(+), 11 deletions(-) diff --git a/client/client.go b/client/client.go index 5ced8a7..a29f3cf 100644 --- a/client/client.go +++ b/client/client.go @@ -7,7 +7,7 @@ import ( ) type ConfigClient struct { - Clients []Client + Clients []CloudClient } type CloudClient interface { @@ -19,7 +19,7 @@ type Client struct { httpClient *http.Client } -func (client *Client) Get(uriVariables ...string) (resp *http.Response, err error) { +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) diff --git a/client/client_test.go b/client/client_test.go index f63e75a..7de7864 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -7,20 +7,44 @@ import ( "testing" ) +type mockCloudClient struct { + code int + response string + error error +} + +func (client mockCloudClient) Get(uriVariables ...string) (resp *http.Response, err error) { + if client.error != nil { + return nil, client.error + } + return &http.Response{ + StatusCode: client.code, + // Send response to be tested + Body: ioutil.NopCloser(bytes.NewBufferString(client.response)), + // Must be set to non-nil value or it panics + Header: make(http.Header), + }, nil +} + +func createMockConfigClient(code int, response string, err error) *ConfigClient { + client := mockCloudClient{code: code, response: response, error: err} + return &ConfigClient{Clients: []CloudClient{client}} +} + type RoundTripFunc func(req *http.Request) *http.Response func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req), nil } -func CreateMockClient(fn RoundTripFunc) *http.Client { +func createMockHttpClient(fn RoundTripFunc) *http.Client { return &http.Client{ Transport: RoundTripFunc(fn), } } func TestClient_Get(t *testing.T) { - httpClient := CreateMockClient(func(req *http.Request) *http.Response { + httpClient := createMockHttpClient(func(req *http.Request) *http.Response { return &http.Response{ StatusCode: 200, // Send response to be tested diff --git a/client/configuration.go b/client/configuration.go index b6ef2f2..49b26e3 100644 --- a/client/configuration.go +++ b/client/configuration.go @@ -24,7 +24,7 @@ type Configuration interface { GetConfiguration(applicationName string, profiles []string) (*Source, error) } -func (configClient *ConfigClient) GetConfiguration(applicationName string, profiles []string) (*Source, error) { +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 { diff --git a/client/configuration_test.go b/client/configuration_test.go index da13c8e..7c111df 100644 --- a/client/configuration_test.go +++ b/client/configuration_test.go @@ -1 +1,65 @@ package client + +import ( + "github.com/pkg/errors" + "testing" +) + +const ( + configurationSource = `{"name":"testConfig", "profiles":["profile"],"propertySources":[{"name":"test","source":{"field1":"value1", "field2":1}}]}` +) + +func TestConfigClient_GetConfiguration(t *testing.T) { + configClient := createMockConfigClient(200, configurationSource, nil) + configuration, 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"}) + 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"}) + 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"}) + 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"}) + if err == nil { + t.Error("expected an error to occur") + } + if configuration != nil { + t.Error("retrieved configuration when not found") + } +} diff --git a/client/local.go b/client/local.go index 6bb52e2..ee84678 100644 --- a/client/local.go +++ b/client/local.go @@ -9,7 +9,7 @@ import ( ) const ( - environmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" + EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" ) func CreateLocalClient() (*ConfigClient, error) { @@ -17,7 +17,7 @@ func CreateLocalClient() (*ConfigClient, error) { if err != nil { return nil, errors.Wrap(err, "failed to create a local client") } - configClients := make([]Client, len(serviceCredentials.Credentials)) + configClients := make([]CloudClient, len(serviceCredentials.Credentials)) for index, cred := range serviceCredentials.Credentials { configUri := cred.Uri client := net.CreateDefaultHttpClient() @@ -27,9 +27,9 @@ func CreateLocalClient() (*ConfigClient, error) { } func GetLocalCredentials() (*cfservices.ServiceCredentials, error) { - localUrls := os.Getenv(environmentLocalConfigServerUrls) + 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, errors.Errorf("No local Config Server URLs provided in environment variable %s", EnvironmentLocalConfigServerUrls) } urls := strings.Split(localUrls, ",") var creds []cfservices.Credentials diff --git a/client/local_test.go b/client/local_test.go index da13c8e..3e8cd27 100644 --- a/client/local_test.go +++ b/client/local_test.go @@ -1 +1,55 @@ package client + +import ( + "os" + "testing" +) + +func TestCreateLocalClient(t *testing.T) { + const localURI = "http://localhost:8080" + os.Setenv(EnvironmentLocalConfigServerUrls, localURI) + defer os.Unsetenv(EnvironmentLocalConfigServerUrls) + configClient, err := CreateLocalClient() + if err != nil { + t.Errorf("failed to create local client with error %v", err) + } + if configClient == nil { + t.Error("failed to create local client") + } +} + +func TestCreateLocalClientWhenENVNotSet(t *testing.T) { + configClient, err := CreateLocalClient() + if err == nil { + t.Errorf("failed to create local client with error %v", err) + } + if configClient != nil { + t.Error("failed to create local client") + } +} + +func TestGetLocalCredentials(t *testing.T) { + const localURI = "http://localhost:8080" + os.Setenv(EnvironmentLocalConfigServerUrls, localURI) + defer os.Unsetenv(EnvironmentLocalConfigServerUrls) + serviceCredentials, err := GetLocalCredentials() + if err != nil { + t.Errorf("failed to get local credentials with error %v", err) + } + if serviceCredentials == nil { + t.Error("failed to create local credentials") + } + if serviceCredentials.Credentials[0].Uri != localURI { + t.Error("local credentials does not have the local url") + } +} + +func TestGetLocalCredentialsWhenEnvNotSet(t *testing.T) { + serviceCredentials, err := GetLocalCredentials() + if err == nil { + t.Errorf("expected an error when creating credentials") + } + if serviceCredentials != nil { + t.Error("created local credentials when uri not set") + } +} diff --git a/client/oauth2.go b/client/oauth2.go index b74ec20..97245cf 100644 --- a/client/oauth2.go +++ b/client/oauth2.go @@ -19,7 +19,7 @@ func CreateCloudClientForService(name string) (*ConfigClient, error) { if err != nil { return nil, errors.Wrap(err, "failed to create cloud client") } - configClients := make([]Client, len(serviceCredentials.Credentials)) + configClients := make([]CloudClient, len(serviceCredentials.Credentials)) for index, cred := range serviceCredentials.Credentials { configUri := cred.Uri client, err := net.CreateOAuth2Client(&cred) diff --git a/client/resource.go b/client/resource.go index 2f665cf..139247f 100644 --- a/client/resource.go +++ b/client/resource.go @@ -15,7 +15,7 @@ type Resource interface { GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error } -func (configClient *ConfigClient) GetFile(directory string, file string, interfaceType interface{}) error { +func (configClient 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") From 909afc6add5e5a299822cc960d9514ccabbd3a09 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 12:22:31 -0600 Subject: [PATCH 15/22] add tests --- client/configuration_test.go | 16 +++++++- client/oauth2_test.go | 76 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/client/configuration_test.go b/client/configuration_test.go index 7c111df..75cd565 100644 --- a/client/configuration_test.go +++ b/client/configuration_test.go @@ -6,7 +6,21 @@ import ( ) const ( - configurationSource = `{"name":"testConfig", "profiles":["profile"],"propertySources":[{"name":"test","source":{"field1":"value1", "field2":1}}]}` + configurationSource = `{ + "name": "testConfig", + "profiles": [ + "profile" + ], + "propertySources": [ + { + "name": "test", + "source": { + "field1": "value1", + "field2": 1 + } + } + ] +}` ) func TestConfigClient_GetConfiguration(t *testing.T) { diff --git a/client/oauth2_test.go b/client/oauth2_test.go index da13c8e..9c2d73a 100644 --- a/client/oauth2_test.go +++ b/client/oauth2_test.go @@ -1 +1,77 @@ package client + +import ( + "github.com/Piszmog/cfservices" + "os" + "testing" +) + +const ( + vcapServices = `{ + "p-config-server": [ + { + "name": "config-server", + "instance_name": "config-server", + "binding_name": null, + "credentials": { + "uri": "https://config-uri.com", + "client_secret": "clientSecret", + "client_id": "config-client-id", + "access_token_uri": "https://tokenuri.com" + }, + "syslog_drain_url": null, + "volume_mounts": [], + "label": "p-config-server", + "provider": null, + "plan": "testPlan", + "tags": [ + "testTag" + ] + } + ] +}` +) + +func TestCreateCloudClient(t *testing.T) { + os.Setenv(cfservices.VCAPServices, vcapServices) + defer os.Unsetenv(cfservices.VCAPServices) + configClient, err := CreateCloudClient() + if err != nil { + t.Errorf("failed to create cloud client with error %v", err) + } + if configClient == nil { + t.Error("failed to create cloud client") + } +} + +func TestCreateCloudClientWhenENVNotSet(t *testing.T) { + configClient, err := CreateCloudClient() + if err == nil { + t.Error("expected error when env is not set") + } + if configClient != nil { + t.Error("created cloud client when env is not set") + } +} + +func TestGetCloudCredentials(t *testing.T) { + os.Setenv(cfservices.VCAPServices, vcapServices) + defer os.Unsetenv(cfservices.VCAPServices) + serviceCredentials, err := GetCloudCredentials(defaultConfigServerName) + if err != nil { + t.Errorf("failed to create cloud credentials with error %v", err) + } + if serviceCredentials == nil { + t.Error("failed to create cloud credentials") + } +} + +func TestGetCloudCredentialsWhenENVNotSet(t *testing.T) { + serviceCredentials, err := GetCloudCredentials(defaultConfigServerName) + if err == nil { + t.Error("expected error when env is not set") + } + if serviceCredentials != nil { + t.Error("created cloud credentials when env is not set") + } +} From 8283d74bb12f115e365ab082fdaa3451844d544c Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 12:31:50 -0600 Subject: [PATCH 16/22] add resource test --- client/resource_test.go | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/client/resource_test.go b/client/resource_test.go index da13c8e..198be03 100644 --- a/client/resource_test.go +++ b/client/resource_test.go @@ -1 +1,142 @@ package client + +import ( + "github.com/pkg/errors" + "testing" +) + +const ( + testJSONFile = `{ + "example":{ + "field":"value" + } +}` +) + +type file struct { + Example example `json:"example"` +} + +type example struct { + Field string `json:"field"` +} + +func TestConfigClient_GetFile(t *testing.T) { + configClient := createMockConfigClient(200, testJSONFile, nil) + var file file + err := configClient.GetFile("directory", "file.json", &file) + if err != nil { + t.Errorf("failed to retrieve file with error %v", err) + } + if file.Example.Field != "value" { + t.Error("failed to retrieve file") + } +} + +func TestConfigClient_GetFileWhen404(t *testing.T) { + configClient := createMockConfigClient(404, "", nil) + var file file + err := configClient.GetFile("directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileWhenError(t *testing.T) { + configClient := createMockConfigClient(500, "", errors.New("failed")) + var file file + err := configClient.GetFile("directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileWhenNoErrorBut500(t *testing.T) { + configClient := createMockConfigClient(500, "", nil) + var file file + err := configClient.GetFile("directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileInvalidResponseBody(t *testing.T) { + configClient := createMockConfigClient(200, "", nil) + var file file + err := configClient.GetFile("directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileFromBranch(t *testing.T) { + configClient := createMockConfigClient(200, testJSONFile, nil) + var file file + err := configClient.GetFileFromBranch("branch", "directory", "file.json", &file) + if err != nil { + t.Errorf("failed to retrieve file with error %v", err) + } + if file.Example.Field != "value" { + t.Error("failed to retrieve file") + } +} + +func TestConfigClient_GetFileFromBranchWhen404(t *testing.T) { + configClient := createMockConfigClient(404, "", nil) + var file file + err := configClient.GetFileFromBranch("branch", "directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileFromBranchWhenError(t *testing.T) { + configClient := createMockConfigClient(500, "", errors.New("failed")) + var file file + err := configClient.GetFileFromBranch("branch", "directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileFromBranchWhenNoErrorBut500(t *testing.T) { + configClient := createMockConfigClient(500, "", nil) + var file file + err := configClient.GetFileFromBranch("branch", "directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} + +func TestConfigClient_GetFileFromBranchInvalidResponseBody(t *testing.T) { + configClient := createMockConfigClient(200, "", nil) + var file file + err := configClient.GetFileFromBranch("branch", "directory", "file.json", &file) + if err == nil { + t.Error("expected an error to occur") + } + if file.Example.Field == "value" { + t.Error("retrieved configuration when not found") + } +} From 8c7490389eecf232572788716b7f1dd24f0782a4 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 12:34:46 -0600 Subject: [PATCH 17/22] do not use append and allocate array --- client/local.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/local.go b/client/local.go index ee84678..36218cb 100644 --- a/client/local.go +++ b/client/local.go @@ -32,11 +32,11 @@ func GetLocalCredentials() (*cfservices.ServiceCredentials, error) { return nil, errors.Errorf("No local Config Server URLs provided in environment variable %s", EnvironmentLocalConfigServerUrls) } urls := strings.Split(localUrls, ",") - var creds []cfservices.Credentials - for _, url := range urls { - creds = append(creds, cfservices.Credentials{ + creds := make([]cfservices.Credentials, len(urls)) + for index, url := range urls { + creds[index] = cfservices.Credentials{ Uri: url, - }) + } } return &cfservices.ServiceCredentials{Credentials: creds}, nil } From 80547f405a8141455843dd3faa54a780afce4412 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 12:59:01 -0600 Subject: [PATCH 18/22] update readme --- README.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- main.go | 34 ------------------------- 2 files changed, 73 insertions(+), 35 deletions(-) delete mode 100644 main.go diff --git a/README.md b/README.md index ff7d837..1c12e6c 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,65 @@ can be used to load the base configurations an application requires to function. This library provides clients the ability to load Configurations and Files from the Config Server +## Example Usage +Below is an example usage of the library to retrieve a file from the Config Server and to retrieve the application's configurations + +* For local config client, ensure `CONFIG_SERVER_URLS` is set + * `CONFIG_SERVER_URLS` is a comma separated list of all the base URLs +* For running in Cloud Foundry, ensure a Config Server is bounded to the application. `VCAP_SERVICES` will be provided as an environment variables with the credentials to access the Config Server + * If not running in Cloud Foundry but still want to connect to a Config Server via OAuth2, manually set the `VCAP_SERVICES` -- example value in `client/oauth2_test.go` + +```go +package main + +import ( + "fmt" + "github.com/Piszmog/cloudconfigclient/client" +) + +type File struct { + Example Example `json:"example"` +} + +type Example struct { + Field string `json:"field"` +} + +func main() { + // To create a Client for a locally running Spring Config Server + configClient, err := client.CreateLocalClient() + // or to create a Client for a Spring Config Server in Cloud Foundry + configClient, err := client.CreateCloudClient() + if err != nil { + panic(err) + } + var file File + // Retrieves a 'temp1.json' from the Config Server's default branch in directory 'temp' and deserialize to File + err = configClient.GetFile("temp", "temp1.json", &file) + if err != nil { + panic(err) + } + fmt.Printf("%+v\n", file) + + // Retrieves the configurations from the Config Server based on the application name and active profiles + config, err := configClient.GetConfiguration("testApp", []string{"dev"}) + if err != nil { + panic(err) + } + fmt.Printf("%+v", config) +} +``` + +## Config Client Creation +There are two type of clients that can be created. A local client for a locally running Config Server without security (OAuth2) +and a cloud client for a Config Server running in a cloud environment. + +### Local +To create a local client, call `client.CreateLocalClient()`. The client is configured with timeouts set and to use a pool of connections. + +### Cloud +To create a cloud client, call `client.CreateCloudClient()`. The client is an OAuth2 client. The OAuth2 configurations are determined from the `VCAP_SERVICES` environment variable. + ## Configurations The Config Server allows the ability to retrieve configurations for an application. Only files that follow a strict naming convention will be loaded, @@ -47,8 +106,21 @@ The loaded configurations are in the following JSON format, } ``` -To use the library to retrieve configurations, use a client of `configuration.Configuration` called `configuration.Client` and +To use the library to retrieve configurations, create a `client/ConfigClient` and invoke the method `GetConfiguration(applicationName string, profiles []string)`. The return will be the struct representation of the configuration JSON - `model.Configuration`. ## Resources +Spring's Config Server allows two ways to retrieve files from a backing repository. + +| URL Path | +| :---: | +|`////?useDefaultLabel=true`| +|`/////?useDefaultLabel=true`| + +* When retrieving a file from the Config Server's default branch, the file must not exist at the root of the repository. + +The functions available to retrieve resource files are, `GetFile(directory string, file string, interfaceType interface{}) error` and +`GetFileFromBranch(branch string, directory string, file string, interfaceType interface{})`. + +* The `interfaceTypee` is the object to deserialize the file to \ No newline at end of file diff --git a/main.go b/main.go deleted file mode 100644 index 6e7f545..0000000 --- a/main.go +++ /dev/null @@ -1,34 +0,0 @@ -package main - -import ( - "fmt" - "github.com/Piszmog/cloudconfigclient/client" -) - -type File struct { - Example Example `json:"example"` -} - -type Example struct { - Field3 string `json:"field3"` -} - -func main() { - //configClient, err := client.CreateLocalClient() - configClient, err := client.CreateCloudClient() - if err != nil { - panic(err) - } - var file File - err = configClient.GetFile("temp", "temp1.json", &file) - if err != nil { - panic(err) - } - fmt.Printf("%+v\n", file) - - config, err := configClient.GetConfiguration("testapp", []string{"dev"}) - if err != nil { - panic(err) - } - fmt.Printf("%+v", config) -} From 19a400f30cd26d51e97532d97a818a720a36da31 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 13:06:38 -0600 Subject: [PATCH 19/22] start adding docs --- client/client.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/client.go b/client/client.go index a29f3cf..c539664 100644 --- a/client/client.go +++ b/client/client.go @@ -6,19 +6,23 @@ import ( "net/http" ) +// Client for the Config Server type ConfigClient struct { Clients []CloudClient } +// Client interacting with the Config Server's REST APIs type CloudClient interface { Get(uriVariables ...string) (resp *http.Response, err error) } +// Client that wraps http.Client and the base Uri of the http client type Client struct { configUri string httpClient *http.Client } +// Get performs a REST GET func (client Client) Get(uriVariables ...string) (resp *http.Response, err error) { fullUrl := net.CreateUrl(client.configUri, uriVariables...) response, err := client.httpClient.Get(fullUrl) From fb4ede181dab59b876cdb7f571420239105c83b9 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 21:17:24 -0600 Subject: [PATCH 20/22] add docs --- client/configuration.go | 7 +++++++ client/local.go | 7 +++++++ client/oauth2.go | 9 +++++++++ client/resource.go | 7 +++++++ net/net.go | 11 +++++++++++ net/oauth2.go | 2 ++ 6 files changed, 43 insertions(+) diff --git a/client/configuration.go b/client/configuration.go index 49b26e3..8a4383a 100644 --- a/client/configuration.go +++ b/client/configuration.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" ) +// The application's source configurations. It con contain zero to n number of property sources. type Source struct { Name string `json:"name"` Profiles []string `json:"profiles"` @@ -15,15 +16,21 @@ type Source struct { PropertySources []PropertySource `json:"propertySources"` } +// A property source for the application. +// +// A property source is either a YAML or a PROPERTIES file located in the repository that a Config Server is pointed at. type PropertySource struct { Name string `json:"name"` Source map[string]interface{} `json:"source"` } +// The configuration interface for retrieving an application's configuration files from the Config Server. type Configuration interface { GetConfiguration(applicationName string, profiles []string) (*Source, error) } +// 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)) diff --git a/client/local.go b/client/local.go index 36218cb..da3427b 100644 --- a/client/local.go +++ b/client/local.go @@ -9,9 +9,13 @@ import ( ) const ( + // Environment variable for setting base URLs for local Config Servers. EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" ) +// CreateLocalClient creates a ConfigClient for a locally running Config Server. +// +// The ConfigClient's underlying http.Client is configured with timeouts and connection pools. func CreateLocalClient() (*ConfigClient, error) { serviceCredentials, err := GetLocalCredentials() if err != nil { @@ -26,6 +30,9 @@ func CreateLocalClient() (*ConfigClient, error) { return &ConfigClient{Clients: configClients}, nil } +// GetLocalCredentials creates the credentials that are used to configure a ConfigClient to access a local Config Server. +// +// Retrieves the base URLs of Config Servers from the environment variable 'CONFIG_SERVER_URLS' - a comma separated list. func GetLocalCredentials() (*cfservices.ServiceCredentials, error) { localUrls := os.Getenv(EnvironmentLocalConfigServerUrls) if len(localUrls) == 0 { diff --git a/client/oauth2.go b/client/oauth2.go index 97245cf..9846d60 100644 --- a/client/oauth2.go +++ b/client/oauth2.go @@ -10,10 +10,18 @@ const ( defaultConfigServerName = "p-config-server" ) +// CreateCloudClient creates a ConfigClient to access Config Servers running in the cloud (specifically Cloud Foundry). +// +// The environment variables 'VCAP_SERVICES' provides a JSON that contains an entry with the key 'p-config-server'. This +// entry and used to build an OAuth2 client. func CreateCloudClient() (*ConfigClient, error) { return CreateCloudClientForService(defaultConfigServerName) } +// CreateCloudClientForService creates a ConfigClient to access Config Servers running in the cloud (specifically Cloud Foundry). +// +// The environment variables 'VCAP_SERVICES' provides a JSON. The JSON should contain the entry matching the specified name. This +// entry and used to build an OAuth2 client. func CreateCloudClientForService(name string) (*ConfigClient, error) { serviceCredentials, err := GetCloudCredentials(name) if err != nil { @@ -31,6 +39,7 @@ func CreateCloudClientForService(name string) (*ConfigClient, error) { return &ConfigClient{Clients: configClients}, nil } +// GetCloudCredentials retrieves the Config Server's credentials so an OAuth2 client can be created. func GetCloudCredentials(name string) (*cfservices.ServiceCredentials, error) { vcapServices := cfservices.LoadFromEnvironment() serviceCreds, err := cfservices.GetServiceCredentials(name, vcapServices) diff --git a/client/resource.go b/client/resource.go index 139247f..56c98f4 100644 --- a/client/resource.go +++ b/client/resource.go @@ -10,11 +10,15 @@ const ( defaultApplicationProfile = "default" ) +// The resource interface describes how to retrieve files from the Config Server. type Resource interface { GetFile(directory string, file string, interfaceType interface{}) error GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error } +// 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 { fileFound := false for _, client := range configClient.Clients { @@ -42,6 +46,9 @@ func (configClient ConfigClient) GetFile(directory string, file string, interfac return nil } +// 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 { fileFound := false for _, client := range configClient.Clients { diff --git a/net/net.go b/net/net.go index b2d962b..1a9a597 100644 --- a/net/net.go +++ b/net/net.go @@ -7,6 +7,9 @@ import ( "time" ) +// CreateUrl creates a full URL from the specified base URL and the array of URI variables. +// +// URI variables are separated by '/'. func CreateUrl(baseUrl string, uriVariables ...string) string { url := strings.TrimRight(baseUrl, "/") for _, uriVariable := range uriVariables { @@ -15,14 +18,22 @@ func CreateUrl(baseUrl string, uriVariables ...string) string { return url } +// JoinProfiles joins the array of profiles with a comma. func JoinProfiles(profiles []string) string { return strings.Join(profiles, ",") } +// CreateDefaultHttpClient creates a default http.Client. +// +// Timeout set to 5 seconds, keep alive set to 30 seconds, TLS handshake timeout set to 5 seconds, and idleConnection set to +// 90 seconds. func CreateDefaultHttpClient() *http.Client { return CreateHttpClient(5*time.Second, 30*time.Second, 5*time.Second, 90*time.Second) } +// Creates a http.Client from the specified timeouts and keep alive. +// +// The client also has the maximum number of idle connections set to 100 and number of connections per host as 100. func CreateHttpClient(timeout time.Duration, keepAlive time.Duration, tlsHandshakeTimeout time.Duration, idleConnection time.Duration) *http.Client { transport := &http.Transport{ DialContext: (&net.Dialer{ diff --git a/net/oauth2.go b/net/oauth2.go index 71b4c1f..d858028 100644 --- a/net/oauth2.go +++ b/net/oauth2.go @@ -8,6 +8,7 @@ import ( "net/http" ) +// CreateOAuth2Client creates an OAuth2 http.Client from the provided credentials. func CreateOAuth2Client(cred *cfservices.Credentials) (*http.Client, error) { config, err := CreateOAuth2Config(cred) if err != nil { @@ -16,6 +17,7 @@ func CreateOAuth2Client(cred *cfservices.Credentials) (*http.Client, error) { return config.Client(context.Background()), nil } +// CreateOAuth2Config creates an OAuth2 config from the provided credentials. func CreateOAuth2Config(cred *cfservices.Credentials) (*clientcredentials.Config, error) { if cred == nil { return nil, errors.New("cannot create oauth2 config when credentials are nil") From fc65f8dde072313a08639588ea22c0d872c35e97 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 21:19:46 -0600 Subject: [PATCH 21/22] update docs --- client/client.go | 4 ++-- client/configuration.go | 6 +++--- client/resource.go | 2 +- net/net.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/client.go b/client/client.go index c539664..81bc196 100644 --- a/client/client.go +++ b/client/client.go @@ -6,12 +6,12 @@ import ( "net/http" ) -// Client for the Config Server +// ConfigClient contains the clients of the Config Servers. type ConfigClient struct { Clients []CloudClient } -// Client interacting with the Config Server's REST APIs +// CloudClient interacts with the Config Server's REST APIs type CloudClient interface { Get(uriVariables ...string) (resp *http.Response, err error) } diff --git a/client/configuration.go b/client/configuration.go index 8a4383a..f9db9ea 100644 --- a/client/configuration.go +++ b/client/configuration.go @@ -6,7 +6,7 @@ import ( "github.com/pkg/errors" ) -// The application's source configurations. It con contain zero to n number of property sources. +// Source is the application's source configurations. It con contain zero to n number of property sources. type Source struct { Name string `json:"name"` Profiles []string `json:"profiles"` @@ -16,7 +16,7 @@ type Source struct { PropertySources []PropertySource `json:"propertySources"` } -// A property source for the application. +// PropertySource is the property source for the application. // // A property source is either a YAML or a PROPERTIES file located in the repository that a Config Server is pointed at. type PropertySource struct { @@ -24,7 +24,7 @@ type PropertySource struct { Source map[string]interface{} `json:"source"` } -// The configuration interface for retrieving an application's configuration files from the Config Server. +// Configuration interface for retrieving an application's configuration files from the Config Server. type Configuration interface { GetConfiguration(applicationName string, profiles []string) (*Source, error) } diff --git a/client/resource.go b/client/resource.go index 56c98f4..88aa8c0 100644 --- a/client/resource.go +++ b/client/resource.go @@ -10,7 +10,7 @@ const ( defaultApplicationProfile = "default" ) -// The resource interface describes how to retrieve files from the Config Server. +// Resource interface describes how to retrieve files from the Config Server. type Resource interface { GetFile(directory string, file string, interfaceType interface{}) error GetFileFromBranch(branch string, directory string, file string, interfaceType interface{}) error diff --git a/net/net.go b/net/net.go index 1a9a597..e5b7bc6 100644 --- a/net/net.go +++ b/net/net.go @@ -31,7 +31,7 @@ func CreateDefaultHttpClient() *http.Client { return CreateHttpClient(5*time.Second, 30*time.Second, 5*time.Second, 90*time.Second) } -// Creates a http.Client from the specified timeouts and keep alive. +// CreateHttpClient creates a http.Client from the specified timeouts and keep alive. // // The client also has the maximum number of idle connections set to 100 and number of connections per host as 100. func CreateHttpClient(timeout time.Duration, keepAlive time.Duration, tlsHandshakeTimeout time.Duration, idleConnection time.Duration) *http.Client { From d126642455bef7657eb3c12b0027c6a0aafaa1a3 Mon Sep 17 00:00:00 2001 From: Piszmog Date: Sat, 11 Aug 2018 21:20:26 -0600 Subject: [PATCH 22/22] update docs --- client/local.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/local.go b/client/local.go index da3427b..08980ff 100644 --- a/client/local.go +++ b/client/local.go @@ -9,7 +9,7 @@ import ( ) const ( - // Environment variable for setting base URLs for local Config Servers. + // EnvironmentLocalConfigServerUrls is an environment variable for setting base URLs for local Config Servers. EnvironmentLocalConfigServerUrls = "CONFIG_SERVER_URLS" )