├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin ├── linux64 │ └── cf-targets-plugin ├── osx │ └── cf-targets-plugin └── win64 │ └── cf-targets-plugin.exe ├── build-all.sh ├── cf_targets.go ├── cf_targets_suite_test.go ├── cf_targets_test.go ├── go.mod ├── go.sum └── repo-index.yml /.gitignore: -------------------------------------------------------------------------------- 1 | /cf-targets-plugin 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - tip 4 | 5 | -------------------------------------------------------------------------------- /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 {yyyy} {name of copyright owner} 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 | CF Targets Plugin 2 | ================= 3 | 4 | [![Build Status](https://travis-ci.org/guidowb/cf-targets-plugin.svg?branch=master)](https://travis-ci.org/guidowb/cf-targets-plugin) 5 | 6 | This plugin facilitates the use of multiple api targets with the Cloud Foundry CLI. 7 | 8 | It originated from the need for a Go play project, and the realization that I was 9 | frequently switching back and forth between development and various test environments, 10 | using tricks like 11 | 12 | ``` 13 | CF_HOME=~/cf-development cf push my-app 14 | CF_HOME=~/cf-production cf push my-app 15 | ``` 16 | 17 | This plugin makes switching a lot less painful by allowing you to save your currently 18 | configured target using a name, then switching back to it by name at any point. 19 | 20 | 21 | ## Usage 22 | 23 | Configure and save any number of named targets 24 | 25 | ``` 26 | $ cf api 27 | $ cf login 28 | ... 29 | $ cf save-target development 30 | ``` 31 | 32 | Followed by 33 | 34 | ``` 35 | $ cf api 36 | $ cf login 37 | ... 38 | $ cf save-target production 39 | ``` 40 | 41 | After saving targets, easily switch back and forth between them using: 42 | 43 | ``` 44 | $ cf set-target development 45 | $ cf target 46 | API Endpoint: 47 | ... 48 | $ cf set-target production 49 | $ cf target 50 | API Endpoint: 51 | ... 52 | ``` 53 | 54 | View saved targets using 55 | 56 | ``` 57 | $ cf targets 58 | development 59 | production (current) 60 | ``` 61 | 62 | 63 | ## Installation 64 | ##### Install from CLI 65 | ``` 66 | $ cf add-plugin-repo CF-Community https://plugins.cloudfoundry.org/ 67 | $ cf install-plugin Targets -r CF-Community 68 | ``` 69 | 70 | 71 | ##### Install from Source (need to have [Go](http://golang.org/dl/) installed) 72 | ``` 73 | $ go get github.com/cloudfoundry/cli 74 | $ go get github.com/guidowb/cf-targets-plugin 75 | $ cd $GOPATH/src/github.com/guidowb/cf-targets-plugin 76 | $ go build 77 | $ cf install-plugin cf-targets-plugin 78 | ``` 79 | 80 | ## Full Command List 81 | 82 | | command | usage | description| 83 | | :--------------- |:---------------| :------------| 84 | |`targets`| `cf targets` |list all saved targets| 85 | |`save-target`|`cf save-target [-f] []`|save the current target for later use| 86 | |`set-target`|`cf set-target [-f] `|restore a previously saved target| 87 | |`delete-target`|`cf delete-target `|delete a previously saved target| 88 | -------------------------------------------------------------------------------- /bin/linux64/cf-targets-plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidowb/cf-targets-plugin/5d0d97277aaf59ce5ef535f5a464c240eb5f0bb9/bin/linux64/cf-targets-plugin -------------------------------------------------------------------------------- /bin/osx/cf-targets-plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidowb/cf-targets-plugin/5d0d97277aaf59ce5ef535f5a464c240eb5f0bb9/bin/osx/cf-targets-plugin -------------------------------------------------------------------------------- /bin/win64/cf-targets-plugin.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/guidowb/cf-targets-plugin/5d0d97277aaf59ce5ef535f5a464c240eb5f0bb9/bin/win64/cf-targets-plugin.exe -------------------------------------------------------------------------------- /build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [[ "$1" = "release" ]] ; then 4 | TAG="$2" 5 | : ${TAG:?"Usage: build_all.sh [release] [TAG]"} 6 | 7 | git tag | grep $TAG > /dev/null 2>&1 8 | if [ $? -eq 0 ] ; then 9 | echo "$TAG exists, remove it or increment" 10 | exit 1 11 | else 12 | MAJOR=`echo $TAG | sed 's/^v//' | awk 'BEGIN {FS = "." } ; { printf $1;}'` 13 | MINOR=`echo $TAG | sed 's/^v//' | awk 'BEGIN {FS = "." } ; { printf $2;}'` 14 | BUILD=`echo $TAG | sed 's/^v//' | awk 'BEGIN {FS = "." } ; { printf $3;}'` 15 | 16 | `sed -i .bak -e "s/Major:.*/Major: $MAJOR,/" \ 17 | -e "s/Minor:.*/Minor: $MINOR,/" \ 18 | -e "s/Build:.*/Build: $BUILD,/" cf_targets.go` 19 | fi 20 | fi 21 | 22 | GOOS=linux GOARCH=amd64 go build 23 | LINUX64_SHA1=`cat cf-targets-plugin | openssl sha1` 24 | mkdir -p bin/linux64 25 | mv cf-targets-plugin bin/linux64 26 | 27 | GOOS=darwin GOARCH=amd64 go build 28 | OSX_SHA1=`cat cf-targets-plugin | openssl sha1` 29 | mkdir -p bin/osx 30 | mv cf-targets-plugin bin/osx 31 | 32 | GOOS=windows GOARCH=amd64 go build 33 | WIN64_SHA1=`cat cf-targets-plugin.exe | openssl sha1` 34 | mkdir -p bin/win64 35 | mv cf-targets-plugin.exe bin/win64 36 | 37 | cat repo-index.yml | 38 | sed "s/osx-sha1/$OSX_SHA1/" | 39 | sed "s/win64-sha1/$WIN64_SHA1/" | 40 | sed "s/linux64-sha1/$LINUX64_SHA1/" | 41 | sed "s/_TAG_/$TAG/" | 42 | cat 43 | 44 | #Final build gives developer a plugin to install 45 | go build 46 | 47 | if [[ "$1" = "release" ]] ; then 48 | git commit -am "Build version $TAG" 49 | git tag $TAG 50 | echo "Tagged release, 'git push --tags' to move it to github, and copy the output above" 51 | echo "to the cli repo you plan to deploy in" 52 | fi 53 | 54 | -------------------------------------------------------------------------------- /cf_targets.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "path/filepath" 8 | "strings" 9 | 10 | realio "io/ioutil" 11 | realos "os" 12 | 13 | "github.com/cloudfoundry/cli/cf/configuration" 14 | "github.com/cloudfoundry/cli/cf/configuration/confighelpers" 15 | "github.com/cloudfoundry/cli/cf/configuration/coreconfig" 16 | "github.com/cloudfoundry/cli/plugin" 17 | ) 18 | 19 | type TargetsPlugin struct { 20 | configPath string 21 | targetsPath string 22 | currentPath string 23 | suffix string 24 | status TargetStatus 25 | } 26 | 27 | type TargetStatus struct { 28 | currentHasName bool 29 | currentName string 30 | currentNeedsSaving bool 31 | currentNeedsUpdate bool 32 | } 33 | 34 | type RealOS struct{} 35 | type OS interface { 36 | Exit(int) 37 | Mkdir(string, realos.FileMode) 38 | Remove(string) 39 | Symlink(string, string) error 40 | ReadDir(string) ([]realos.FileInfo, error) 41 | ReadFile(string) ([]byte, error) 42 | WriteFile(string, []byte, realos.FileMode) error 43 | } 44 | 45 | func (*RealOS) Exit(code int) { realos.Exit(code) } 46 | func (*RealOS) Mkdir(path string, mode realos.FileMode) { realos.Mkdir(path, mode) } 47 | func (*RealOS) Remove(path string) { realos.Remove(path) } 48 | func (*RealOS) Symlink(target string, source string) error { return realos.Symlink(target, source) } 49 | func (*RealOS) ReadDir(path string) ([]realos.FileInfo, error) { return realio.ReadDir(path) } 50 | func (*RealOS) ReadFile(path string) ([]byte, error) { return realio.ReadFile(path) } 51 | func (*RealOS) WriteFile(path string, content []byte, mode realos.FileMode) error { 52 | return realio.WriteFile(path, content, mode) 53 | } 54 | 55 | var os OS 56 | 57 | func newTargetsPlugin() *TargetsPlugin { 58 | configPath, _ := confighelpers.DefaultFilePath() 59 | targetsPath := filepath.Join(filepath.Dir(configPath), "targets") 60 | os.Mkdir(targetsPath, 0700) 61 | return &TargetsPlugin{ 62 | configPath: configPath, 63 | targetsPath: targetsPath, 64 | currentPath: filepath.Join(targetsPath, "current"), 65 | suffix: "." + filepath.Base(configPath), 66 | } 67 | } 68 | 69 | func (c *TargetsPlugin) GetMetadata() plugin.PluginMetadata { 70 | return plugin.PluginMetadata{ 71 | Name: "cf-targets", 72 | Version: plugin.VersionType{ 73 | Major: 1, 74 | Minor: 2, 75 | Build: 0, 76 | }, 77 | Commands: []plugin.Command{ 78 | { 79 | Name: "targets", 80 | HelpText: "List available targets", 81 | UsageDetails: plugin.Usage{ 82 | Usage: "cf targets", 83 | }, 84 | }, 85 | { 86 | Name: "set-target", 87 | HelpText: "Set current target", 88 | UsageDetails: plugin.Usage{ 89 | Usage: "cf set-target [-f] NAME", 90 | Options: map[string]string{ 91 | "f": "replace the current target even if it has not been saved", 92 | }, 93 | }, 94 | }, 95 | { 96 | Name: "save-target", 97 | HelpText: "Save current target", 98 | UsageDetails: plugin.Usage{ 99 | Usage: "cf save-target [-f] [NAME]", 100 | Options: map[string]string{ 101 | "f": "save the target even if the specified name already exists", 102 | }, 103 | }, 104 | }, 105 | { 106 | Name: "delete-target", 107 | HelpText: "Delete a saved target", 108 | UsageDetails: plugin.Usage{ 109 | Usage: "cf delete-target NAME", 110 | }, 111 | }, 112 | }, 113 | } 114 | } 115 | 116 | func main() { 117 | os = &RealOS{} 118 | plugin.Start(newTargetsPlugin()) 119 | } 120 | 121 | func (c *TargetsPlugin) Run(cliConnection plugin.CliConnection, args []string) { 122 | defer func() { 123 | reason := recover() 124 | if code, ok := reason.(int); ok { 125 | os.Exit(code) 126 | } else if reason != nil { 127 | panic(reason) 128 | } 129 | }() 130 | 131 | c.checkStatus() 132 | if args[0] == "targets" { 133 | c.TargetsCommand(args) 134 | } else if args[0] == "set-target" { 135 | c.SetTargetCommand(args) 136 | } else if args[0] == "save-target" { 137 | c.SaveTargetCommand(args) 138 | } else if args[0] == "delete-target" { 139 | c.DeleteTargetCommand(args) 140 | } 141 | } 142 | 143 | func (c *TargetsPlugin) TargetsCommand(args []string) { 144 | if len(args) != 1 { 145 | c.exitWithUsage("targets") 146 | } 147 | targets := c.getTargets() 148 | if len(targets) < 1 { 149 | fmt.Println("No targets have been saved yet. To save the current target, use:") 150 | fmt.Println(" cf save-target NAME") 151 | } else { 152 | for _, target := range targets { 153 | var qualifier string 154 | if c.isCurrent(target) { 155 | qualifier = "(current" 156 | if c.status.currentNeedsSaving { 157 | qualifier += ", modified" 158 | } else if c.status.currentNeedsUpdate { 159 | qualifier += "*" 160 | } 161 | qualifier += ")" 162 | } 163 | fmt.Println(target, qualifier) 164 | } 165 | } 166 | } 167 | 168 | func (c *TargetsPlugin) SetTargetCommand(args []string) { 169 | flagSet := flag.NewFlagSet("set-target", flag.ContinueOnError) 170 | force := flagSet.Bool("f", false, "force") 171 | err := flagSet.Parse(args[1:]) 172 | if err != nil || len(flagSet.Args()) != 1 { 173 | c.exitWithUsage("set-target") 174 | } 175 | targetName := flagSet.Arg(0) 176 | targetPath := c.targetPath(targetName) 177 | if !c.targetExists(targetPath) { 178 | fmt.Println("Target", targetName, "does not exist.") 179 | panic(1) 180 | } 181 | if *force || !c.status.currentNeedsSaving { 182 | c.copyContents(targetPath, c.configPath) 183 | c.linkCurrent(targetPath) 184 | } else { 185 | fmt.Println("Your current target has not been saved. Use save-target first, or use -f to discard your changes.") 186 | panic(1) 187 | } 188 | fmt.Println("Set target to", targetName) 189 | } 190 | 191 | func (c *TargetsPlugin) SaveTargetCommand(args []string) { 192 | flagSet := flag.NewFlagSet("save-target", flag.ContinueOnError) 193 | force := flagSet.Bool("f", false, "force") 194 | err := flagSet.Parse(args[1:]) 195 | if err != nil || len(flagSet.Args()) > 1 { 196 | c.exitWithUsage("save-target") 197 | } 198 | if len(flagSet.Args()) < 1 { 199 | c.SaveCurrentTargetCommand(*force) 200 | } else { 201 | c.SaveNamedTargetCommand(flagSet.Arg(0), *force) 202 | } 203 | } 204 | 205 | func (c *TargetsPlugin) SaveNamedTargetCommand(targetName string, force bool) { 206 | targetPath := c.targetPath(targetName) 207 | if force || !c.targetExists(targetPath) { 208 | c.copyContents(c.configPath, targetPath) 209 | c.linkCurrent(targetPath) 210 | } else { 211 | fmt.Println("Target", targetName, "already exists. Use -f to overwrite it.") 212 | panic(1) 213 | } 214 | fmt.Println("Saved current target as", targetName) 215 | } 216 | 217 | func (c *TargetsPlugin) SaveCurrentTargetCommand(force bool) { 218 | if !c.status.currentHasName { 219 | fmt.Println("Current target has not been previously saved. Please provide a name.") 220 | panic(1) 221 | } 222 | targetName := c.status.currentName 223 | targetPath := c.targetPath(targetName) 224 | if c.status.currentNeedsSaving && !force { 225 | fmt.Println("You've made substantial changes to the current target.") 226 | fmt.Println("Use -f if you intend to overwrite the target named", targetName, "or provide an alternate name") 227 | panic(1) 228 | } 229 | c.copyContents(c.configPath, targetPath) 230 | fmt.Println("Saved current target as", targetName) 231 | } 232 | 233 | func (c *TargetsPlugin) DeleteTargetCommand(args []string) { 234 | if len(args) != 2 { 235 | c.exitWithUsage("delete-target") 236 | } 237 | targetName := args[1] 238 | targetPath := c.targetPath(targetName) 239 | if !c.targetExists(targetPath) { 240 | fmt.Println("Target", targetName, "does not exist") 241 | panic(1) 242 | } 243 | os.Remove(targetPath) 244 | if c.isCurrent(targetName) { 245 | os.Remove(c.currentPath) 246 | } 247 | fmt.Println("Deleted target", targetName) 248 | } 249 | 250 | func (c *TargetsPlugin) getTargets() []string { 251 | var targets []string 252 | files, _ := os.ReadDir(c.targetsPath) 253 | for _, file := range files { 254 | filename := file.Name() 255 | if strings.HasSuffix(filename, c.suffix) { 256 | targets = append(targets, strings.TrimSuffix(filename, c.suffix)) 257 | } 258 | } 259 | return targets 260 | } 261 | 262 | func (c *TargetsPlugin) targetExists(targetPath string) bool { 263 | target := configuration.NewDiskPersistor(targetPath) 264 | return target.Exists() 265 | } 266 | 267 | func (c *TargetsPlugin) checkStatus() { 268 | currentConfig := configuration.NewDiskPersistor(c.configPath) 269 | currentTarget := configuration.NewDiskPersistor(c.currentPath) 270 | if !currentTarget.Exists() { 271 | os.Remove(c.currentPath) 272 | c.status = TargetStatus{false, "", true, false} 273 | return 274 | } 275 | 276 | name := c.getCurrent() 277 | 278 | configData := coreconfig.NewData() 279 | targetData := coreconfig.NewData() 280 | 281 | err := currentConfig.Load(configData) 282 | c.checkError(err) 283 | err = currentTarget.Load(targetData) 284 | c.checkError(err) 285 | 286 | // Ignore the access-token field, as it changes frequently 287 | needsUpdate := targetData.AccessToken != configData.AccessToken 288 | targetData.AccessToken = configData.AccessToken 289 | 290 | currentContent, err := configData.JSONMarshalV3() 291 | c.checkError(err) 292 | savedContent, err := targetData.JSONMarshalV3() 293 | c.checkError(err) 294 | c.status = TargetStatus{true, name, !bytes.Equal(currentContent, savedContent), needsUpdate} 295 | } 296 | 297 | func (c *TargetsPlugin) copyContents(sourcePath, targetPath string) { 298 | content, err := os.ReadFile(sourcePath) 299 | c.checkError(err) 300 | err = os.WriteFile(targetPath, content, 0600) 301 | c.checkError(err) 302 | } 303 | 304 | func (c *TargetsPlugin) linkCurrent(targetPath string) { 305 | os.Remove(c.currentPath) 306 | err := os.Symlink(targetPath, c.currentPath) 307 | c.checkError(err) 308 | } 309 | 310 | func (c *TargetsPlugin) targetPath(targetName string) string { 311 | return filepath.Join(c.targetsPath, targetName+c.suffix) 312 | } 313 | 314 | func (c *TargetsPlugin) checkError(err error) { 315 | if err != nil { 316 | fmt.Println("Error:", err) 317 | panic(1) 318 | } 319 | } 320 | 321 | func (c *TargetsPlugin) exitWithUsage(command string) { 322 | metadata := c.GetMetadata() 323 | for _, candidate := range metadata.Commands { 324 | if candidate.Name == command { 325 | fmt.Println("Usage: " + candidate.UsageDetails.Usage) 326 | panic(1) 327 | } 328 | } 329 | } 330 | 331 | func (c *TargetsPlugin) getCurrent() string { 332 | targetPath, err := filepath.EvalSymlinks(c.currentPath) 333 | c.checkError(err) 334 | return strings.TrimSuffix(filepath.Base(targetPath), c.suffix) 335 | } 336 | 337 | func (c *TargetsPlugin) isCurrent(target string) bool { 338 | return c.status.currentHasName && c.status.currentName == target 339 | } 340 | -------------------------------------------------------------------------------- /cf_targets_suite_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | 7 | "testing" 8 | ) 9 | 10 | func TestTargets(t *testing.T) { 11 | RegisterFailHandler(Fail) 12 | RunSpecs(t, "Targets Suite") 13 | } 14 | -------------------------------------------------------------------------------- /cf_targets_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | realos "os" 5 | 6 | fakes "github.com/cloudfoundry/cli/plugin/pluginfakes" 7 | . "github.com/cloudfoundry/cli/cf/util/testhelpers/io" 8 | . "github.com/cloudfoundry/cli/cf/util/testhelpers/matchers" 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | ) 12 | 13 | type FakeOS struct { 14 | exitCalled int 15 | exitCalledWithCode int 16 | mkdirCalled int 17 | mkdirCalledWithPath string 18 | mkdirCalledWithMode realos.FileMode 19 | removeCalled int 20 | removeCalledWithPath string 21 | symlinkCalled int 22 | symlinkCalledWithTarget string 23 | symlinkCalledWithSource string 24 | readdirCalled int 25 | readdirCalledWithPath string 26 | readfileCalled int 27 | readfileCalledWithPath string 28 | writefileCalled int 29 | writefileCalledWithPath string 30 | writefileCalledWithContent []byte 31 | writefileCalledWithMode realos.FileMode 32 | readdirShouldReturn []realos.FileInfo 33 | readfileShouldReturn []byte 34 | } 35 | 36 | func (os *FakeOS) Exit(code int) { 37 | os.exitCalled++ 38 | os.exitCalledWithCode = code 39 | } 40 | 41 | func (os *FakeOS) Mkdir(path string, mode realos.FileMode) { 42 | os.mkdirCalled++ 43 | os.mkdirCalledWithPath = path 44 | os.mkdirCalledWithMode = mode 45 | } 46 | 47 | func (os *FakeOS) Remove(path string) { 48 | os.removeCalled++ 49 | os.removeCalledWithPath = path 50 | } 51 | 52 | func (os *FakeOS) Symlink(target string, source string) error { 53 | os.symlinkCalled++ 54 | os.symlinkCalledWithTarget = target 55 | os.symlinkCalledWithSource = source 56 | return nil 57 | } 58 | 59 | func (os *FakeOS) ReadDir(path string) ([]realos.FileInfo, error) { 60 | os.readdirCalled++ 61 | os.readdirCalledWithPath = path 62 | return os.readdirShouldReturn, nil 63 | } 64 | 65 | func (os *FakeOS) ReadFile(path string) ([]byte, error) { 66 | os.readfileCalled++ 67 | os.readfileCalledWithPath = path 68 | return os.readfileShouldReturn, nil 69 | } 70 | 71 | func (os *FakeOS) WriteFile(path string, content []byte, mode realos.FileMode) error { 72 | os.writefileCalled++ 73 | os.writefileCalledWithPath = path 74 | os.writefileCalledWithContent = content 75 | os.writefileCalledWithMode = mode 76 | return nil 77 | } 78 | 79 | var _ = Describe("TargetsPlugin", func() { 80 | 81 | var fakeCliConnection *fakes.FakeCliConnection 82 | var targetsPlugin *TargetsPlugin 83 | var fakeOS FakeOS 84 | 85 | BeforeEach(func() { 86 | fakeOS = FakeOS{} 87 | os = &fakeOS 88 | fakeCliConnection = &fakes.FakeCliConnection{} 89 | targetsPlugin = newTargetsPlugin() 90 | }) 91 | 92 | Describe("Command Syntax", func() { 93 | It("displays usage when targets called with too many arguments", func() { 94 | output := CaptureOutput(func() { 95 | targetsPlugin.Run(fakeCliConnection, []string{"targets", "blah"}) 96 | }) 97 | Expect(fakeOS.exitCalled).To(Equal(1)) 98 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 99 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "targets"})) 100 | }) 101 | 102 | It("displays usage when set-target called with too many arguments", func() { 103 | output := CaptureOutput(func() { 104 | targetsPlugin.Run(fakeCliConnection, []string{"set-target", "blah", "blah"}) 105 | }) 106 | Expect(fakeOS.exitCalled).To(Equal(1)) 107 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 108 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "set-target", "[-f]", "NAME"})) 109 | }) 110 | 111 | It("displays usage when set-target called with too few arguments", func() { 112 | output := CaptureOutput(func() { 113 | targetsPlugin.Run(fakeCliConnection, []string{"set-target"}) 114 | }) 115 | Expect(fakeOS.exitCalled).To(Equal(1)) 116 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 117 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "set-target", "[-f]", "NAME"})) 118 | }) 119 | 120 | It("displays usage when set-target called with unsupported option", func() { 121 | output := CaptureOutput(func() { 122 | targetsPlugin.Run(fakeCliConnection, []string{"set-target", "blah", "-k"}) 123 | }) 124 | Expect(fakeOS.exitCalled).To(Equal(1)) 125 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 126 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "set-target", "[-f]", "NAME"})) 127 | }) 128 | 129 | It("displays usage when save-target called with too many arguments", func() { 130 | output := CaptureOutput(func() { 131 | targetsPlugin.Run(fakeCliConnection, []string{"save-target", "blah", "blah"}) 132 | }) 133 | Expect(fakeOS.exitCalled).To(Equal(1)) 134 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 135 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "save-target", "[-f]", "[NAME]"})) 136 | }) 137 | 138 | It("displays usage when save-target called with unsupported option", func() { 139 | output := CaptureOutput(func() { 140 | targetsPlugin.Run(fakeCliConnection, []string{"save-target", "blah", "-k"}) 141 | }) 142 | Expect(fakeOS.exitCalled).To(Equal(1)) 143 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 144 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "save-target", "[-f]", "[NAME]"})) 145 | }) 146 | 147 | It("displays usage when delete-target called with too few arguments", func() { 148 | output := CaptureOutput(func() { 149 | targetsPlugin.Run(fakeCliConnection, []string{"delete-target"}) 150 | }) 151 | Expect(fakeOS.exitCalled).To(Equal(1)) 152 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 153 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "delete-target", "NAME"})) 154 | }) 155 | 156 | It("displays usage when delete-target called with too many arguments", func() { 157 | output := CaptureOutput(func() { 158 | targetsPlugin.Run(fakeCliConnection, []string{"delete-target", "blah", "blah"}) 159 | }) 160 | Expect(fakeOS.exitCalled).To(Equal(1)) 161 | Expect(fakeOS.exitCalledWithCode).To(Equal(1)) 162 | Expect(output).To(ContainSubstrings([]string{"Usage:", "cf", "delete-target", "NAME"})) 163 | }) 164 | 165 | It("displays proper first time message", func() { 166 | output := CaptureOutput(func() { 167 | targetsPlugin.Run(fakeCliConnection, []string{"targets"}) 168 | }) 169 | Expect(fakeOS.exitCalled).To(Equal(0)) 170 | Expect(output).To(ContainSubstrings([]string{"No targets have been saved"})) 171 | Expect(output).To(ContainSubstrings([]string{"cf", "save-target", "NAME"})) 172 | }) 173 | }) 174 | 175 | Describe("Configuration File Manipulation", func() { 176 | 177 | It("creates the proper target directory", func() { 178 | Expect(fakeOS.mkdirCalled).To(Equal(1)) 179 | Expect(fakeOS.mkdirCalledWithPath).To(HaveSuffix("/.cf/targets")) 180 | }) 181 | 182 | It("properly saves first target", func() { 183 | targetsPlugin.Run(fakeCliConnection, []string{"save-target", "first"}) 184 | Expect(fakeOS.exitCalled).To(Equal(0)) 185 | Expect(fakeOS.writefileCalled).To(Equal(1)) 186 | Expect(fakeOS.writefileCalledWithPath).To(HaveSuffix("/.cf/targets/first.config.json")) 187 | Expect(fakeOS.symlinkCalled).To(Equal(1)) 188 | Expect(fakeOS.symlinkCalledWithSource).To(HaveSuffix("/.cf/targets/current")) 189 | Expect(fakeOS.symlinkCalledWithTarget).To(HaveSuffix("/.cf/targets/first.config.json")) 190 | }) 191 | 192 | It("properly saves second target", func() { 193 | targetsPlugin.Run(fakeCliConnection, []string{"save-target", "first"}) 194 | targetsPlugin.Run(fakeCliConnection, []string{"save-target", "second"}) 195 | Expect(fakeOS.exitCalled).To(Equal(0)) 196 | Expect(fakeOS.writefileCalled).To(Equal(2)) 197 | Expect(fakeOS.writefileCalledWithPath).To(HaveSuffix("/.cf/targets/second.config.json")) 198 | Expect(fakeOS.removeCalledWithPath).To(HaveSuffix("/.cf/targets/current")) 199 | Expect(fakeOS.symlinkCalled).To(Equal(2)) 200 | Expect(fakeOS.symlinkCalledWithSource).To(HaveSuffix("/.cf/targets/current")) 201 | Expect(fakeOS.symlinkCalledWithTarget).To(HaveSuffix("/.cf/targets/second.config.json")) 202 | }) 203 | }) 204 | }) 205 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/guidowb/cf-targets-plugin 2 | 3 | go 1.12 4 | 5 | require ( 6 | code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6 // indirect 7 | code.cloudfoundry.org/cli v6.46.0+incompatible // indirect 8 | code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f 9 | github.com/SermoDigital/jose v0.0.0-20161205225155-2bd9b81ac51d 10 | github.com/blang/semver v3.1.0+incompatible 11 | github.com/bmatcuk/doublestar v1.1.5 // indirect 12 | github.com/bmizerany/pat v0.0.0-20160217103242-c068ca2f0aac 13 | github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 // indirect 14 | github.com/cloudfoundry-incubator/cli-plugin-repo v0.0.0-20170127230318-62545a44009b 15 | github.com/cloudfoundry/bosh-cli v6.0.0+incompatible // indirect 16 | github.com/cloudfoundry/bosh-utils v0.0.0-20190813211102-4d0b7c5acdfd // indirect 17 | github.com/cloudfoundry/cli v6.46.0+incompatible 18 | github.com/cloudfoundry/cli-plugin-repo v0.0.0-20190808155747-f1af6115b6ff // indirect 19 | github.com/cloudfoundry/go-ccapi v0.0.0-20170111115350-71998966d470 20 | github.com/cloudfoundry/noaa v0.0.0-20170131060151-9e087230f2fe 21 | github.com/cloudfoundry/sonde-go v0.0.0-20170118225207-78019103037a 22 | github.com/cppforlife/go-patch v0.2.0 // indirect 23 | github.com/cyphar/filepath-securejoin v0.2.2 // indirect 24 | github.com/fatih/color v0.0.0-20150823214434-76d423163af7 25 | github.com/gogo/protobuf v0.0.0-20170125171659-fb8a359905af 26 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 27 | github.com/gorilla/websocket v0.0.0-20151216051058-3986be78bf85 28 | github.com/lunixbochs/vtclean v1.0.0 // indirect 29 | github.com/mattn/go-colorable v0.0.7 30 | github.com/mattn/go-isatty v0.0.0-20150814002629-7fcbc72f853b 31 | github.com/mattn/go-runewidth v0.0.4 // indirect 32 | github.com/nicksnyder/go-i18n v0.0.0-20170120160056-64786dc4f56b 33 | github.com/onsi/ginkgo v0.0.0-20170126062008-bb93381d543b 34 | github.com/onsi/gomega v0.0.0-20160705151310-82a02eccf12c 35 | github.com/pkg/errors v0.8.1 // indirect 36 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 // indirect 37 | github.com/sirupsen/logrus v1.4.2 // indirect 38 | github.com/tedsuo/rata v1.0.0 39 | github.com/vito/go-interact v1.0.0 // indirect 40 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 41 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 42 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 43 | golang.org/x/text v0.3.2 // indirect 44 | gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect 45 | gopkg.in/yaml.v2 v2.0.0-20151201162745-f7716cbe52ba 46 | ) 47 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6 h1:tW+ztA4A9UT9xnco5wUjW1oNi35k22eUEn9tNpPYVwE= 2 | code.cloudfoundry.org/bytefmt v0.0.0-20190710193110-1eb035ffe2b6/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= 3 | code.cloudfoundry.org/cli v6.46.0+incompatible h1:zTiCQRgRENIbZmivFlI3HIqBsR3ZM99Jrp98L1btrO0= 4 | code.cloudfoundry.org/cli v6.46.0+incompatible/go.mod h1:e4d+EpbwevNhyTZKybrLlyTvpH+W22vMsmdmcTxs/Fo= 5 | code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f h1:UrKzEwTgeiff9vxdrfdqxibzpWjxLnuXDI5m6z3GJAk= 6 | code.cloudfoundry.org/gofileutils v0.0.0-20170111115228-4d0c80011a0f/go.mod h1:sk5LnIjB/nIEU7yP5sDQExVm62wu0pBh3yrElngUisI= 7 | github.com/SermoDigital/jose v0.0.0-20161205225155-2bd9b81ac51d h1:JnQDOo5mwFZCKRwIQxaT0hTK5L/eBsiC4sTH4vV3/zw= 8 | github.com/SermoDigital/jose v0.0.0-20161205225155-2bd9b81ac51d/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= 9 | github.com/blang/semver v3.1.0+incompatible h1:7hqmJYuaEK3qwVjWubYiht3j93YI0WQBuysxHIfUriU= 10 | github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= 11 | github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk= 12 | github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= 13 | github.com/bmizerany/pat v0.0.0-20160217103242-c068ca2f0aac/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c= 14 | github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1 h1:vTlpHKxJqykyKdW9bkrDJNWeKNuSIAJ0TP/K4lRsz/Q= 15 | github.com/charlievieth/fs v0.0.0-20170613215519-7dc373669fa1/go.mod h1:sAoA1zHCH4FJPE2gne5iBiiVG66U7Nyp6JqlOo+FEyg= 16 | github.com/cloudfoundry-incubator/cli-plugin-repo v0.0.0-20170127230318-62545a44009b/go.mod h1:uye/8NBs56pgj9MjPzSuCt4ndCdBjbQFPzTOaOfbEjc= 17 | github.com/cloudfoundry/bosh-cli v6.0.0+incompatible h1:qVhfYV0o9SHhdy/k/rN7CsuFX8UHz0uzL3n/FywxQwo= 18 | github.com/cloudfoundry/bosh-cli v6.0.0+incompatible/go.mod h1:rzIB+e1sn7wQL/TJ54bl/FemPKRhXby5BIMS3tLuWFM= 19 | github.com/cloudfoundry/bosh-utils v0.0.0-20190813211102-4d0b7c5acdfd h1:jWzC8mIT6Y5Czr8+RIaPEvtZQiy+h56jIYzGVSXw+oc= 20 | github.com/cloudfoundry/bosh-utils v0.0.0-20190813211102-4d0b7c5acdfd/go.mod h1:JCrKwetZGjxbfq1U139TZuXDBfdGLtjOEAfxMWKV/QM= 21 | github.com/cloudfoundry/cli v6.46.0+incompatible h1:o0P0ugLktTXV3blvtyJd5B3Qw0ReUZOjyOSVs5c8N28= 22 | github.com/cloudfoundry/cli v6.46.0+incompatible/go.mod h1:uUVSLzSuwWNhis5+tY5XRUp66kLbHhBktg8b3ZfcJHI= 23 | github.com/cloudfoundry/cli-plugin-repo v0.0.0-20190808155747-f1af6115b6ff h1:8iEnKJ5dw25YBihiMcByz233vQdbnXTkcUTOhq7T0JI= 24 | github.com/cloudfoundry/cli-plugin-repo v0.0.0-20190808155747-f1af6115b6ff/go.mod h1:yC/5vqtuzGhS40ZSeBtWvDMMrfdSeG673lF91gu5K+Y= 25 | github.com/cloudfoundry/go-ccapi v0.0.0-20170111115350-71998966d470/go.mod h1:0q249CLeqa94nd5IQ81NDDLpu4RWGMrD4jhijL1Ag4w= 26 | github.com/cloudfoundry/noaa v0.0.0-20170131060151-9e087230f2fe h1:ttje5RcQAeOk4XfCRg12QUCTc54KZ2vRkPk8sfPUbJA= 27 | github.com/cloudfoundry/noaa v0.0.0-20170131060151-9e087230f2fe/go.mod h1:5LmacnptvxzrTvMfL9+EJhgkUfIgcwI61BVSTh47ECo= 28 | github.com/cloudfoundry/sonde-go v0.0.0-20170118225207-78019103037a h1:2zYENVlDYl+nq+0vNNQwYaJIARDPQmKh2nwGhoPpP9Y= 29 | github.com/cloudfoundry/sonde-go v0.0.0-20170118225207-78019103037a/go.mod h1:GS0pCHd7onIsewbw8Ue9qa9pZPv2V88cUZDttK6KzgI= 30 | github.com/cppforlife/go-patch v0.2.0 h1:Y14MnCQjDlbw7WXT4k+u6DPAA9XnygN4BfrSpI/19RU= 31 | github.com/cppforlife/go-patch v0.2.0/go.mod h1:67a7aIi94FHDZdoeGSJRRFDp66l9MhaAG1yGxpUoFD8= 32 | github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg= 33 | github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= 34 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 35 | github.com/fatih/color v0.0.0-20150823214434-76d423163af7 h1:hyLdP1Jx+bXCxaDdnlFn5f65kuAW6m5WLEkXsYoSXXk= 36 | github.com/fatih/color v0.0.0-20150823214434-76d423163af7/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 37 | github.com/gogo/protobuf v0.0.0-20170125171659-fb8a359905af h1:GBOdCZr+VWkLWRBBqK14VFUuZ0flJxLj4mWDTcqXdx8= 38 | github.com/gogo/protobuf v0.0.0-20170125171659-fb8a359905af/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 39 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 h1:zLTLjkaOFEFIOxY5BWLFLwh+cL8vOBW4XJ2aqLE/Tf0= 40 | github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 41 | github.com/gorilla/websocket v0.0.0-20151216051058-3986be78bf85 h1:SevoKCPTzyy/YSrISy+yMC7rLAkTVSS5gnBGymBikz4= 42 | github.com/gorilla/websocket v0.0.0-20151216051058-3986be78bf85/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= 43 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 44 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 45 | github.com/lunixbochs/vtclean v1.0.0 h1:xu2sLAri4lGiovBDQKxl5mrXyESr3gUr5m5SM5+LVb8= 46 | github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 47 | github.com/mattn/go-colorable v0.0.7 h1:zh4kz16dcPG+l666m12h0+dO2HGnQ1ngy7crMErE2UU= 48 | github.com/mattn/go-colorable v0.0.7/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 49 | github.com/mattn/go-isatty v0.0.0-20150814002629-7fcbc72f853b h1:KOTLb2pwNaE8UOCVz1AgsFYzcswaNroRkBDIhblvUFk= 50 | github.com/mattn/go-isatty v0.0.0-20150814002629-7fcbc72f853b/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 51 | github.com/mattn/go-runewidth v0.0.4 h1:2BvfKmzob6Bmd4YsL0zygOqfdFnK7GR4QL06Do4/p7Y= 52 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= 53 | github.com/nicksnyder/go-i18n v0.0.0-20170120160056-64786dc4f56b/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= 54 | github.com/onsi/ginkgo v0.0.0-20170126062008-bb93381d543b h1:C6YSkN2aG2o72zzLoJByHJi0/TJKJPCKSAwtc3RCM9g= 55 | github.com/onsi/ginkgo v0.0.0-20170126062008-bb93381d543b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 56 | github.com/onsi/gomega v0.0.0-20160705151310-82a02eccf12c h1:RKhgQGPHFOBrY3bJUPSEju2mbId4ZOk1WhVOhzVlGXc= 57 | github.com/onsi/gomega v0.0.0-20160705151310-82a02eccf12c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 58 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 59 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 60 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 61 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo= 62 | github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg= 63 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 64 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 65 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 66 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 67 | github.com/tedsuo/rata v1.0.0/go.mod h1:X47ELzhOoLbfFIY0Cql9P6yo3Cdwf2CMX3FVZxRzJPc= 68 | github.com/vito/go-interact v1.0.0 h1:niLW3NjGoMWOayoR6iQ8AxWVM1Q4rR8VGZ1mt6uK3BM= 69 | github.com/vito/go-interact v1.0.0/go.mod h1:W1mz+UVUZScRM3eUjQhEQiLDnQ+yLnXkB2rjBfGPrXg= 70 | golang.org/x/crypto v0.0.0-20170126200616-854ae91cdcbf h1:9gWAbeIsvnXInTSMRl6+MG5RgK+gObAHAjp/dvUNDJY= 71 | golang.org/x/crypto v0.0.0-20170126200616-854ae91cdcbf/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 72 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 73 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= 74 | golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 75 | golang.org/x/net v0.0.0-20151016005417-cd8c2701a5e1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 76 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= 77 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 78 | golang.org/x/sys v0.0.0-20160615012701-62bee0375999/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 79 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 80 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 81 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= 82 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 83 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 84 | golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= 85 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 86 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 87 | gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= 88 | gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= 89 | gopkg.in/yaml.v2 v2.0.0-20151201162745-f7716cbe52ba h1:zs5kJPNmBQxScpRIdp+2eZySRHFWheLSbXfME4zAxgw= 90 | gopkg.in/yaml.v2 v2.0.0-20151201162745-f7716cbe52ba/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= 91 | -------------------------------------------------------------------------------- /repo-index.yml: -------------------------------------------------------------------------------- 1 | - name: Targets 2 | description: Easily manage multiple CF targets 3 | version: _TAG_ 4 | created: 2015-4-17 5 | updated: 2015-8-14 6 | company: 7 | authors: 8 | - name: Guido Westenberg 9 | homepage: http://github.com/guidowb 10 | contact: gwestenberg@pivotal.io 11 | homepage: http://github.com/guidowb/cf-targets-plugin 12 | binaries: 13 | - platform: osx 14 | url: http://github.com/guidowb/cf-targets-plugin/raw/_TAG_/bin/osx/cf-targets-plugin 15 | checksum: osx-sha1 16 | - platform: win64 17 | url: http://github.com/guidowb/cf-targets-plugin/raw/_TAG_/bin/win64/cf-targets-plugin.exe 18 | checksum: win64-sha1 19 | - platform: linux64 20 | url: http://github.com/guidowb/cf-targets-plugin/raw/_TAG_/bin/linux64/cf-targets-plugin 21 | checksum: linux64-sha1 22 | --------------------------------------------------------------------------------