├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── application.go ├── cfenv.go ├── cfenv_internals_suite_test.go ├── cfenv_suite_test.go ├── cfenv_test.go ├── environment.go ├── environment_test.go ├── envmap.go ├── envmap_test.go ├── go.mod ├── go.sum └── service.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | vendor 24 | Godeps 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | 4 | env: 5 | - GO111MODULE=on 6 | 7 | go: 8 | - 1.11.x 9 | - 1.12.x 10 | - tip 11 | 12 | script: 13 | - go build ./... 14 | - go test -race ./... 15 | - go test -cover ./... 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2014-Present Joe Fitzgerald 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Go CF Environment Package [![Build Status - Master](https://travis-ci.org/cloudfoundry-community/go-cfenv.svg?branch=master)](https://travis-ci.org/cloudfoundry-community/go-cfenv) 2 | 3 | ### Overview 4 | 5 | [![GoDoc](https://godoc.org/github.com/cloudfoundry-community/go-cfenv?status.png)](https://godoc.org/github.com/cloudfoundry-community/go-cfenv) 6 | 7 | `cfenv` is a package to assist you in writing Go apps that run on [Cloud Foundry](http://cloudfoundry.org). It provides convenience functions and structures that map to Cloud Foundry environment variable primitives (http://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html). 8 | 9 | ### Usage 10 | 11 | `go get github.com/cloudfoundry-community/go-cfenv` 12 | 13 | ```go 14 | package main 15 | 16 | import ( 17 | "github.com/cloudfoundry-community/go-cfenv" 18 | ) 19 | 20 | func main() { 21 | appEnv, _ := cfenv.Current() 22 | 23 | fmt.Println("ID:", appEnv.ID) 24 | fmt.Println("Index:", appEnv.Index) 25 | fmt.Println("Name:", appEnv.Name) 26 | fmt.Println("Host:", appEnv.Host) 27 | fmt.Println("Port:", appEnv.Port) 28 | fmt.Println("Version:", appEnv.Version) 29 | fmt.Println("Home:", appEnv.Home) 30 | fmt.Println("MemoryLimit:", appEnv.MemoryLimit) 31 | fmt.Println("WorkingDir:", appEnv.WorkingDir) 32 | fmt.Println("TempDir:", appEnv.TempDir) 33 | fmt.Println("User:", appEnv.User) 34 | fmt.Println("Services:", appEnv.Services) 35 | } 36 | ``` 37 | 38 | ### Contributing 39 | 40 | Pull requests welcomed. 41 | -------------------------------------------------------------------------------- /application.go: -------------------------------------------------------------------------------- 1 | package cfenv 2 | 3 | // An App holds information about the current app running on Cloud Foundry 4 | type App struct { 5 | ID string `json:"-"` // DEPRECATED id of the instance 6 | InstanceID string `json:"instance_id"` // id of the instance 7 | AppID string `json:"application_id"` // id of the application 8 | Index int `json:"instance_index"` // index of the app 9 | Name string `json:"name"` // name of the app 10 | Host string `json:"host"` // host of the app 11 | Port int `json:"port"` // port of the app 12 | Version string `json:"version"` // version of the app 13 | ApplicationURIs []string `json:"application_uris"` // application uri of the app 14 | SpaceID string `json:"space_id"` // id of the space 15 | SpaceName string `json:"space_name"` // name of the space 16 | Home string // root folder for the deployed app 17 | MemoryLimit string // maximum amount of memory that each instance of the application can consume 18 | WorkingDir string // present working directory, where the buildpack that processed the application ran 19 | TempDir string // directory location where temporary and staging files are stored 20 | User string // user account under which the DEA runs 21 | Services Services // services bound to the app 22 | CFAPI string `json:"cf_api"` // URL for the Cloud Foundry API endpoint 23 | Limits *Limits `json:"limits"` // limits imposed on this process 24 | } 25 | 26 | type Limits struct { 27 | Disk int `json:"disk"` // disk limit 28 | FDs int `json:"fds"` // file descriptors limit 29 | Mem int `json:"mem"` // memory limit 30 | } 31 | -------------------------------------------------------------------------------- /cfenv.go: -------------------------------------------------------------------------------- 1 | // Package cfenv provides information about the current app deployed on Cloud Foundry, including any bound service(s). 2 | package cfenv 3 | 4 | import ( 5 | "encoding/json" 6 | "os" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/mitchellh/mapstructure" 11 | ) 12 | 13 | // New creates a new App with the provided environment. 14 | func New(env map[string]string) (*App, error) { 15 | var app App 16 | appVar := env["VCAP_APPLICATION"] 17 | if err := json.Unmarshal([]byte(appVar), &app); err != nil { 18 | return nil, err 19 | } 20 | // duplicate the InstanceID to the previously named ID field for backwards 21 | // compatibility 22 | app.ID = app.InstanceID 23 | 24 | app.Home = env["HOME"] 25 | app.MemoryLimit = env["MEMORY_LIMIT"] 26 | if port, err := strconv.Atoi(env["PORT"]); err == nil { 27 | app.Port = port 28 | } 29 | app.WorkingDir = env["PWD"] 30 | app.TempDir = env["TMPDIR"] 31 | app.User = env["USER"] 32 | 33 | var rawServices map[string]interface{} 34 | servicesVar := env["VCAP_SERVICES"] 35 | if err := json.Unmarshal([]byte(servicesVar), &rawServices); err != nil { 36 | return nil, err 37 | } 38 | 39 | services := make(map[string][]Service) 40 | for k, v := range rawServices { 41 | var serviceInstances []Service 42 | if err := mapstructure.WeakDecode(v, &serviceInstances); err != nil { 43 | return nil, err 44 | } 45 | services[k] = serviceInstances 46 | } 47 | app.Services = services 48 | return &app, nil 49 | } 50 | 51 | // Current creates a new App with the current environment; returns an error if the current environment is not a Cloud Foundry environment 52 | func Current() (*App, error) { 53 | return New(CurrentEnv()) 54 | } 55 | 56 | // IsRunningOnCF returns true if the current environment is Cloud Foundry and false if it is not Cloud Foundry 57 | func IsRunningOnCF() bool { 58 | return strings.TrimSpace(os.Getenv("VCAP_APPLICATION")) != "" 59 | } 60 | -------------------------------------------------------------------------------- /cfenv_internals_suite_test.go: -------------------------------------------------------------------------------- 1 | package cfenv 2 | 3 | import ( 4 | reporter "github.com/joefitzgerald/rainbow-reporter" 5 | "github.com/sclevine/spec" 6 | 7 | "testing" 8 | ) 9 | 10 | var suite spec.Suite 11 | 12 | func init() { 13 | suite = spec.New("go-cfenv internals", spec.Report(reporter.Rainbow{})) 14 | suite("envmap", testEnvMap) 15 | } 16 | 17 | func TestInternalsSuite(t *testing.T) { 18 | suite.Run(t) 19 | } 20 | -------------------------------------------------------------------------------- /cfenv_suite_test.go: -------------------------------------------------------------------------------- 1 | package cfenv_test 2 | 3 | import ( 4 | reporter "github.com/joefitzgerald/rainbow-reporter" 5 | "github.com/sclevine/spec" 6 | 7 | "testing" 8 | ) 9 | 10 | var suite spec.Suite 11 | 12 | func init() { 13 | suite = spec.New("go-cfenv api", spec.Report(reporter.Rainbow{})) 14 | suite("environment", testEnvironment) 15 | suite("cfenv", testcfenv) 16 | } 17 | 18 | func TestSuite(t *testing.T) { 19 | suite.Run(t) 20 | } 21 | -------------------------------------------------------------------------------- /cfenv_test.go: -------------------------------------------------------------------------------- 1 | package cfenv_test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/cloudfoundry-community/go-cfenv" 8 | "github.com/mitchellh/mapstructure" 9 | . "github.com/onsi/gomega" 10 | "github.com/sclevine/spec" 11 | ) 12 | 13 | func testcfenv(t *testing.T, when spec.G, it spec.S) { 14 | it.Before(func() { 15 | RegisterTestingT(t) 16 | }) 17 | 18 | when("application deserialization", func() { 19 | validEnv := []string{ 20 | `VCAP_APPLICATION={"instance_id":"451f045fd16427bb99c895a2649b7b2a","application_id":"abcabc123123defdef456456","cf_api": "https://api.system_domain.com","instance_index":0,"host":"0.0.0.0","port":61857,"started_at":"2013-08-12 00:05:29 +0000","started_at_timestamp":1376265929,"start":"2013-08-12 00:05:29 +0000","state_timestamp":1376265929,"limits":{"mem":512,"disk":1024,"fds":16384},"application_version":"c1063c1c-40b9-434e-a797-db240b587d32","application_name":"styx-james","application_uris":["styx-james.a1-app.cf-app.com"],"version":"c1063c1c-40b9-434e-a797-db240b587d32","name":"styx-james","space_id":"3e0c28c5-6d9c-436b-b9ee-1f4326e54d05","space_name":"jdk","uris":["styx-james.a1-app.cf-app.com"],"users":null}`, 21 | `HOME=/home/vcap/app`, 22 | `MEMORY_LIMIT=512m`, 23 | `PWD=/home/vcap`, 24 | `TMPDIR=/home/vcap/tmp`, 25 | `USER=vcap`, 26 | `VCAP_SERVICES={"elephantsql-dev":[{"name":"elephantsql-dev-c6c60","label":"elephantsql-dev","tags":["New Product","relational","Data Store","postgresql"],"plan":"turtle","credentials":{"uri":"postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd"}}],"sendgrid":[{"name":"mysendgrid","label":"sendgrid","tags":["smtp","Email"],"plan":"free","credentials":{"hostname":"smtp.sendgrid.net","username":"QvsXMbJ3rK","password":"HCHMOYluTv"}}],"nfs":[{"credentials":{},"label":"nfs","name":"nfs","plan":"Existing","tags":["nfs"],"volume_mounts":[{"container_dir":"/testpath","device_type":"shared","mode":"rw"}]}]}`, 27 | } 28 | 29 | validEnvWithoutSpaceIDAndName := []string{ 30 | `VCAP_APPLICATION={"instance_id":"451f045fd16427bb99c895a2649b7b2a","application_id":"abcabc123123defdef456456","cf_api": "https://api.system_domain.com","instance_index":0,"host":"0.0.0.0","port":61857,"started_at":"2013-08-12 00:05:29 +0000","started_at_timestamp":1376265929,"start":"2013-08-12 00:05:29 +0000","state_timestamp":1376265929,"limits":{"mem":512,"disk":1024,"fds":16384},"application_version":"c1063c1c-40b9-434e-a797-db240b587d32","application_name":"styx-james","application_uris":["styx-james.a1-app.cf-app.com"],"version":"c1063c1c-40b9-434e-a797-db240b587d32","name":"styx-james","uris":["styx-james.a1-app.cf-app.com"],"users":null}`, 31 | `HOME=/home/vcap/app`, 32 | `MEMORY_LIMIT=512m`, 33 | `PWD=/home/vcap`, 34 | `TMPDIR=/home/vcap/tmp`, 35 | `USER=vcap`, 36 | `VCAP_SERVICES={"elephantsql-dev":[{"name":"elephantsql-dev-c6c60","label":"elephantsql-dev","tags":["New Product","relational","Data Store","postgresql"],"plan":"turtle","credentials":{"uri":"postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd"}}],"sendgrid":[{"name":"mysendgrid","label":"sendgrid","tags":["smtp","Email"],"plan":"free","credentials":{"hostname":"smtp.sendgrid.net","username":"QvsXMbJ3rK","password":"HCHMOYluTv"}}]}`, 37 | } 38 | 39 | envWithIntCredentials := []string{ 40 | `VCAP_APPLICATION={"instance_id":"451f045fd16427bb99c895a2649b7b2a","application_id":"abcabc123123defdef456456","cf_api": "https://api.system_domain.com","instance_index":0,"host":"0.0.0.0","port":61857,"started_at":"2013-08-12 00:05:29 +0000","started_at_timestamp":1376265929,"start":"2013-08-12 00:05:29 +0000","state_timestamp":1376265929,"limits":{"mem":512,"disk":1024,"fds":16384},"application_version":"c1063c1c-40b9-434e-a797-db240b587d32","application_name":"styx-james","application_uris":["styx-james.a1-app.cf-app.com"],"version":"c1063c1c-40b9-434e-a797-db240b587d32","name":"styx-james","uris":["styx-james.a1-app.cf-app.com"],"users":null}`, 41 | `HOME=/home/vcap/app`, 42 | `MEMORY_LIMIT=512m`, 43 | `PWD=/home/vcap`, 44 | `TMPDIR=/home/vcap/tmp`, 45 | `USER=vcap`, 46 | `VCAP_SERVICES={"elephantsql-dev":[{"name":"elephantsql-dev-c6c60","label":"elephantsql-dev","tags":["New Product","relational","Data Store","postgresql"],"plan":"turtle","credentials":{"uri":"postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd"}}],"cloudantNoSQLDB": [{ "name": "my_cloudant", "label": "cloudantNoSQLDB", "plan": "Shared", "credentials": { "username": "18675309-0000-4aaa-bbbb-999999999-bluemix", "password": "18675309deadbeefaaaabbbbccccddddeeeeffff000099999999999999999999", "host": "01234567-9999-4999-aaaa-abcdefabcdef-bluemix.cloudant.com", "port": 443, "url": "https://18675309-0000-4aaa-bbbb-999999999-bluemix:18675309deadbeefaaaabbbbccccddddeeeeffff000099999999999999999999@01234567-9999-4999-aaaa-abcdefabcdef-bluemix.cloudant.com"}}],"sendgrid":[{"name":"mysendgrid","label":"sendgrid","tags":["smtp","Email"],"plan":"free","credentials":{"hostname":"smtp.sendgrid.net","username":"QvsXMbJ3rK","password":"HCHMOYluTv"}}]}`, 47 | } 48 | 49 | envWithArrayCredentials := []string{ 50 | `VCAP_APPLICATION={}`, 51 | `VCAP_SERVICES={"p-kafka": [{"credentials": { "kafka" : { "port": 9092, "node_ips": ["10.244.9.2", "10.244.9.6", "10.244.9.10"]}}}]}`, 52 | } 53 | 54 | invalidEnv := []string{ 55 | `VCAP_APPLICATION={"instance_index":0,"host":"0.0.0.0","port":61857,"started_at":"2013-08-12 00:05:29 +0000","started_at_timestamp":1376265929,"start":"2013-08-12 00:05:29 +0000","state_timestamp":1376265929,"limits":{"mem":512,"disk":1024,"fds":16384},"application_version":"c1063c1c-40b9-434e-a797-db240b587d32","application_name":"styx-james","application_uris":["styx-james.a1-app.cf-app.com"],"version":"c1063c1c-40b9-434e-a797-db240b587d32","name":"styx-james","uris":["styx-james.a1-app.cf-app.com"],"users":null}`, 56 | `HOME=/home/vcap/app`, 57 | `MEMORY_LIMIT_INVALID=512m`, 58 | `PWD=/home/vcap`, 59 | `TMPDIR=/home/vcap/tmp`, 60 | `USER=vcap`, 61 | `VCAP_SERVICES={"elephantsql-dev":[{"name":"","label":"elephantsql-dev","plan":"turtle","credentials":{"uri":"postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd"}}],"sendgrid":[{"name":"mysendgrid","label":"sendgrid","plan":"free","credentials":{"hostname":"smtp.sendgrid.net","username":"QvsXMbJ3rK","password":"HCHMOYluTv"}}]}`, 62 | } 63 | 64 | notCFEnv := []string{ 65 | `HOME=/home/vcap/app`, 66 | `MEMORY_LIMIT_INVALID=512m`, 67 | `PWD=/home/vcap`, 68 | `PORT=1234`, 69 | `TMPDIR=/home/vcap/tmp`, 70 | `USER=vcap`, 71 | } 72 | 73 | cfEnv := []string{ 74 | `VCAP_APPLICATION={"instance_id":"451f045fd16427bb99c895a2649b7b2a","application_id":"abcabc123123defdef456456","cf_api": "https://api.system_domain.com","instance_index":0,"host":"0.0.0.0","port":61857,"started_at":"2013-08-12 00:05:29 +0000","started_at_timestamp":1376265929,"start":"2013-08-12 00:05:29 +0000","state_timestamp":1376265929,"limits":{"mem":512,"disk":1024,"fds":16384},"application_version":"c1063c1c-40b9-434e-a797-db240b587d32","application_name":"styx-james","application_uris":["styx-james.a1-app.cf-app.com"],"version":"c1063c1c-40b9-434e-a797-db240b587d32","name":"styx-james","uris":["styx-james.a1-app.cf-app.com"],"users":null}`, 75 | `HOME=/home/vcap/app`, 76 | `MEMORY_LIMIT_INVALID=512m`, 77 | `PWD=/home/vcap`, 78 | `PORT=1234`, 79 | `TMPDIR=/home/vcap/tmp`, 80 | `USER=vcap`, 81 | `VCAP_SERVICES={"elephantsql-dev":[{"name":"","label":"elephantsql-dev","plan":"turtle","credentials":{"uri":"postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd"}}],"sendgrid":[{"name":"mysendgrid","label":"sendgrid","plan":"free","credentials":{"hostname":"smtp.sendgrid.net","username":"QvsXMbJ3rK","password":"HCHMOYluTv"}}],"nfs":[{"credentials":{},"label":"nfs","name":"nfsexport","plan":"Existing","volume_mounts":[{"container_dir":"/testpath","device_type":"shared","mode":"rw"}]}]}`, 82 | } 83 | 84 | when("when not running on Cloud Foundry", func() { 85 | it("IsRunningOnCF() returns false", func() { 86 | testEnv := cfenv.Env(notCFEnv) 87 | _, err := cfenv.New(testEnv) 88 | Expect(err).To(HaveOccurred()) 89 | Expect(cfenv.IsRunningOnCF()).To(BeFalse()) 90 | }) 91 | }) 92 | 93 | when("when running on Cloud Foundry", func() { 94 | it.Before(func() { 95 | os.Setenv("VCAP_APPLICATION", "{}") 96 | }) 97 | 98 | it.After(func() { 99 | os.Unsetenv("VCAP_APPLICATION") 100 | }) 101 | 102 | it("IsRunningOnCF() returns true", func() { 103 | testEnv := cfenv.Env(cfEnv) 104 | _, err := cfenv.New(testEnv) 105 | Expect(err).NotTo(HaveOccurred()) 106 | Expect(cfenv.IsRunningOnCF()).To(BeTrue()) 107 | }) 108 | }) 109 | 110 | when("with valid environment", func() { 111 | it("should deserialize correctly", func() { 112 | testEnv := cfenv.Env(validEnv) 113 | env, err := cfenv.New(testEnv) 114 | Expect(err).To(BeNil()) 115 | Expect(env).NotTo(BeNil()) 116 | 117 | Expect(env.ID).To(Equal("451f045fd16427bb99c895a2649b7b2a")) 118 | Expect(env.InstanceID).To(Equal("451f045fd16427bb99c895a2649b7b2a")) 119 | Expect(env.AppID).To(Equal("abcabc123123defdef456456")) 120 | Expect(env.CFAPI).To(Equal("https://api.system_domain.com")) 121 | Expect(env.Index).To(Equal(0)) 122 | Expect(env.Name).To(Equal("styx-james")) 123 | Expect(env.SpaceName).To(Equal("jdk")) 124 | Expect(env.SpaceID).To(Equal("3e0c28c5-6d9c-436b-b9ee-1f4326e54d05")) 125 | Expect(env.Host).To(Equal("0.0.0.0")) 126 | Expect(env.Port).To(Equal(61857)) 127 | Expect(env.Version).To(Equal("c1063c1c-40b9-434e-a797-db240b587d32")) 128 | Expect(env.Home).To(Equal("/home/vcap/app")) 129 | Expect(env.MemoryLimit).To(Equal("512m")) 130 | Expect(env.WorkingDir).To(Equal("/home/vcap")) 131 | Expect(env.TempDir).To(Equal("/home/vcap/tmp")) 132 | Expect(env.User).To(Equal("vcap")) 133 | Expect(env.Limits.Disk).To(Equal(1024)) 134 | Expect(env.Limits.Mem).To(Equal(512)) 135 | Expect(env.Limits.FDs).To(Equal(16384)) 136 | Expect(env.ApplicationURIs[0]).To(Equal("styx-james.a1-app.cf-app.com")) 137 | Expect(len(env.Services)).To(Equal(3)) 138 | Expect(env.Services["elephantsql-dev"][0].Name).To(Equal("elephantsql-dev-c6c60")) 139 | Expect(env.Services["elephantsql-dev"][0].Label).To(Equal("elephantsql-dev")) 140 | Expect(env.Services["elephantsql-dev"][0].Tags).To(Equal([]string{"New Product", "relational", "Data Store", "postgresql"})) 141 | Expect(env.Services["elephantsql-dev"][0].Plan).To(Equal("turtle")) 142 | Expect(len(env.Services["elephantsql-dev"][0].Credentials)).To(Equal(1)) 143 | Expect(env.Services["elephantsql-dev"][0].Credentials["uri"]).To(Equal("postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd")) 144 | Expect(env.Services["sendgrid"][0].Name).To(Equal("mysendgrid")) 145 | Expect(env.Services["sendgrid"][0].Label).To(Equal("sendgrid")) 146 | Expect(env.Services["sendgrid"][0].Tags).To(Equal([]string{"smtp", "Email"})) 147 | Expect(env.Services["sendgrid"][0].Plan).To(Equal("free")) 148 | Expect(len(env.Services["sendgrid"][0].Credentials)).To(Equal(3)) 149 | Expect(env.Services["sendgrid"][0].Credentials["hostname"]).To(Equal("smtp.sendgrid.net")) 150 | Expect(env.Services["sendgrid"][0].Credentials["username"]).To(Equal("QvsXMbJ3rK")) 151 | Expect(env.Services["sendgrid"][0].Credentials["password"]).To(Equal("HCHMOYluTv")) 152 | 153 | Expect(env.Services["nfs"][0].VolumeMounts[0]["container_dir"]).To(Equal("/testpath")) 154 | 155 | name, err := env.Services.WithName("elephantsql-dev-c6c60") 156 | Expect(name.Name).To(Equal("elephantsql-dev-c6c60")) 157 | Expect(err).To(BeNil()) 158 | 159 | tag, err := env.Services.WithTag("postgresql") 160 | Expect(len(tag)).To(Equal(1)) 161 | Expect(tag[0].Tags).To(ContainElement("postgresql")) 162 | Expect(err).To(BeNil()) 163 | 164 | label, err := env.Services.WithLabel("elephantsql-dev") 165 | Expect(len(label)).To(Equal(1)) 166 | Expect(label[0].Label).To(Equal("elephantsql-dev")) 167 | Expect(err).To(BeNil()) 168 | 169 | names, err := env.Services.WithNameUsingPattern(".*(sql|mysend).*") 170 | Expect(len(names)).To(Equal(2)) 171 | Expect(err).To(BeNil()) 172 | isValidNames := true 173 | for _, service := range names { 174 | if service.Name != "mysendgrid" && service.Name != "elephantsql-dev-c6c60" { 175 | isValidNames = false 176 | } 177 | } 178 | Expect(isValidNames).To(BeTrue(), "Not valid names when finding by regex") 179 | 180 | tags, err := env.Services.WithTagUsingPattern(".*sql.*") 181 | Expect(len(tags)).To(Equal(1)) 182 | Expect(err).To(BeNil()) 183 | isValidTags := true 184 | for _, service := range tags { 185 | if service.Name != "elephantsql-dev-c6c60" { 186 | isValidTags = false 187 | } 188 | } 189 | Expect(isValidTags).To(BeTrue(), "Not valid tags when finding by regex") 190 | 191 | }) 192 | 193 | it("should prefer the PORT environment variable over VCAP_APPLICATION.PORT", func() { 194 | validEnv = append(validEnv, "PORT=12345") 195 | testEnv := cfenv.Env(validEnv) 196 | env, err := cfenv.New(testEnv) 197 | Expect(err).To(BeNil()) 198 | Expect(env).NotTo(BeNil()) 199 | Expect(env.Port).To(Equal(12345)) 200 | }) 201 | }) 202 | 203 | when("without a space name and id", func() { 204 | it("should deserialize correctly", func() { 205 | testEnv := cfenv.Env(validEnvWithoutSpaceIDAndName) 206 | env, err := cfenv.New(testEnv) 207 | Expect(err).To(BeNil()) 208 | Expect(env).NotTo(BeNil()) 209 | Expect(env.SpaceID).To(BeEmpty()) 210 | Expect(env.SpaceName).To(BeEmpty()) 211 | }) 212 | }) 213 | 214 | when("with valid environment with a service with credentials that are an array", func() { 215 | it("should deserialize correctly", func() { 216 | testEnv := cfenv.Env(envWithArrayCredentials) 217 | env, err := cfenv.New(testEnv) 218 | Expect(err).To(BeNil()) 219 | Expect(env).NotTo(BeNil()) 220 | 221 | credential := map[string]interface{}{} 222 | mapstructure.Decode(env.Services["p-kafka"][0].Credentials["kafka"], &credential) 223 | 224 | Expect(len(env.Services["p-kafka"][0].Credentials)).To(Equal(1)) 225 | Expect(credential["node_ips"]).To(Equal([]interface{}{"10.244.9.2", "10.244.9.6", "10.244.9.10"})) 226 | Expect(credential["port"]).To(Equal(float64(9092))) 227 | }) 228 | }) 229 | 230 | when("with valid environment with a service with credentials with a port that is an int", func() { 231 | it("should to deserialize correctly", func() { 232 | testEnv := cfenv.Env(envWithIntCredentials) 233 | env, err := cfenv.New(testEnv) 234 | Expect(err).To(BeNil()) 235 | Expect(env).NotTo(BeNil()) 236 | 237 | Expect(env.ID).To(Equal("451f045fd16427bb99c895a2649b7b2a")) 238 | Expect(env.Index).To(Equal(0)) 239 | Expect(env.Name).To(Equal("styx-james")) 240 | Expect(env.Host).To(Equal("0.0.0.0")) 241 | Expect(env.Port).To(Equal(61857)) 242 | Expect(env.Version).To(Equal("c1063c1c-40b9-434e-a797-db240b587d32")) 243 | Expect(env.Home).To(Equal("/home/vcap/app")) 244 | Expect(env.MemoryLimit).To(Equal("512m")) 245 | Expect(env.WorkingDir).To(Equal("/home/vcap")) 246 | Expect(env.TempDir).To(Equal("/home/vcap/tmp")) 247 | Expect(env.User).To(Equal("vcap")) 248 | Expect(env.ApplicationURIs[0]).To(Equal("styx-james.a1-app.cf-app.com")) 249 | Expect(len(env.Services)).To(Equal(3)) 250 | 251 | Expect(env.Services["elephantsql-dev"][0].Name).To(Equal("elephantsql-dev-c6c60")) 252 | Expect(env.Services["elephantsql-dev"][0].Label).To(Equal("elephantsql-dev")) 253 | Expect(env.Services["elephantsql-dev"][0].Tags).To(Equal([]string{"New Product", "relational", "Data Store", "postgresql"})) 254 | Expect(env.Services["elephantsql-dev"][0].Plan).To(Equal("turtle")) 255 | Expect(len(env.Services["elephantsql-dev"][0].Credentials)).To(Equal(1)) 256 | Expect(env.Services["elephantsql-dev"][0].Credentials["uri"]).To(Equal("postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd")) 257 | 258 | Expect(env.Services["cloudantNoSQLDB"][0].Name).To(Equal("my_cloudant")) 259 | Expect(env.Services["cloudantNoSQLDB"][0].Label).To(Equal("cloudantNoSQLDB")) 260 | Expect(env.Services["cloudantNoSQLDB"][0].Plan).To(Equal("Shared")) 261 | Expect(len(env.Services["cloudantNoSQLDB"][0].Credentials)).To(Equal(5)) 262 | Expect(env.Services["cloudantNoSQLDB"][0].Credentials["port"]).To(Equal(float64(443))) 263 | 264 | Expect(env.Services["sendgrid"][0].Name).To(Equal("mysendgrid")) 265 | Expect(env.Services["sendgrid"][0].Label).To(Equal("sendgrid")) 266 | Expect(env.Services["sendgrid"][0].Tags).To(Equal([]string{"smtp", "Email"})) 267 | Expect(env.Services["sendgrid"][0].Plan).To(Equal("free")) 268 | Expect(len(env.Services["sendgrid"][0].Credentials)).To(Equal(3)) 269 | Expect(env.Services["sendgrid"][0].Credentials["hostname"]).To(Equal("smtp.sendgrid.net")) 270 | Expect(env.Services["sendgrid"][0].Credentials["username"]).To(Equal("QvsXMbJ3rK")) 271 | Expect(env.Services["sendgrid"][0].Credentials["password"]).To(Equal("HCHMOYluTv")) 272 | 273 | name, err := env.Services.WithName("elephantsql-dev-c6c60") 274 | Expect(name.Name).To(Equal("elephantsql-dev-c6c60")) 275 | Expect(err).To(BeNil()) 276 | 277 | tag, err := env.Services.WithTag("postgresql") 278 | Expect(len(tag)).To(Equal(1)) 279 | Expect(tag[0].Tags).To(ContainElement("postgresql")) 280 | Expect(err).To(BeNil()) 281 | 282 | label, err := env.Services.WithLabel("elephantsql-dev") 283 | Expect(len(label)).To(Equal(1)) 284 | Expect(label[0].Label).To(Equal("elephantsql-dev")) 285 | Expect(err).To(BeNil()) 286 | 287 | names, err := env.Services.WithNameUsingPattern(".*(sql|my_cloud).*") 288 | Expect(len(names)).To(Equal(2)) 289 | Expect(err).To(BeNil()) 290 | isValidNames := true 291 | for _, service := range names { 292 | if service.Name != "my_cloudant" && service.Name != "elephantsql-dev-c6c60" { 293 | isValidNames = false 294 | } 295 | } 296 | Expect(isValidNames).To(BeTrue(), "Not valid names when finding by regex") 297 | 298 | tags, err := env.Services.WithTagUsingPattern(".*s.*") 299 | Expect(len(tags)).To(Equal(2)) 300 | Expect(err).To(BeNil()) 301 | isValidTags := true 302 | for _, service := range tags { 303 | if service.Name != "mysendgrid" && service.Name != "elephantsql-dev-c6c60" { 304 | isValidTags = false 305 | } 306 | } 307 | Expect(isValidTags).To(BeTrue(), "Not valid tags when finding by regex") 308 | }) 309 | }) 310 | 311 | when("with invalid environment", func() { 312 | it("should deserialize correctly, with missing values", func() { 313 | testEnv := cfenv.Env(invalidEnv) 314 | env, err := cfenv.New(testEnv) 315 | Expect(err).To(BeNil()) 316 | Expect(env).NotTo(BeNil()) 317 | 318 | Expect(env.ID).To(Equal("")) 319 | Expect(env.Index).To(Equal(0)) 320 | Expect(env.Name).To(Equal("styx-james")) 321 | Expect(env.Host).To(Equal("0.0.0.0")) 322 | Expect(env.Port).To(Equal(61857)) 323 | Expect(env.Version).To(Equal("c1063c1c-40b9-434e-a797-db240b587d32")) 324 | Expect(env.Home).To(Equal("/home/vcap/app")) 325 | Expect(env.MemoryLimit).To(Equal("")) 326 | Expect(env.WorkingDir).To(Equal("/home/vcap")) 327 | Expect(env.TempDir).To(Equal("/home/vcap/tmp")) 328 | Expect(env.User).To(Equal("vcap")) 329 | Expect(env.ApplicationURIs[0]).To(Equal("styx-james.a1-app.cf-app.com")) 330 | Expect(len(env.Services)).To(Equal(2)) 331 | Expect(len(env.Services)).To(Equal(2)) 332 | Expect(env.Services["elephantsql-dev"][0].Name).To(Equal("")) 333 | Expect(env.Services["elephantsql-dev"][0].Label).To(Equal("elephantsql-dev")) 334 | Expect(env.Services["elephantsql-dev"][0].Plan).To(Equal("turtle")) 335 | Expect(len(env.Services["elephantsql-dev"][0].Credentials)).To(Equal(1)) 336 | Expect(env.Services["elephantsql-dev"][0].Credentials["uri"]).To(Equal("postgres://seilbmbd:PHxTPJSbkcDakfK4cYwXHiIX9Q8p5Bxn@babar.elephantsql.com:5432/seilbmbd")) 337 | 338 | Expect(env.Services["sendgrid"][0].Name).To(Equal("mysendgrid")) 339 | Expect(env.Services["sendgrid"][0].Label).To(Equal("sendgrid")) 340 | Expect(env.Services["sendgrid"][0].Plan).To(Equal("free")) 341 | Expect(len(env.Services["sendgrid"][0].Credentials)).To(Equal(3)) 342 | Expect(env.Services["sendgrid"][0].Credentials["hostname"]).To(Equal("smtp.sendgrid.net")) 343 | Expect(env.Services["sendgrid"][0].Credentials["username"]).To(Equal("QvsXMbJ3rK")) 344 | Expect(env.Services["sendgrid"][0].Credentials["password"]).To(Equal("HCHMOYluTv")) 345 | }) 346 | }) 347 | }) 348 | 349 | when("CredentialString", func() { 350 | var service = cfenv.Service{ 351 | Credentials: map[string]interface{}{ 352 | "string": "stringy-credential", 353 | "int": 42, 354 | "nested": map[string]string{ 355 | "key": "value", 356 | }, 357 | }, 358 | } 359 | 360 | it("returns the requested credential as a string when the credential is a string", func() { 361 | result, ok := service.CredentialString("string") 362 | Expect(ok).To(BeTrue()) 363 | Expect(result).To(Equal("stringy-credential")) 364 | }) 365 | 366 | it("returns false when the credential is not a string", func() { 367 | _, ok := service.CredentialString("int") 368 | Expect(ok).To(BeFalse()) 369 | }) 370 | 371 | it("returns false when the credential is a nested thing", func() { 372 | _, ok := service.CredentialString("nested") 373 | Expect(ok).To(BeFalse()) 374 | }) 375 | }) 376 | } 377 | -------------------------------------------------------------------------------- /environment.go: -------------------------------------------------------------------------------- 1 | package cfenv 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | // CurrentEnv translates the current environment to a map[string]string. 8 | func CurrentEnv() map[string]string { 9 | return Env(os.Environ()) 10 | } 11 | 12 | // Env translates the provided environment to a map[string]string. 13 | func Env(env []string) map[string]string { 14 | vars := mapEnv(env, splitEnv) 15 | return vars 16 | } 17 | -------------------------------------------------------------------------------- /environment_test.go: -------------------------------------------------------------------------------- 1 | package cfenv_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/cloudfoundry-community/go-cfenv" 7 | . "github.com/onsi/gomega" 8 | "github.com/sclevine/spec" 9 | ) 10 | 11 | func testEnvironment(t *testing.T, when spec.G, it spec.S) { 12 | it.Before(func() { 13 | RegisterTestingT(t) 14 | }) 15 | 16 | when("environment variables should be mapped with default environment", func() { 17 | it("should contain at least one mapped variable", func() { 18 | vars := cfenv.CurrentEnv() 19 | Expect(len(vars)).To(BeNumerically(">", 0), "Environment variables should exist") 20 | }) 21 | 22 | it("should split variables into keys and values", func() { 23 | vars := cfenv.CurrentEnv() 24 | valueCount := 0 25 | for k, v := range vars { 26 | // Key should never be empty 27 | Expect(k).NotTo(BeEmpty()) 28 | 29 | // Key should never have equals 30 | Expect(k).NotTo(ContainSubstring("=")) 31 | 32 | // Value may be empty, but let's track non-empty values 33 | if v != "" { 34 | valueCount++ 35 | } 36 | } 37 | 38 | // Ensure we get at least one value from the environment 39 | Expect(valueCount).To(BeNumerically(">", 0)) 40 | }) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /envmap.go: -------------------------------------------------------------------------------- 1 | package cfenv 2 | 3 | import "strings" 4 | 5 | // splitEnv splits item, a key=value string, into its key and value components. 6 | func splitEnv(item string) (key, val string) { 7 | splits := strings.Split(item, "=") 8 | key = splits[0] 9 | val = strings.Join(splits[1:], "=") 10 | return 11 | } 12 | 13 | func mapEnv(data []string, keyFunc func(item string) (key, val string)) map[string]string { 14 | items := make(map[string]string) 15 | for _, item := range data { 16 | key, val := keyFunc(item) 17 | items[key] = val 18 | } 19 | return items 20 | } 21 | -------------------------------------------------------------------------------- /envmap_test.go: -------------------------------------------------------------------------------- 1 | package cfenv 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/onsi/gomega" 7 | "github.com/sclevine/spec" 8 | ) 9 | 10 | func testEnvMap(t *testing.T, when spec.G, it spec.S) { 11 | it.Before(func() { 12 | RegisterTestingT(t) 13 | }) 14 | 15 | test := func(input string, expectedKey string, expectedValue string) { 16 | k, v := splitEnv(input) 17 | Expect(k).To(Equal(expectedKey)) 18 | Expect(v).To(Equal(expectedValue)) 19 | } 20 | 21 | when("splitting environment variables", func() { 22 | when("with empty env var", func() { 23 | it("should have empty value", func() { 24 | test("", "", "") 25 | }) 26 | }) 27 | 28 | when("with env var not split by equals", func() { 29 | it("should have empty value", func() { 30 | test("TEST", "TEST", "") 31 | }) 32 | }) 33 | 34 | when("with env var split by equals but no value", func() { 35 | it("should have empty value", func() { 36 | test("TEST=", "TEST", "") 37 | }) 38 | }) 39 | 40 | when("with env var split by equals with key and value", func() { 41 | it("should have non-empty key and value", func() { 42 | test("TEST=VAL", "TEST", "VAL") 43 | }) 44 | }) 45 | 46 | when("with env var split by equals with key and value containing equals", func() { 47 | it("should have non-empty key and value", func() { 48 | test("TEST=VAL=OTHERVAL", "TEST", "VAL=OTHERVAL") 49 | }) 50 | }) 51 | }) 52 | } 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudfoundry-community/go-cfenv 2 | 3 | go 1.11 4 | 5 | require ( 6 | github.com/joefitzgerald/rainbow-reporter v0.1.0 7 | github.com/mitchellh/mapstructure v1.1.2 8 | github.com/onsi/ginkgo v1.8.0 // indirect 9 | github.com/onsi/gomega v1.5.0 10 | github.com/sclevine/spec v1.2.0 11 | ) 12 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 2 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 3 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 4 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 5 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 6 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 7 | github.com/joefitzgerald/rainbow-reporter v0.1.0 h1:AuMG652zjdzI0YCCnXAqATtRBpGXMcAnrajcaTrSeuo= 8 | github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= 9 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 10 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 11 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 12 | github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= 13 | github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 14 | github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= 15 | github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 16 | github.com/sclevine/spec v1.2.0 h1:1Jwdf9jSfDl9NVmt8ndHqbTZ7XCCPbh1jI3hkDBHVYA= 17 | github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= 18 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 19 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 20 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 21 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 22 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 23 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 24 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 25 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 26 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 27 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 28 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 29 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 30 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 31 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 32 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 33 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 34 | -------------------------------------------------------------------------------- /service.go: -------------------------------------------------------------------------------- 1 | package cfenv 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | // Service describes a bound service. For bindable services Cloud Foundry will 10 | // add connection details to the VCAP_SERVICES environment variable when you 11 | // restart your application, after binding a service instance to your 12 | // application. 13 | // 14 | // The results are returned as a JSON document that contains an object for each 15 | // service for which one or more instances are bound to the application. The 16 | // service object contains a child object for each service instance of that 17 | // service that is bound to the application. 18 | type Service struct { 19 | Name string // name of the service 20 | Label string // label of the service 21 | Tags []string // tags for the service 22 | Plan string // plan of the service 23 | Credentials map[string]interface{} // credentials for the service 24 | VolumeMounts []map[string]string `mapstructure:"volume_mounts"` // volume mount info as provided by the nfsbroker 25 | } 26 | 27 | func (s *Service) CredentialString(key string) (string, bool) { 28 | credential, ok := s.Credentials[key].(string) 29 | return credential, ok 30 | } 31 | 32 | // Services is an association of service labels to a slice of services with that 33 | // label. 34 | type Services map[string][]Service 35 | 36 | // WithTag finds services with the specified tag. 37 | func (s *Services) WithTag(tag string) ([]Service, error) { 38 | result := []Service{} 39 | for _, services := range *s { 40 | for i := range services { 41 | service := services[i] 42 | for _, t := range service.Tags { 43 | if strings.EqualFold(tag, t) { 44 | result = append(result, service) 45 | break 46 | } 47 | } 48 | } 49 | } 50 | 51 | if len(result) > 0 { 52 | return result, nil 53 | } 54 | 55 | return nil, fmt.Errorf("no services with tag %s", tag) 56 | } 57 | 58 | // WithTag finds services with a tag pattern. 59 | func (s *Services) WithTagUsingPattern(tagPattern string) ([]Service, error) { 60 | result := []Service{} 61 | for _, services := range *s { 62 | for i := range services { 63 | service := services[i] 64 | for _, t := range service.Tags { 65 | if s.match(tagPattern, t) { 66 | result = append(result, service) 67 | break 68 | } 69 | } 70 | } 71 | } 72 | 73 | if len(result) > 0 { 74 | return result, nil 75 | } 76 | 77 | return nil, fmt.Errorf("no services with tag pattern %s", tagPattern) 78 | } 79 | 80 | // WithLabel finds the service with the specified label. 81 | func (s *Services) WithLabel(label string) ([]Service, error) { 82 | for l, services := range *s { 83 | if strings.EqualFold(label, l) { 84 | return services, nil 85 | } 86 | } 87 | 88 | return nil, fmt.Errorf("no services with label %s", label) 89 | } 90 | func (s *Services) match(matcher, content string) bool { 91 | regex, err := regexp.Compile("(?i)^" + matcher + "$") 92 | if err != nil { 93 | return false 94 | } 95 | return regex.MatchString(content) 96 | } 97 | 98 | // WithName finds the service with a name pattern. 99 | func (s *Services) WithNameUsingPattern(namePattern string) ([]Service, error) { 100 | result := []Service{} 101 | for _, services := range *s { 102 | for i := range services { 103 | service := services[i] 104 | if s.match(namePattern, service.Name) { 105 | result = append(result, service) 106 | } 107 | } 108 | } 109 | if len(result) > 0 { 110 | return result, nil 111 | } 112 | return nil, fmt.Errorf("no service with name pattern %s", namePattern) 113 | } 114 | 115 | // WithName finds the service with the specified name. 116 | func (s *Services) WithName(name string) (*Service, error) { 117 | for _, services := range *s { 118 | for i := range services { 119 | service := services[i] 120 | if strings.EqualFold(name, service.Name) { 121 | return &service, nil 122 | } 123 | } 124 | } 125 | 126 | return nil, fmt.Errorf("no service with name %s", name) 127 | } 128 | --------------------------------------------------------------------------------