├── .gitignore ├── NOTICE ├── README.md ├── assert └── error.go ├── bin ├── linux64 │ └── brooklyn-plugin └── osx │ └── brooklyn-plugin ├── broker └── broker.go ├── brooklyn-plugin.go ├── catalog └── catalog.go ├── docs ├── build-and-test.md ├── manifest.md └── use.md ├── effectors └── effectors.go ├── io └── yaml-util.go ├── license.txt ├── push └── push.go └── sensors └── sensors.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.yaml 2 | *.yml 3 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-Present CloudFoundry.org Foundation, Inc. All Rights Reserved. 2 | 3 | This project is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | You may not use this project except in compliance with the License. 5 | 6 | This project may include a number of subcomponents with separate copyright notices 7 | and license terms. Your use of these subcomponents is subject to the terms and 8 | conditions of the subcomponent's license, as noted in the LICENSE file. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brooklyn Cloud Foundry Plugin 2 | 3 | This project makes a plugin to the cloud foundry [CLI](https://github.com/cloudfoundry-community/brooklyn-service-broker) to manage services 4 | brokered by the Brooklyn Service Broker. 5 | 6 | ## Quick Start 7 | 8 | If you are using CLI version 6.10+ you can install the 9 | plugin using Plugin Discovery with the community repository: 10 | 11 | $ cf add-plugin-repo community http://plugins.cloudfoundry.org/ 12 | $ cf install-plugin Brooklyn -r community 13 | 14 | Otherwise, you can [build it from source]{docs/build-and-test.md}. Then login using 15 | 16 | $ cf brooklyn login 17 | 18 | which will prompt for a broker, and if not already stored a username and password. 19 | It will then store these details in $HOME/.cf_brooklyn_plugin 20 | 21 | The plugin is then ready for [use](docs/use.md). 22 | -------------------------------------------------------------------------------- /assert/error.go: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import ( 4 | "github.com/cloudfoundry/cli/cf/errors" 5 | ) 6 | 7 | func Condition(cond bool, message string) { 8 | if !cond { 9 | panic(errors.New("PLUGIN ERROR: " + message)) 10 | } 11 | } 12 | 13 | func ErrorIsNil(err error) { 14 | if err != nil { 15 | Condition(false, "error not nil, "+err.Error()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /bin/linux64/brooklyn-plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudfoundry-attic/brooklyn-plugin/5cadf2dbd436c49f733ae9f9d11655e7e2501b39/bin/linux64/brooklyn-plugin -------------------------------------------------------------------------------- /bin/osx/brooklyn-plugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudfoundry-attic/brooklyn-plugin/5cadf2dbd436c49f733ae9f9d11655e7e2501b39/bin/osx/brooklyn-plugin -------------------------------------------------------------------------------- /broker/broker.go: -------------------------------------------------------------------------------- 1 | package broker 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 6 | "github.com/cloudfoundry/cli/cf/errors" 7 | "github.com/cloudfoundry/cli/plugin" 8 | "io/ioutil" 9 | "net/http" 10 | "net/url" 11 | "strings" 12 | ) 13 | 14 | type BrokerCredentials struct { 15 | Broker string 16 | Username string 17 | Password string 18 | } 19 | 20 | func NewBrokerCredentials(broker, username, password string) *BrokerCredentials { 21 | return &BrokerCredentials{broker, username, password} 22 | } 23 | 24 | func SendRequest(req *http.Request) ([]byte, error) { 25 | client := &http.Client{} 26 | resp, err := client.Do(req) 27 | assert.ErrorIsNil(err) 28 | defer resp.Body.Close() 29 | body, err := ioutil.ReadAll(resp.Body) 30 | if resp.Status != "200 OK" { 31 | fmt.Println("response Status:", resp.Status) 32 | fmt.Println("response Headers:", resp.Header) 33 | fmt.Println("response Body:", string(body)) 34 | } 35 | return body, err 36 | } 37 | 38 | func ServiceBrokerUrl(cliConnection plugin.CliConnection, broker string) (string, error) { 39 | brokers, err := cliConnection.CliCommandWithoutTerminalOutput("service-brokers") 40 | assert.Condition(err == nil, "could not get service broker url") 41 | for _, a := range brokers { 42 | fields := strings.Fields(a) 43 | if fields[0] == broker { 44 | return fields[1], nil 45 | } 46 | } 47 | return "", errors.New("No such broker") 48 | } 49 | 50 | func CreateRestCallUrlString(cliConnection plugin.CliConnection, cred *BrokerCredentials, path string) string { 51 | brokerUrl, err := ServiceBrokerUrl(cliConnection, cred.Broker) 52 | assert.Condition(err == nil, "No such broker") 53 | brooklynUrl, err := url.Parse(brokerUrl) 54 | assert.Condition(err == nil, "Can't parse url") 55 | brooklynUrl.Path = path 56 | brooklynUrl.User = url.UserPassword(cred.Username, cred.Password) 57 | return brooklynUrl.String() 58 | } 59 | -------------------------------------------------------------------------------- /brooklyn-plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "path/filepath" 5 | "fmt" 6 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 7 | "github.com/cloudfoundry-community/brooklyn-plugin/broker" 8 | "github.com/cloudfoundry-community/brooklyn-plugin/catalog" 9 | "github.com/cloudfoundry-community/brooklyn-plugin/effectors" 10 | "github.com/cloudfoundry-community/brooklyn-plugin/io" 11 | "github.com/cloudfoundry-community/brooklyn-plugin/push" 12 | "github.com/cloudfoundry-community/brooklyn-plugin/sensors" 13 | "github.com/cloudfoundry/cli/cf/terminal" 14 | "github.com/cloudfoundry/cli/generic" 15 | "github.com/cloudfoundry/cli/plugin" 16 | "os" 17 | ) 18 | 19 | type BrooklynPlugin struct { 20 | ui terminal.UI 21 | cliConnection plugin.CliConnection 22 | yamlMap generic.Map 23 | credentials *broker.BrokerCredentials 24 | } 25 | 26 | func (c *BrooklynPlugin) printHelp(name string) { 27 | metadata := c.GetMetadata() 28 | for _, command := range metadata.Commands { 29 | if command.Name == name{ 30 | fmt.Println("Name:") 31 | fmt.Printf(" %-s - %-s\n", command.Name, command.HelpText) 32 | fmt.Println("Usage:") 33 | fmt.Printf(" %-s\n", command.UsageDetails.Usage) 34 | return 35 | } 36 | } 37 | } 38 | 39 | func (c *BrooklynPlugin) Run(cliConnection plugin.CliConnection, args []string) { 40 | defer func() { 41 | if r := recover(); r != nil { 42 | fmt.Println(r) 43 | } 44 | }() 45 | argLength := len(args) 46 | 47 | c.ui = terminal.NewUI(os.Stdin, terminal.NewTeePrinter()) 48 | c.cliConnection = cliConnection 49 | 50 | if argLength == 1 { 51 | metadata := c.GetMetadata() 52 | for _, command := range metadata.Commands { 53 | fmt.Printf("%-25s %-50s\n", command.Name, command.HelpText) 54 | } 55 | return 56 | } 57 | 58 | if argLength == 3 && args[2] == "-h" { 59 | c.printHelp(args[0] + " " + args[1]) 60 | return 61 | } 62 | 63 | // check to see if ~/.cf_brooklyn_plugin exists 64 | // Then Parse it to get user credentials 65 | home := os.Getenv("HOME") 66 | file := filepath.Join(home, ".cf_brooklyn_plugin") 67 | if _, err := os.Stat(file); os.IsNotExist(err) { 68 | io.WriteYAMLFile(generic.NewMap(), file) 69 | } 70 | yamlMap := io.ReadYAMLFile(file) 71 | var target, username, password string 72 | target, found := yamlMap.Get("target").(string) 73 | if found { 74 | auth, found := yamlMap.Get("auth").(map[interface{}]interface{}) 75 | if found { 76 | creds := auth[target].(map[interface{}]interface{}) 77 | username, found = creds["username"].(string) 78 | if found { 79 | password, found = creds["password"].(string) 80 | } 81 | } 82 | } 83 | 84 | brokerCredentials := broker.NewBrokerCredentials(target, username, password) 85 | 86 | switch args[1] { 87 | case "login": 88 | broker := c.ui.Ask("Broker") 89 | if !yamlMap.Has("auth"){ 90 | yamlMap.Set("auth", generic.NewMap()) 91 | } 92 | auth := generic.NewMap(yamlMap.Get("auth")) 93 | 94 | if !auth.Has(broker) { 95 | user := c.ui.Ask("Username") 96 | pass := c.ui.AskForPassword("Password") 97 | auth.Set(broker, generic.NewMap(map[string]string{ 98 | "username": user, 99 | "password": pass, 100 | })) 101 | } 102 | yamlMap.Set("target", broker) 103 | io.WriteYAMLFile(yamlMap, file) 104 | case "push": 105 | push.NewPushCommand(cliConnection, c.ui, brokerCredentials).Push(args[1:]) 106 | case "add-catalog": 107 | if argLength == 3 { 108 | assert.Condition(found, "target not set") 109 | catalog.NewAddCatalogCommand(cliConnection, c.ui).AddCatalog(brokerCredentials, args[2]) 110 | } else if argLength == 6 { 111 | brokerCredentials = broker.NewBrokerCredentials(args[2], args[3], args[4]) 112 | catalog.NewAddCatalogCommand(cliConnection, c.ui).AddCatalog(brokerCredentials, args[5]) 113 | } else { 114 | assert.Condition(false, "incorrect number of arguments") 115 | } 116 | defer fmt.Println("Catalog item sucessfully added.") 117 | case "delete-catalog": 118 | if argLength == 4 { 119 | assert.Condition(found, "target not set") 120 | catalog.NewAddCatalogCommand(cliConnection, c.ui).DeleteCatalog(brokerCredentials, args[2], args[3]) 121 | } else if argLength == 7 { 122 | brokerCredentials = broker.NewBrokerCredentials(args[2], args[3], args[4]) 123 | catalog.NewAddCatalogCommand(cliConnection, c.ui).DeleteCatalog(brokerCredentials, args[5], args[6]) 124 | } else { 125 | assert.Condition(false, "incorrect number of arguments") 126 | } 127 | case "effectors": 128 | if argLength == 3 { 129 | assert.Condition(found, "target not set") 130 | effectors.NewEffectorCommand(cliConnection, c.ui).ListEffectors(brokerCredentials, args[2]) 131 | } else if argLength == 6 { 132 | brokerCredentials = broker.NewBrokerCredentials(args[2], args[3], args[4]) 133 | effectors.NewEffectorCommand(cliConnection, c.ui).ListEffectors(brokerCredentials, args[5]) 134 | } else { 135 | assert.Condition(false, "incorrect number of arguments") 136 | } 137 | case "invoke": 138 | // TODO need to take a flag to specify broker creds 139 | // if args[2] == -b then args[2:4] are broker creds 140 | // so that command can be run without specifying 141 | // broker credentials 142 | if argLength >= 7 { 143 | brokerCredentials = broker.NewBrokerCredentials(args[2], args[3], args[4]) 144 | effectors.NewEffectorCommand(cliConnection, c.ui).InvokeEffector(brokerCredentials, args[5], args[6], args[7:]) 145 | } else { 146 | assert.Condition(false, "incorrect number of arguments") 147 | } 148 | case "sensors": 149 | if argLength == 3 { 150 | assert.Condition(found, "target not set") 151 | sensors.NewSensorCommand(cliConnection, c.ui).ListSensors(brokerCredentials, args[2]) 152 | } else if argLength == 6 { 153 | brokerCredentials = broker.NewBrokerCredentials(args[2], args[3], args[4]) 154 | sensors.NewSensorCommand(cliConnection, c.ui).ListSensors(brokerCredentials, args[5]) 155 | } else { 156 | assert.Condition(false, "incorrect number of arguments") 157 | } 158 | case "ready": 159 | if argLength == 3 { 160 | assert.Condition(found, "target not set") 161 | fmt.Println("Ready:", sensors.NewSensorCommand(cliConnection, c.ui).IsServiceReady(brokerCredentials, args[2])) 162 | } else if argLength == 6 { 163 | brokerCredentials = broker.NewBrokerCredentials(args[2], args[3], args[4]) 164 | fmt.Println("Ready:", sensors.NewSensorCommand(cliConnection, c.ui).IsServiceReady(brokerCredentials, args[5])) 165 | } else { 166 | assert.Condition(false, "incorrect number of arguments") 167 | } 168 | } 169 | fmt.Println(terminal.ColorizeBold("OK", 32)) 170 | 171 | } 172 | 173 | func (c *BrooklynPlugin) GetMetadata() plugin.PluginMetadata { 174 | return plugin.PluginMetadata{ 175 | Name: "BrooklynPlugin", 176 | Version: plugin.VersionType{ 177 | Major: 0, 178 | Minor: 1, 179 | Build: 1, 180 | }, 181 | Commands: []plugin.Command{ 182 | { // required to be a registered command 183 | Name: "brooklyn", 184 | HelpText: "Brooklyn plugin command's help text", 185 | UsageDetails: plugin.Usage{ 186 | Usage: "cf brooklyn", 187 | }, 188 | }, 189 | { 190 | Name: "brooklyn login", 191 | HelpText: "Store Broker login credentials for use between commands", 192 | UsageDetails: plugin.Usage{ 193 | Usage: "cf brooklyn login", 194 | }, 195 | }, 196 | { 197 | Name: "brooklyn push", 198 | HelpText: "Push a new app, replacing " + 199 | "brooklyn section with instantiated services", 200 | UsageDetails: plugin.Usage{ 201 | Usage: "cf brooklyn push [-f MANIFEST]", 202 | }, 203 | }, 204 | { 205 | Name: "brooklyn add-catalog", 206 | HelpText: "Submit a Blueprint to Brooklyn to be " + 207 | "added to its catalog", 208 | UsageDetails: plugin.Usage{ 209 | Usage: "cf brooklyn add-catalog CATALOG", 210 | }, 211 | }, 212 | { 213 | Name: "brooklyn delete-catalog", 214 | HelpText: "Delete an item from the Brooklyn catalog", 215 | UsageDetails: plugin.Usage{ 216 | Usage: "cf brooklyn delete-catalog SERVICE VERSION", 217 | }, 218 | }, 219 | { 220 | Name: "brooklyn effectors", 221 | HelpText: "List the effectors available to a service", 222 | UsageDetails: plugin.Usage{ 223 | Usage: "cf brooklyn effectors [BROKER USERNAME PASSWORD] SERVICE", 224 | }, 225 | }, 226 | { 227 | Name: "brooklyn invoke", 228 | HelpText: "Invoke an effector on a service", 229 | UsageDetails: plugin.Usage{ 230 | Usage: "cf brooklyn invoke [BROKER USERNAME PASSWORD] SERVICE EFFECTOR", 231 | }, 232 | }, 233 | { 234 | Name: "brooklyn sensors", 235 | HelpText: "List the sensors with their outputs for a service", 236 | UsageDetails: plugin.Usage{ 237 | Usage: "cf brooklyn sensors [BROKER USERNAME PASSWORD] SERVICE", 238 | }, 239 | }, 240 | }, 241 | } 242 | } 243 | 244 | func main() { 245 | plugin.Start(new(BrooklynPlugin)) 246 | } 247 | -------------------------------------------------------------------------------- /catalog/catalog.go: -------------------------------------------------------------------------------- 1 | package catalog 2 | 3 | import ( 4 | "fmt" 5 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 6 | "github.com/cloudfoundry-community/brooklyn-plugin/broker" 7 | "github.com/cloudfoundry/cli/cf/terminal" 8 | "github.com/cloudfoundry/cli/plugin" 9 | "net/http" 10 | "os" 11 | "path/filepath" 12 | ) 13 | 14 | type AddCatalogCommand struct { 15 | cliConnection plugin.CliConnection 16 | ui terminal.UI 17 | } 18 | 19 | func NewAddCatalogCommand(cliConnection plugin.CliConnection, ui terminal.UI) *AddCatalogCommand { 20 | command := new(AddCatalogCommand) 21 | command.cliConnection = cliConnection 22 | command.ui = ui 23 | return command 24 | } 25 | 26 | func (c *AddCatalogCommand) AddCatalog(cred *broker.BrokerCredentials, filePath string) { 27 | fmt.Println("Adding Brooklyn catalog item...") 28 | 29 | file, err := os.Open(filepath.Clean(filePath)) 30 | assert.ErrorIsNil(err) 31 | defer file.Close() 32 | 33 | req, err := http.NewRequest("POST", broker.CreateRestCallUrlString(c.cliConnection, cred, "create"), file) 34 | assert.ErrorIsNil(err) 35 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 36 | broker.SendRequest(req) 37 | } 38 | 39 | func (c *AddCatalogCommand) DeleteCatalog(cred *broker.BrokerCredentials, name, version string) { 40 | fmt.Println("Deleting Brooklyn catalog item...") 41 | req, err := http.NewRequest("DELETE", 42 | broker.CreateRestCallUrlString(c.cliConnection, cred, "delete/"+name+"/"+version+"/"), 43 | nil) 44 | assert.ErrorIsNil(err) 45 | broker.SendRequest(req) 46 | } 47 | -------------------------------------------------------------------------------- /docs/build-and-test.md: -------------------------------------------------------------------------------- 1 | For information about compiling Go source code, see [Compile packages and depen$ 2 | 3 | To build, 4 | 5 | $ go build 6 | 7 | To install, 8 | 9 | $ cf install-plugin brooklyn-plugin 10 | 11 | -------------------------------------------------------------------------------- /docs/manifest.md: -------------------------------------------------------------------------------- 1 | # Adding service definitions to the Application Manifest 2 | 3 | When doing a 4 | 5 | $ cf brooklyn push 6 | 7 | this will lookup the manifest.yml and look for service descriptions in 8 | any of three locations: 9 | 10 | 1. Under a `brooklyn` section for an application. This section must contain 11 | three fields: a name, a location, and a service(s). These correspond to the 12 | Name, Plan, and Service from the Brooklyn Service Broker. 13 | 14 | 2. Under the top-level `services` section. A Brooklyn blueprint that contains 15 | as a minimum, a name, a location, and a type. 16 | 17 | 3. Under an application-level `services` section. This also is a Brooklyn 18 | blueprint that contains as a minimum, a name, a location, and a type. 19 | 20 | The Brooklyn Service Broker will create these services and generate a new 21 | manifest.temp.yml file taking out the service definitions replacing them in 22 | a services section containing the service instances created by the broker. 23 | It will then delegate to the original push command withthe manifest.temp.yml 24 | file before deleting it. 25 | 26 | ## Example 1. 27 | 28 | applications: 29 | - name: my-app 30 | ... 31 | brooklyn: 32 | - name: my-demo-web-cluster 33 | location: localhost 34 | service: Demo Web Cluster with DB 35 | services: 36 | - old-service 37 | 38 | creates an instance of the service `Demo Web Cluster with DB` in the service broker with the plan `localhost` before creating the temp file, 39 | 40 | applications: 41 | - name: my-app 42 | ... 43 | services: 44 | - my-demo-web-cluster 45 | - old-service 46 | 47 | to use with push. 48 | 49 | ## Example 2. 50 | 51 | Specify a Brooklyn blueprint in the manifest under the brooklyn section: 52 | 53 | applications: 54 | - name: my-app 55 | brooklyn: 56 | - name: my-MySQL 57 | location: localhost 58 | services: 59 | - type: brooklyn.entity.database.mysql.MySqlNode 60 | services: 61 | - old-service 62 | 63 | In this instance, the brooklyn section will be extracted and converted into a catalog.temp.yml file: 64 | 65 | brooklyn.catalog: 66 | id: 67 | version: 1.0 68 | iconUrl: 69 | description: A user defined blueprint 70 | name: my-MySQL 71 | services: 72 | - type: brooklyn.entity.basic.BasicApplication 73 | brooklyn.children: 74 | - type: brooklyn.entity.database.mysql.MySqlNode 75 | 76 | The broker will then be refreshed and the service enabled. Then the 77 | service broker will create an instance of this service and 78 | replace the section in the manifest with, 79 | 80 | applications: 81 | - name: my-app 82 | ... 83 | services: 84 | - my-MySQL 85 | - old-service 86 | 87 | ## Example 3. 88 | 89 | Top level services. 90 | 91 | applications: 92 | - name: my-app 93 | ... 94 | services: 95 | - old-service 96 | - name: my-MySQL 97 | location: localhost 98 | type: brooklyn.entity.database.mysql.MySqlNode 99 | 100 | ## Example 4. 101 | 102 | Application-level services 103 | 104 | applications: 105 | - name: my-app 106 | services: 107 | - old-service 108 | - name: my-MySQL 109 | location: localhost 110 | type: brooklyn.entity.database.mysql.MySqlNode 111 | 112 | # Wait for service up 113 | The Brooklyn Push command will then wait for the service to be provisioned 114 | before delegating to the original push for binding etc. 115 | -------------------------------------------------------------------------------- /docs/use.md: -------------------------------------------------------------------------------- 1 | Push 2 | ----- 3 | 4 | $ cf brooklyn push 5 | 6 | creates services specified in the application manifest. See here for [instructions on writing 7 | services descriptions](manifest.md) 8 | 9 | Adding catalog items manually 10 | ----------------------------- 11 | 12 | $ cf brooklyn add-catalog [ ] 13 | 14 | this allows new entities to be created and added to the brooklyn 15 | catalog. The service broker that is associated will need to be 16 | refreshed with `cf update-service-broker` and enabled with 17 | `enable-service-access` for these new services to become available. 18 | 19 | Deleting catalog items 20 | ---------------------- 21 | 22 | $ cf brooklyn delete-catalog [ ] 23 | 24 | this allows catalog items to be deleted from the service broker. 25 | As with `add-catalog`, the service broker will need to be refreshed 26 | for the changes to take effect. 27 | 28 | Listing Effectors 29 | ----------------- 30 | 31 | $ cf brooklyn effectors [ ] 32 | 33 | this lists all of the effectors that can be invoked on the specified service. 34 | 35 | Invoking Effectors 36 | ------------------ 37 | 38 | $ cf brooklyn invoke [ ] 39 | 40 | invokes the effector on this service. 41 | 42 | Viewing Sensors 43 | --------------- 44 | 45 | $ cf brooklyn sensors [ ] 46 | 47 | views the sensors associated with this service. 48 | Check if a service is ready for binding 49 | --------------------------------------- 50 | 51 | $ cf brooklyn ready [ ] 52 | 53 | checks if the service has been provisioned yet and is running. 54 | It is useful for this to be true before binding, since the 55 | VCAP_SERVICES variable will contain the sensor information that 56 | exists at bind time. 57 | -------------------------------------------------------------------------------- /effectors/effectors.go: -------------------------------------------------------------------------------- 1 | package effectors 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 8 | "github.com/cloudfoundry-community/brooklyn-plugin/broker" 9 | "github.com/cloudfoundry/cli/cf/terminal" 10 | "github.com/cloudfoundry/cli/plugin" 11 | "net/http" 12 | "strings" 13 | ) 14 | 15 | type EffectorCommand struct { 16 | cliConnection plugin.CliConnection 17 | ui terminal.UI 18 | } 19 | 20 | func NewEffectorCommand(cliConnection plugin.CliConnection, ui terminal.UI) *EffectorCommand { 21 | command := new(EffectorCommand) 22 | command.cliConnection = cliConnection 23 | command.ui = ui 24 | return command 25 | } 26 | 27 | func (c *EffectorCommand) InvokeEffector(cred *broker.BrokerCredentials, service, effector string, params []string) { 28 | guid, err := c.cliConnection.CliCommandWithoutTerminalOutput("service", service, "--guid") 29 | assert.ErrorIsNil(err) 30 | assert.Condition(strings.Contains(effector, ":"), "invalid effector format") 31 | split := strings.Split(effector, ":") 32 | path := "invoke/" + guid[0] + "/" + split[0] + "/" + split[1] 33 | fmt.Println("Invoking effector", terminal.ColorizeBold(effector, 36)) 34 | 35 | m := make(map[string]string) 36 | for i := 0; i < len(params); i = i + 2 { 37 | assert.Condition(strings.HasPrefix(params[i], "--"), "invalid parameter format") 38 | k := strings.TrimPrefix(params[i], "--") 39 | v := params[i+1] 40 | 41 | m[k] = v 42 | } 43 | post, err := json.Marshal(m) 44 | assert.ErrorIsNil(err) 45 | req, err := http.NewRequest("POST", broker.CreateRestCallUrlString(c.cliConnection, cred, path), bytes.NewBuffer(post)) 46 | req.Header.Set("Content-Type", "application/json") 47 | assert.ErrorIsNil(err) 48 | body, _ := broker.SendRequest(req) 49 | fmt.Println(string(body)) 50 | } 51 | 52 | func (c *EffectorCommand) ListEffectors(cred *broker.BrokerCredentials, service string) { 53 | guid, err := c.cliConnection.CliCommandWithoutTerminalOutput("service", service, "--guid") 54 | url := broker.CreateRestCallUrlString(c.cliConnection, cred, "effectors/"+guid[0]) 55 | req, err := http.NewRequest("GET", url, nil) 56 | assert.ErrorIsNil(err) 57 | body, _ := broker.SendRequest(req) 58 | //fmt.Println(string(body)) 59 | var effectors map[string]interface{} 60 | err = json.Unmarshal(body, &effectors) 61 | assert.ErrorIsNil(err) 62 | fmt.Println(terminal.ColorizeBold(service, 32)) 63 | for i := 0; i < len(service); i++ { 64 | fmt.Print(terminal.ColorizeBold("-", 32)) 65 | } 66 | fmt.Println() 67 | c.outputChildren(0, effectors) 68 | 69 | } 70 | 71 | func (c *EffectorCommand) outputChildren(indent int, effectors map[string]interface{}) { 72 | children := effectors["children"] 73 | for k, v := range effectors { 74 | if k != "children" { 75 | c.printIndent(indent) 76 | if indent == 0 { 77 | fmt.Print(terminal.ColorizeBold("Application:", 32)) 78 | } 79 | fmt.Println(terminal.ColorizeBold(k, 32)) 80 | c.outputEffectors(indent+1, v.(map[string]interface{})) 81 | } 82 | } 83 | 84 | if children != nil { 85 | c.outputChildren(indent+1, children.(map[string]interface{})) 86 | } 87 | } 88 | 89 | func (c *EffectorCommand) outputEffectors(indent int, effectors map[string]interface{}) { 90 | children := effectors["children"] 91 | for k, v := range effectors { 92 | if k != "children" { 93 | c.printIndent(indent) 94 | c.printEffectorDescription(indent, terminal.ColorizeBold(k, 31), v.(map[string]interface{})) 95 | } 96 | } 97 | if children != nil { 98 | c.outputChildren(indent, children.(map[string]interface{})) 99 | } 100 | } 101 | 102 | func (c *EffectorCommand) printEffectorDescription(indent int, effectorName string, effector map[string]interface{}) { 103 | params := effector["parameters"].([]interface{}) 104 | 105 | fmt.Printf("%-30s %s\n", effectorName, effector["description"].(string)) 106 | 107 | if len(params) != 0 { 108 | 109 | c.printIndent(indent + 1) 110 | fmt.Println("parameters: ") 111 | for _, k := range params { 112 | c.printParameterDescription(indent+1, k.(map[string]interface{})) 113 | } 114 | } 115 | 116 | } 117 | 118 | func (c *EffectorCommand) printParameterDescription(indent int, parameter map[string]interface{}) { 119 | 120 | c.printIndent(indent) 121 | fmt.Printf("%-17s %-s\n", parameter["name"].(string), parameter["description"].(string)) 122 | } 123 | 124 | func (c *EffectorCommand) printIndent(indent int) { 125 | for i := 0; i < indent; i++ { 126 | fmt.Print(" ") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /io/yaml-util.go: -------------------------------------------------------------------------------- 1 | package io 2 | 3 | import ( 4 | //"fmt" 5 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 6 | "github.com/cloudfoundry-incubator/candiedyaml" 7 | "github.com/cloudfoundry/cli/cf/errors" 8 | . "github.com/cloudfoundry/cli/cf/i18n" 9 | "github.com/cloudfoundry/cli/generic" 10 | "io" 11 | "os" 12 | "path/filepath" 13 | ) 14 | 15 | func ReadYAMLFile(path string) generic.Map { 16 | file, err := os.Open(filepath.Clean(path)) 17 | assert.ErrorIsNil(err) 18 | defer file.Close() 19 | 20 | yamlMap, err := parse(file) 21 | assert.ErrorIsNil(err) 22 | return yamlMap 23 | } 24 | 25 | func parse(file io.Reader) (yamlMap generic.Map, err error) { 26 | decoder := candiedyaml.NewDecoder(file) 27 | yamlMap = generic.NewMap() 28 | err = decoder.Decode(yamlMap) 29 | 30 | assert.ErrorIsNil(err) 31 | 32 | if !generic.IsMappable(yamlMap) { 33 | err = errors.New(T("Invalid. Expected a map")) 34 | return 35 | } 36 | 37 | return 38 | } 39 | 40 | func WriteYAMLFile(yamlMap generic.Map, path string) { 41 | 42 | fileToWrite, err := os.Create(path) 43 | assert.ErrorIsNil(err) 44 | 45 | encoder := candiedyaml.NewEncoder(fileToWrite) 46 | err = encoder.Encode(yamlMap) 47 | 48 | assert.ErrorIsNil(err) 49 | 50 | return 51 | } 52 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | 178 | 179 | --------------------------------------------------- 180 | 181 | This distribution contains third party resources. 182 | 183 | This product includes software developed at The jQuery Foundation (http://jquery.org/): 184 | jquery.js, selected jquery*.js. 185 | Copyright (c) John Resig (2011). 186 | Licensed under the MIT license (see below). 187 | 188 | This product includes software developed at The Dojo Foundation (http://dojofoundation.org/): 189 | require.js, text.js, sizzle.js in jquery.js. 190 | Copyright (c) The Dojo Foundation (2011, 2012). 191 | Licensed under the MIT license (see below). 192 | 193 | This product includes software developed at DocumentCloud Inc (http://www.documentcloud.org/): 194 | backbone.js, underscore.js. 195 | Copyright (c) Jeremy Ashkenas, DocumentCloud Inc (2010-2013). 196 | Licensed under the MIT license (see below). 197 | 198 | This product includes software developed by Miller Medeiros (https://github.com/millermedeiros/): 199 | async.js. 200 | Copyright (c) Miller Medeiros (2011). 201 | Licensed under the MIT license (see below). 202 | 203 | This product includes software developed at Twitter, Inc (http://twitter.com): 204 | typeahead.js, bloodhound.js 205 | Copyright (c) Twitter, Inc (2013). 206 | Licensed under the MIT license (see below). 207 | 208 | This product includes software developed by Yehuda Katz (https://github.com/wycats/): 209 | handlebars*.js. 210 | Copyright (c) Yehuda Katz (2012). 211 | Licensed under the MIT license (see below). 212 | 213 | This product includes software developed by "Cowboy" Ben Alman (http://benalman.com/). 214 | jquery.ba-bbq*.js 215 | Copyright (c) "Cowboy" Ben Alman (2010). 216 | Licensed under the MIT license (see below). 217 | 218 | This product includes software developed by WonderGroup and Jordan Thomas (http://labs.wondergroup.com/demos/mini-ui/index.html): 219 | jquery.wiggle.js. 220 | Copyright (c) WonderGroup and Jordan Thomas (2010). 221 | Licensed under the MIT license (see below). 222 | 223 | This product includes software developed by Tim Wood (http://momentjs.com): 224 | moment.js 225 | Copyright (c) Tim Wood, Iskren Chernev, Moment.js contributors (2011-2014). 226 | Licensed under the MIT license (see below). 227 | 228 | This product includes software developed by ZeroClipboard contributors (https://github.com/zeroclipboard): 229 | ZeroClipboard.js 230 | Copyright (c) Jon Rohan, James M. Greene (2014). 231 | Licensed under the MIT license (see below). 232 | 233 | 234 | This product includes software developed at SpryMedia Ltd (http://sprymedia.co.uk/): 235 | jquery.dataTables.js, dataTables.extensions.js. 236 | Copyright (c) Allan Jardine (2008-2012). 237 | Licensed under the New BSD license (see below). 238 | 239 | This product includes software developed by js-uri contributors (https://code.google.com/js-uri): 240 | URI.js. 241 | Copyright (c) js-uri contributors (2013). 242 | Licensed under the New BSD license (see below). 243 | 244 | 245 | The MIT License ("MIT") 246 | 247 | Permission is hereby granted, free of charge, to any person obtaining a copy 248 | of this software and associated documentation files (the "Software"), to deal 249 | in the Software without restriction, including without limitation the rights 250 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 251 | copies of the Software, and to permit persons to whom the Software is 252 | furnished to do so, subject to the following conditions: 253 | 254 | The above copyright notice and this permission notice shall be included in 255 | all copies or substantial portions of the Software. 256 | 257 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 258 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 259 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 260 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 261 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 262 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 263 | THE SOFTWARE. 264 | 265 | 266 | 267 | The BSD 3-Clause License ("New BSD") 268 | 269 | Redistribution and use in source and binary forms, with or without modification, 270 | are permitted provided that the following conditions are met: 271 | 272 | 1. Redistributions of source code must retain the above copyright notice, 273 | this list of conditions and the following disclaimer. 274 | 275 | 2. Redistributions in binary form must reproduce the above copyright notice, 276 | this list of conditions and the following disclaimer in the documentation 277 | and/or other materials provided with the distribution. 278 | 279 | 3. Neither the name of the copyright holder nor the names of its contributors 280 | may be used to endorse or promote products derived from this software without 281 | specific prior written permission. 282 | 283 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 284 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 285 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 286 | IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 287 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 288 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 289 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 290 | WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 291 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 292 | POSSIBILITY OF SUCH DAMAGE. 293 | -------------------------------------------------------------------------------- /push/push.go: -------------------------------------------------------------------------------- 1 | package push 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/base64" 6 | "fmt" 7 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 8 | "github.com/cloudfoundry-community/brooklyn-plugin/broker" 9 | "github.com/cloudfoundry-community/brooklyn-plugin/catalog" 10 | "github.com/cloudfoundry-community/brooklyn-plugin/io" 11 | "github.com/cloudfoundry-community/brooklyn-plugin/sensors" 12 | "github.com/cloudfoundry/cli/cf/terminal" 13 | "github.com/cloudfoundry/cli/generic" 14 | "github.com/cloudfoundry/cli/plugin" 15 | "net/http" 16 | "os" 17 | "path/filepath" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | type PushCommand struct { 23 | cliConnection plugin.CliConnection 24 | ui terminal.UI 25 | yamlMap generic.Map 26 | credentials *broker.BrokerCredentials 27 | } 28 | 29 | func NewPushCommand(cliConnection plugin.CliConnection, ui terminal.UI, credentials *broker.BrokerCredentials) *PushCommand { 30 | command := new(PushCommand) 31 | command.cliConnection = cliConnection 32 | command.ui = ui 33 | command.credentials = credentials 34 | return command 35 | } 36 | 37 | /* 38 | modify the application manifest before passing to to original command 39 | */ 40 | func (c *PushCommand) Push(args []string) { 41 | // args[0] == "push" 42 | 43 | // TODO use CF way of parsing args 44 | manifest := "manifest.yml" 45 | if len(args) >= 3 && args[1] == "-f" { 46 | manifest = args[2] 47 | args = append(args[:1], args[3:]...) 48 | } 49 | c.yamlMap = io.ReadYAMLFile(manifest) 50 | 51 | //fmt.Println("getting brooklyn") 52 | allCreatedServices := []string{} 53 | allCreatedServices = append(allCreatedServices, c.replaceTopLevelServices()...) 54 | allCreatedServices = append(allCreatedServices, c.replaceApplicationServices()...) 55 | 56 | for _, service := range allCreatedServices { 57 | fmt.Printf("Waiting for %s to start...\n", service) 58 | } 59 | 60 | c.waitForServiceReady(allCreatedServices) 61 | 62 | c.pushWith(args, "manifest.temp.yml") 63 | } 64 | 65 | func (c *PushCommand) waitForServiceReady(services []string) { 66 | // before pushing check to see if service is running 67 | 68 | ready := c.allReady(services) 69 | waitTime := 2 * time.Second 70 | for !ready { 71 | fmt.Printf("Trying again in %v\n", waitTime) 72 | time.Sleep(waitTime) 73 | ready = c.allReady(services) 74 | if 2*waitTime == 16*time.Second { 75 | waitTime = 15 * time.Second 76 | } else if 2*waitTime > time.Minute { 77 | waitTime = time.Minute 78 | } else { 79 | waitTime = 2 * waitTime 80 | } 81 | } 82 | } 83 | 84 | func (c *PushCommand) allReady(services []string) bool { 85 | ready := true 86 | for _, service := range services { 87 | serviceReady := sensors.NewSensorCommand(c.cliConnection, c.ui).IsServiceReady(c.credentials, service) 88 | if !serviceReady { 89 | fmt.Printf("%s is not yet running.\n", service) 90 | } 91 | ready = ready && serviceReady 92 | } 93 | return ready 94 | } 95 | 96 | func (c *PushCommand) pushWith(args []string, tempFile string) { 97 | io.WriteYAMLFile(c.yamlMap, tempFile) 98 | _, err := c.cliConnection.CliCommand(append(args, "-f", tempFile)...) 99 | assert.ErrorIsNil(err) 100 | err = os.Remove(tempFile) 101 | assert.ErrorIsNil(err) 102 | } 103 | 104 | func (c *PushCommand) replaceTopLevelServices() []string { 105 | allCreatedServices := []string{} 106 | services, found := c.yamlMap.Get("services").([]interface{}) 107 | if !found { 108 | return allCreatedServices 109 | } 110 | for i, service := range services { 111 | switch service.(type) { 112 | case string: // do nothing, since service is an existing named service 113 | case map[interface{}]interface{}: 114 | createdService := c.newServiceFromMap(service.(map[interface{}]interface{})) 115 | allCreatedServices = append(allCreatedServices, createdService) 116 | // replace the defn in the yaml for its name 117 | services[i] = createdService 118 | } 119 | } 120 | return allCreatedServices 121 | } 122 | 123 | func (c *PushCommand) replaceApplicationServices() []string { 124 | allCreatedServices := []string{} 125 | applications, found := c.yamlMap.Get("applications").([]interface{}) 126 | if !found { 127 | return allCreatedServices 128 | } 129 | for _, app := range applications { 130 | application, found := app.(map[interface{}]interface{}) 131 | assert.Condition(found, "Application not found.") 132 | allCreatedServices = append(allCreatedServices, c.replaceBrooklynCreatingServices(application)...) 133 | allCreatedServices = append(allCreatedServices, c.replaceServicesCreatingServices(application)...) 134 | } 135 | 136 | return allCreatedServices 137 | } 138 | 139 | func (c *PushCommand) replaceBrooklynCreatingServices(application map[interface{}]interface{}) []string { 140 | brooklyn, found := application["brooklyn"].([]interface{}) 141 | var createdServices []string 142 | if !found { 143 | return createdServices 144 | } 145 | createdServices = c.createAllServicesFromBrooklyn(brooklyn) 146 | application["services"] = c.mergeServices(application, createdServices) 147 | delete(application, "brooklyn") 148 | return createdServices 149 | } 150 | 151 | func (c *PushCommand) replaceServicesCreatingServices(application map[interface{}]interface{}) []string { 152 | services, found := application["services"].([]interface{}) 153 | createdServices := []string{} 154 | if !found { 155 | return createdServices 156 | } 157 | createdServices = c.createAllServicesFromServices(services) 158 | return createdServices 159 | } 160 | 161 | func (c *PushCommand) mergeServices(application map[interface{}]interface{}, services []string) []string { 162 | if oldServices, found := application["services"].([]interface{}); found { 163 | for _, name := range oldServices { 164 | services = append(services, name.(string)) 165 | } 166 | } 167 | return services 168 | } 169 | 170 | func (c *PushCommand) createAllServicesFromServices(services []interface{}) []string { 171 | var createdServices []string 172 | for i, service := range services { 173 | switch service.(type) { 174 | case string: // do nothing, since service is an existing named service 175 | case map[interface{}]interface{}: 176 | // service definition 177 | createdService := c.newServiceFromMap(service.(map[interface{}]interface{})) 178 | createdServices = append(createdServices, createdService) 179 | services[i] = createdService 180 | } 181 | } 182 | return createdServices 183 | } 184 | 185 | func (c *PushCommand) createAllServicesFromBrooklyn(brooklyn []interface{}) []string { 186 | services := []string{} 187 | for _, brooklynApp := range brooklyn { 188 | brooklynApplication, found := brooklynApp.(map[interface{}]interface{}) 189 | assert.Condition(found, "Expected Map.") 190 | services = append(services, c.newService(brooklynApplication)) 191 | } 192 | return services 193 | } 194 | 195 | func (c *PushCommand) newServiceFromMap(service map[interface{}]interface{}) string { 196 | name, found := service["name"].(string) 197 | assert.Condition(found, "no name specified in blueprint") 198 | location, found := service["location"].(string) 199 | assert.Condition(found, "no location specified") 200 | if exists := c.catalogItemExists(name); !exists { 201 | c.createNewCatalogItemWithoutLocation(name, []interface{}{service}) 202 | } 203 | c.cliConnection.CliCommand("create-service", name, location, name) 204 | return name 205 | } 206 | 207 | // expects an item from the brooklyn section with a name section 208 | func (c *PushCommand) newService(brooklynApplication map[interface{}]interface{}) string { 209 | name, found := brooklynApplication["name"].(string) 210 | assert.Condition(found, "Expected Name.") 211 | c.createServices(brooklynApplication, name) 212 | return name 213 | } 214 | 215 | // expects an item from the brooklyn section 216 | func (c *PushCommand) createServices(brooklynApplication map[interface{}]interface{}, name string) { 217 | // If there is a service section then this refers to an 218 | // existing catalog entry. 219 | service, found := brooklynApplication["service"].(string) 220 | if found { 221 | // now we must use an existing plan (location) 222 | location, found := brooklynApplication["location"].(string) 223 | assert.Condition(found, "Expected Location") 224 | c.cliConnection.CliCommand("create-service", service, location, name) 225 | } else { 226 | c.extractAndCreateService(brooklynApplication, name) 227 | } 228 | } 229 | 230 | func (c *PushCommand) extractAndCreateService(brooklynApplication map[interface{}]interface{}, name string) { 231 | // If there is a services section then this is a blueprint 232 | // and this should be extracted and sent as a catalog item 233 | blueprints, found := brooklynApplication["services"].([]interface{}) 234 | var location string 235 | if found { 236 | 237 | // only do this if catalog doesn't contain it already 238 | // now we decide whether to add a location to the 239 | // catalog item, or use all locations as plans 240 | switch brooklynApplication["location"].(type) { 241 | case string: 242 | location = brooklynApplication["location"].(string) 243 | if exists := c.catalogItemExists(name); !exists { 244 | c.createNewCatalogItemWithoutLocation(name, blueprints) 245 | } 246 | case map[interface{}]interface{}: 247 | locationMap := brooklynApplication["location"].(map[interface{}]interface{}) 248 | count := 0 249 | for key, _ := range locationMap { 250 | location, found = key.(string) 251 | assert.Condition(found, "location not found") 252 | count = count + 1 253 | } 254 | assert.Condition(count == 1, "Expected only one location") 255 | if exists := c.catalogItemExists(name); !exists { 256 | c.createNewCatalogItemWithLocation(name, blueprints, locationMap) 257 | } 258 | } 259 | c.cliConnection.CliCommand("create-service", name, location, name) 260 | } 261 | } 262 | 263 | func (c *PushCommand) catalogItemExists(name string) bool { 264 | services, err := c.cliConnection.CliCommandWithoutTerminalOutput("marketplace", "-s", name) 265 | if err != nil { 266 | return false 267 | } 268 | 269 | for _, a := range services { 270 | fields := strings.Fields(a) 271 | if fields[0] == "OK" { 272 | return true 273 | } 274 | } 275 | return false 276 | } 277 | 278 | func (c *PushCommand) createCatalogYamlMap(name string, blueprintMap []interface{}) generic.Map { 279 | yamlMap := generic.NewMap() 280 | entry := map[string]string{ 281 | "id": name, 282 | "version": "1.0", 283 | "iconUrl": "", 284 | "description": "A user defined blueprint", 285 | } 286 | yamlMap.Set("brooklyn.catalog", entry) 287 | yamlMap.Set("name", name) 288 | yamlMap.Set("services", []map[string]interface{}{ 289 | map[string]interface{}{ 290 | "type": "brooklyn.entity.basic.BasicApplication", 291 | "name": name, 292 | "brooklyn.children": blueprintMap, 293 | }, 294 | }) 295 | return yamlMap 296 | } 297 | 298 | func (c *PushCommand) createNewCatalogItemWithLocation( 299 | name string, blueprintMap []interface{}, location map[interface{}]interface{}) { 300 | yamlMap := c.createCatalogYamlMap(name, blueprintMap) 301 | yamlMap.Set("location", generic.NewMap(location)) 302 | c.createNewCatalogItem(name, yamlMap) 303 | } 304 | 305 | func (c *PushCommand) createNewCatalogItemWithoutLocation(name string, blueprintMap []interface{}) { 306 | yamlMap := c.createCatalogYamlMap(name, blueprintMap) 307 | c.createNewCatalogItem(name, yamlMap) 308 | } 309 | 310 | func (c *PushCommand) createNewCatalogItem(name string, yamlMap generic.Map) { 311 | tempFile := "catalog.temp.yml" 312 | io.WriteYAMLFile(yamlMap, tempFile) 313 | 314 | cred := c.credentials 315 | brokerUrl, err := broker.ServiceBrokerUrl(c.cliConnection, cred.Broker) 316 | assert.ErrorIsNil(err) 317 | 318 | catalog.NewAddCatalogCommand(c.cliConnection, c.ui).AddCatalog(cred, tempFile) 319 | 320 | c.cliConnection.CliCommand("update-service-broker", cred.Broker, cred.Username, cred.Password, brokerUrl) 321 | c.cliConnection.CliCommand("enable-service-access", name) 322 | err = os.Remove(tempFile) 323 | assert.ErrorIsNil(err) 324 | } 325 | 326 | func (c *PushCommand) addCatalog(cred *broker.BrokerCredentials, filePath string) { 327 | fmt.Println("Adding Brooklyn catalog item...") 328 | 329 | file, err := os.Open(filepath.Clean(filePath)) 330 | assert.ErrorIsNil(err) 331 | defer file.Close() 332 | 333 | req, err := http.NewRequest("POST", broker.CreateRestCallUrlString(c.cliConnection, cred, "create"), file) 334 | assert.ErrorIsNil(err) 335 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 336 | broker.SendRequest(req) 337 | } 338 | 339 | func (c *PushCommand) randomString(size int) string { 340 | rb := make([]byte, size) 341 | _, err := rand.Read(rb) 342 | assert.ErrorIsNil(err) 343 | return base64.URLEncoding.EncodeToString(rb) 344 | } 345 | -------------------------------------------------------------------------------- /sensors/sensors.go: -------------------------------------------------------------------------------- 1 | package sensors 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/cloudfoundry-community/brooklyn-plugin/assert" 7 | "github.com/cloudfoundry-community/brooklyn-plugin/broker" 8 | "github.com/cloudfoundry/cli/cf/terminal" 9 | "github.com/cloudfoundry/cli/plugin" 10 | "net/http" 11 | "strconv" 12 | ) 13 | 14 | type SensorCommand struct { 15 | cliConnection plugin.CliConnection 16 | ui terminal.UI 17 | } 18 | 19 | func NewSensorCommand(cliConnection plugin.CliConnection, ui terminal.UI) *SensorCommand { 20 | command := new(SensorCommand) 21 | command.cliConnection = cliConnection 22 | return command 23 | } 24 | 25 | func (c *SensorCommand) getSensors(cred *broker.BrokerCredentials, service string) map[string]interface{} { 26 | guid, err := c.cliConnection.CliCommandWithoutTerminalOutput("service", service, "--guid") 27 | url := broker.CreateRestCallUrlString(c.cliConnection, cred, "sensors/"+guid[0]) 28 | req, err := http.NewRequest("GET", url, nil) 29 | assert.ErrorIsNil(err) 30 | body, _ := broker.SendRequest(req) 31 | //fmt.Println(string(body)) 32 | var sensors map[string]interface{} 33 | err = json.Unmarshal(body, &sensors) 34 | assert.ErrorIsNil(err) 35 | return sensors 36 | } 37 | 38 | func (c *SensorCommand) IsServiceReady(cred *broker.BrokerCredentials, service string) bool { 39 | guid, err := c.cliConnection.CliCommandWithoutTerminalOutput("service", service, "--guid") 40 | url := broker.CreateRestCallUrlString(c.cliConnection, cred, "is-running/"+guid[0]) 41 | req, err := http.NewRequest("GET", url, nil) 42 | assert.ErrorIsNil(err) 43 | body, _ := broker.SendRequest(req) 44 | //fmt.Println("is running = ", string(body)) 45 | b, err := strconv.ParseBool(string(body)) 46 | return b 47 | } 48 | 49 | func (c *SensorCommand) ListSensors(cred *broker.BrokerCredentials, service string) { 50 | sensors := c.getSensors(cred, service) 51 | fmt.Println(terminal.ColorizeBold(service, 32)) 52 | for i := 0; i < len(service); i++ { 53 | fmt.Print(terminal.ColorizeBold("-", 32)) 54 | } 55 | fmt.Println() 56 | c.outputSensorChildren(0, sensors) 57 | } 58 | 59 | func (c *SensorCommand) outputSensorChildren(indent int, sensors map[string]interface{}) { 60 | for k, v := range sensors { 61 | c.printIndent(indent) 62 | if indent == 0 { 63 | fmt.Print(terminal.ColorizeBold("Entity:", 32)) 64 | } 65 | fmt.Println(terminal.ColorizeBold(k, 32)) 66 | c.outputSensors(indent+1, v.(map[string]interface{})) 67 | } 68 | } 69 | 70 | func (c *SensorCommand) outputSensors(indent int, sensors map[string]interface{}) { 71 | children := sensors["children"] 72 | for k, v := range sensors { 73 | if k != "children" { 74 | c.printIndent(indent) 75 | switch v.(type) { 76 | default: 77 | fmt.Println(k, ":", v) 78 | case map[string]interface{}: 79 | fmt.Println(k) 80 | c.outputSensors(indent+1, v.(map[string]interface{})) 81 | } 82 | } 83 | } 84 | if children != nil { 85 | c.outputSensorChildren(indent+1, children.(map[string]interface{})) 86 | } 87 | } 88 | 89 | func (c *SensorCommand) printIndent(indent int) { 90 | for i := 0; i < indent; i++ { 91 | fmt.Print(" ") 92 | } 93 | } 94 | --------------------------------------------------------------------------------