├── .gitignore ├── LICENSE ├── README.md ├── api └── api.go ├── cache ├── cache.go ├── cache_examples_test.go └── cache_test.go ├── config ├── config.go ├── config_test.go └── homedir.go ├── examples ├── codepackages │ ├── info │ │ └── main.go │ └── list │ │ └── main.go └── tasks │ ├── info │ └── main.go │ ├── list │ └── main.go │ ├── log │ └── main.go │ └── queue │ └── main.go ├── mq ├── mq.go ├── mq_examples_test.go └── mq_test.go └── worker ├── methods.go ├── remote.go ├── worker.go └── worker_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *.sublime* 25 | .idea/ 26 | *.iml 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Iron.io 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Iron.io Go Client Library 2 | ------------- 3 | 4 | # IronMQ 5 | 6 | [IronMQ](http://www.iron.io/products/mq) is an elastic message queue for managing data and event flow within cloud applications and between systems. 7 | 8 | The [full API documentation is here](http://dev.iron.io/mq/reference/api/) and this client tries to stick to the API as 9 | much as possible so if you see an option in the API docs, you can use it in the methods below. 10 | 11 | You can find [Go docs here](http://godoc.org/github.com/iron-io/iron_go). 12 | 13 | ## Getting Started 14 | 15 | ### Get credentials 16 | 17 | To start using iron_go, you need to sign up and get an oauth token. 18 | 19 | 1. Go to http://iron.io/ and sign up. 20 | 2. Create new project at http://hud.iron.io/dashboard 21 | 3. Download the iron.json file from "Credentials" block of project 22 | 23 | -- 24 | 25 | ### Configure 26 | 27 | 1\. Reference the library: 28 | 29 | ```go 30 | import "github.com/iron-io/iron_go/mq" 31 | ``` 32 | 33 | 2\. [Setup your Iron.io credentials](http://dev.iron.io/mq/reference/configuration/) 34 | 35 | 3\. Create an IronMQ client object: 36 | 37 | ```go 38 | queue := mq.New("test_queue"); 39 | ``` 40 | 41 | ## The Basics 42 | 43 | ### Get Queues List 44 | 45 | ```go 46 | queues, err := mq.ListQueues(0, 100); 47 | for _, element := range queues { 48 | fmt.Println(element.Name); 49 | } 50 | ``` 51 | 52 | -- 53 | 54 | ### Get a Queue Object 55 | 56 | You can have as many queues as you want, each with their own unique set of messages. 57 | 58 | ```go 59 | queue := mq.New("test_queue"); 60 | ``` 61 | 62 | Now you can use it. 63 | 64 | -- 65 | 66 | ### Post a Message on a Queue 67 | 68 | Messages are placed on the queue in a FIFO arrangement. 69 | If a queue does not exist, it will be created upon the first posting of a message. 70 | 71 | ```go 72 | id, err := q.PushString("Hello, World!") 73 | ``` 74 | 75 | -- 76 | 77 | ### Retrieve Queue Information 78 | 79 | ```go 80 | info, err := q.Info() 81 | fmt.Println(info.Name); 82 | ``` 83 | 84 | -- 85 | 86 | ### Get a Message off a Queue 87 | 88 | ```go 89 | msg, err := q.Get() 90 | fmt.Printf("The message says: %q\n", msg.Body) 91 | ``` 92 | 93 | -- 94 | 95 | ### Delete a Message from a Queue 96 | 97 | ```go 98 | msg, _ := q.Get() 99 | // perform some actions with a message here 100 | msg.Delete() 101 | ``` 102 | 103 | Be sure to delete a message from the queue when you're done with it. 104 | 105 | -- 106 | 107 | ## Queues 108 | 109 | ### Retrieve Queue Information 110 | 111 | ```go 112 | info, err := q.Info() 113 | fmt.Println(info.Name); 114 | fmt.Println(info.Size); 115 | ``` 116 | 117 | QueueInfo struct consists of the following fields: 118 | 119 | ```go 120 | type QueueInfo struct { 121 | Id string `json:"id,omitempty"` 122 | Name string `json:"name,omitempty"` 123 | PushType string `json:"push_type,omitempty"` 124 | Reserved int `json:"reserved,omitempty"` 125 | RetriesDelay int `json:"retries,omitempty"` 126 | Retries int `json:"retries_delay,omitempty"` 127 | Size int `json:"size,omitempty"` 128 | Subscribers []QueueSubscriber `json:"subscribers,omitempty"` 129 | TotalMessages int `json:"total_messages,omitempty"` 130 | ErrorQueue string `json:"error_queue,omitempty"` 131 | } 132 | ``` 133 | 134 | -- 135 | 136 | ### Delete a Message Queue 137 | 138 | ```go 139 | deleted, err := q.Delete() 140 | if(deleted) { 141 | fmt.Println("Successfully deleted") 142 | } else { 143 | fmt.Println("Cannot delete, because of error: ", err) 144 | } 145 | ``` 146 | 147 | -- 148 | 149 | ### Post Messages to a Queue 150 | 151 | **Single message:** 152 | 153 | ```go 154 | id, err := q.PushString("Hello, World!") 155 | // To control parameters like timeout and delay, construct your own message. 156 | id, err := q.PushMessage(&mq.Message{Timeout: 60, Delay: 0, Body: "Hi there"}) 157 | ``` 158 | 159 | **Multiple messages:** 160 | 161 | You can also pass multiple messages in a single call. 162 | 163 | ```go 164 | ids, err := q.PushStrings("Message 1", "Message 2") 165 | ``` 166 | 167 | To control parameters like timeout and delay, construct your own message. 168 | 169 | ```go 170 | ids, err = q.PushMessages( 171 | &mq.Message{Timeout: 60, Delay: 0, Body: "The first"}, 172 | &mq.Message{Timeout: 60, Delay: 10, Body: "The second"}, 173 | &mq.Message{Timeout: 60, Delay: 10, Body: "The third"}, 174 | &mq.Message{Timeout: 60, Delay: 0, Body: "The fifth"}, 175 | ) 176 | ``` 177 | 178 | **Parameters:** 179 | 180 | * `Timeout`: After timeout (in seconds), item will be placed back onto queue. 181 | You must delete the message from the queue to ensure it does not go back onto the queue. 182 | Default is 60 seconds. Minimum is 30 seconds. Maximum is 86,400 seconds (24 hours). 183 | 184 | * `Delay`: The item will not be available on the queue until this many seconds have passed. 185 | Default is 0 seconds. Maximum is 604,800 seconds (7 days). 186 | 187 | -- 188 | 189 | ### Get Messages from a Queue 190 | 191 | ```go 192 | msg, err := q.Get() 193 | fmt.Printf("The message says: %q\n", msg.Body) 194 | ``` 195 | 196 | When you pop/get a message from the queue, it is no longer on the queue but it still exists within the system. 197 | You have to explicitly delete the message or else it will go back onto the queue after the `timeout`. 198 | The default `timeout` is 60 seconds. Minimal `timeout` is 30 seconds. 199 | 200 | You also can get several messages at a time: 201 | 202 | ```go 203 | // get 5 messages 204 | msgs, err := q.GetN(5) 205 | ``` 206 | 207 | And with timeout param: 208 | 209 | ```go 210 | messages, err := q.GetNWithTimeout(4, 600) 211 | ``` 212 | 213 | ### Touch a Message on a Queue 214 | 215 | Touching a reserved message extends its timeout by the duration specified when the message was created, which is 60 seconds by default. 216 | 217 | ```go 218 | msg, _ := q.Get() 219 | err := msg.Touch() 220 | ``` 221 | 222 | There is another way to touch a message without getting it: 223 | 224 | ```go 225 | err := q.TouchMessage("5987586196292186572") 226 | ``` 227 | 228 | -- 229 | 230 | ### Release Message 231 | 232 | ```go 233 | msg, _ := q.Get() 234 | delay := 30 235 | err := msg.release(delay) 236 | ``` 237 | 238 | Or another way to release a message without creation of message object: 239 | 240 | ```go 241 | delay := 30 242 | err := q.ReleaseMessage("5987586196292186572", delay) 243 | ``` 244 | 245 | **Optional parameters:** 246 | 247 | * `delay`: The item will not be available on the queue until this many seconds have passed. 248 | Default is 0 seconds. Maximum is 604,800 seconds (7 days). 249 | 250 | -- 251 | 252 | ### Delete a Message from a Queue 253 | 254 | ```go 255 | msg, _ := q.Get() 256 | // perform some actions with a message here 257 | err := msg.Delete() 258 | ``` 259 | 260 | Or 261 | 262 | ```go 263 | err := q.DeleteMessage("5987586196292186572") 264 | ``` 265 | 266 | Be sure to delete a message from the queue when you're done with it. 267 | 268 | -- 269 | 270 | ### Peek Messages from a Queue 271 | 272 | Peeking at a queue returns the next messages on the queue, but it does not reserve them. 273 | 274 | ```go 275 | message, err := q.Peek() 276 | ``` 277 | 278 | There is a way to get several messages not reserving them: 279 | 280 | ```go 281 | messages, err := q.PeekN(50) 282 | for _, m := range messages { 283 | fmt.Println(m.Body) 284 | } 285 | ``` 286 | 287 | And with timeout param: 288 | 289 | ```go 290 | messages, err := q.PeekNWithTimeout(4, 600) 291 | ``` 292 | 293 | -- 294 | 295 | ### Clear a Queue 296 | 297 | ```go 298 | err := q.Clear() 299 | ``` 300 | 301 | ### Add an Alert to a Queue 302 | 303 | [Check out our Blog Post on Queue Alerts](http://blog.iron.io). 304 | 305 | Alerts have now been incorporated into IronMQ. This feature lets developers control actions based on the activity within a queue. With alerts, actions can be triggered when the number of messages in a queue reach a certain threshold. These actions can include things like auto-scaling, failure detection, load-monitoring, and system health. 306 | 307 | You may add up to 5 alerts per queue. 308 | 309 | **Required parameters:** 310 | * `type`: required - "fixed" or "progressive". In case of alert's type set to "fixed", alert will be triggered when queue size pass value set by trigger parameter. When type set to "progressive", alert will be triggered when queue size pass any of values, calculated by trigger * N where N >= 1. For example, if trigger set to 10, alert will be triggered at queue sizes 10, 20, 30, etc. 311 | * `direction`: required - "asc" or "desc". Set direction in which queue size must be changed when pass trigger value. If direction set to "asc" queue size must growing to trigger alert. When direction is "desc" queue size must decreasing to trigger alert. 312 | * `trigger`: required. It will be used to calculate actual values of queue size when alert must be triggered. See type field description. Trigger must be integer value greater than 0. 313 | * `queue`: required. Name of queue which will be used to post alert messages. 314 | 315 | ```go 316 | err := q.AddAlerts( 317 | &mq.Alert{Queue: "new_milestone_queue", Trigger: 10, Direction: "asc", Type: "progressive"}, 318 | &mq.Alert{Queue: "low_level_queue", Trigger: 5, Direction: "desc", Type: "fixed" }) 319 | ``` 320 | 321 | #### Update alerts in a queue 322 | ```go 323 | err := q.AddAlerts( 324 | &mq.Alert{Queue: "milestone_queue", Trigger: 100, Direction: "asc", Type: "progressive"}) 325 | ``` 326 | 327 | #### Remove alerts from a queue 328 | 329 | You can delete an alert from a queue by id: 330 | 331 | ```go 332 | err := q.RemoveAlert("532fdf593663ed6afa06ed16") 333 | ``` 334 | 335 | Or delete several alerts by ids: 336 | 337 | ```go 338 | err := q.RemoveAlerts("532f59663ed6afed16483052", "559663ed6af6483399b3400a") 339 | ``` 340 | 341 | Also you can delete all alerts 342 | 343 | ```go 344 | err := q.RemoveAllAlerts() 345 | ``` 346 | 347 | Please, remember, that passing zero of alerts while update process will lead to deleating of all previously added alerts. 348 | 349 | ```go 350 | q.AddAlerts( 351 | &mq.Alert{Queue: "alert1", Trigger: 10, Direction: "asc", Type: "progressive"}, 352 | &mq.Alert{Queue: "alert2", Trigger: 5, Direction: "desc", Type: "fixed" }) 353 | info, _ := q.Info() // 2 354 | 355 | q.UpdateAlerts() 356 | info, _ = q.Info() // 0 357 | ``` 358 | 359 | -- 360 | 361 | ## Push Queues 362 | 363 | IronMQ push queues allow you to setup a queue that will push to an endpoint, rather than having to poll the endpoint. 364 | [Here's the announcement for an overview](http://blog.iron.io/2013/01/ironmq-push-queues-reliable-message.html). 365 | 366 | ### Update a Message Queue 367 | 368 | ```go 369 | queueInfo := mq.QueueInfo{ 370 | //... 371 | } 372 | info, err := q.Update(queueInfo); 373 | ``` 374 | 375 | QueueInfo struct consists of following fields: 376 | 377 | ```go 378 | type QueueInfo struct { 379 | PushType string `json:"push_type,omitempty"` 380 | RetriesDelay int `json:"retries,omitempty"` 381 | Retries int `json:"retries_delay,omitempty"` 382 | Subscribers []QueueSubscriber `json:"subscribers,omitempty"` 383 | // and some other fields not related to push queues 384 | } 385 | ``` 386 | 387 | **The following parameters are all related to Push Queues:** 388 | 389 | * `push_type`: Either `multicast` to push to all subscribers or `unicast` to push to one and only one subscriber. Default is `multicast`. 390 | * `retries`: How many times to retry on failure. Default is 3. Maximum is 100. 391 | * `retries_delay`: Delay between each retry in seconds. Default is 60. 392 | * `subscribers`: An array of `QueueSubscriber` 393 | This set of subscribers will replace the existing subscribers. 394 | To add or remove subscribers, see the add subscribers endpoint or the remove subscribers endpoint. 395 | 396 | QueueSubscriber has the following structure: 397 | 398 | ```go 399 | type QueueSubscriber struct { 400 | URL string `json:"url"` 401 | Headers map[string]string `json:"headers,omitempty"` 402 | } 403 | ``` 404 | 405 | -- 406 | 407 | ### Set Subscribers on a Queue 408 | 409 | Subscribers can be any HTTP endpoint. `push_type` is one of: 410 | 411 | * `multicast`: will push to all endpoints/subscribers 412 | * `unicast`: will push to one and only one endpoint/subscriber 413 | 414 | ```go 415 | err := q.AddSubscribers( 416 | "http://mysterious-brook-1807.herokuapp.com/ironmq_push_3", 417 | "http://mysterious-brook-1807.herokuapp.com/ironmq_push_4") 418 | ``` 419 | 420 | -- 421 | 422 | 448 | 449 | ### Get Message Push Status 450 | 451 | After pushing a message: 452 | 453 | ```go 454 | subscribers, err := message.Subscribers() 455 | ``` 456 | 457 | Returns an array of subscribers with status. 458 | 459 | -- 460 | 461 | ### Revert Queue Back to Pull Queue 462 | 463 | If you want to revert you queue just update `push_type` to `'pull'`. 464 | 465 | ```ruby 466 | q.Update(mq.QueueInfo{ 467 | PushType: "pull", 468 | }); 469 | ``` 470 | 471 | -- 472 | 473 | ## Further Links 474 | 475 | * [IronMQ Overview](http://dev.iron.io/mq/) 476 | * [IronMQ REST/HTTP API](http://dev.iron.io/mq/reference/api/) 477 | * [Push Queues](http://dev.iron.io/mq/reference/push_queues/) 478 | * [Other Client Libraries](http://dev.iron.io/mq/libraries/) 479 | * [Live Chat, Support & Fun](http://get.iron.io/chat) 480 | 481 | ------------- 482 | © 2011 - 2014 Iron.io Inc. All Rights Reserved. 483 | -------------------------------------------------------------------------------- /api/api.go: -------------------------------------------------------------------------------- 1 | // api provides common functionality for all the iron.io APIs 2 | package api 3 | 4 | import ( 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "net/http" 11 | "net/http/httputil" 12 | "net/url" 13 | "os" 14 | "strings" 15 | "time" 16 | 17 | "github.com/iron-io/iron_go/config" 18 | ) 19 | 20 | type URL struct { 21 | URL url.URL 22 | Settings config.Settings 23 | } 24 | 25 | var ( 26 | debug bool 27 | 28 | // HttpClient is the client used by iron_go to make each http request. It is exported in case 29 | // the client would like to modify it from the default behavior from http.DefaultClient. 30 | HttpClient = &http.Client{} 31 | ) 32 | 33 | func dbg(v ...interface{}) { 34 | if debug { 35 | fmt.Fprintln(os.Stderr, v...) 36 | } 37 | } 38 | 39 | func init() { 40 | if os.Getenv("IRON_API_DEBUG") != "" { 41 | debug = true 42 | dbg("debugging of api enabled") 43 | } 44 | } 45 | 46 | func Action(cs config.Settings, prefix string, suffix ...string) *URL { 47 | parts := append([]string{prefix}, suffix...) 48 | return ActionEndpoint(cs, strings.Join(parts, "/")) 49 | } 50 | 51 | func ActionEndpoint(cs config.Settings, endpoint string) *URL { 52 | u := &URL{Settings: cs, URL: url.URL{}} 53 | u.URL.Scheme = cs.Scheme 54 | u.URL.Host = fmt.Sprintf("%s:%d", cs.Host, cs.Port) 55 | u.URL.Path = fmt.Sprintf("/%s/projects/%s/%s", cs.ApiVersion, cs.ProjectId, endpoint) 56 | return u 57 | } 58 | 59 | func VersionAction(cs config.Settings) *URL { 60 | u := &URL{Settings: cs, URL: url.URL{Scheme: cs.Scheme}} 61 | u.URL.Host = fmt.Sprintf("%s:%d", cs.Host, cs.Port) 62 | u.URL.Path = "/version" 63 | return u 64 | } 65 | 66 | func (u *URL) QueryAdd(key string, format string, value interface{}) *URL { 67 | query := u.URL.Query() 68 | query.Add(key, fmt.Sprintf(format, value)) 69 | u.URL.RawQuery = query.Encode() 70 | return u 71 | } 72 | 73 | func (u *URL) Req(method string, in, out interface{}) (err error) { 74 | var reqBody io.Reader 75 | if in != nil { 76 | data, err := json.Marshal(in) 77 | if err != nil { 78 | return err 79 | } 80 | reqBody = bytes.NewBuffer(data) 81 | } 82 | response, err := u.Request(method, reqBody) 83 | if response != nil { 84 | defer response.Body.Close() 85 | } 86 | if err == nil && out != nil { 87 | err = json.NewDecoder(response.Body).Decode(out) 88 | dbg("u:", u, "out:", fmt.Sprintf("%#v\n", out)) 89 | } 90 | 91 | return 92 | } 93 | 94 | var MaxRequestRetries = 5 95 | 96 | func (u *URL) Request(method string, body io.Reader) (response *http.Response, err error) { 97 | var bodyBytes []byte 98 | if body == nil { 99 | bodyBytes = []byte{} 100 | } else { 101 | bodyBytes, err = ioutil.ReadAll(body) 102 | if err != nil { 103 | return nil, err 104 | } 105 | } 106 | 107 | request, err := http.NewRequest(method, u.URL.String(), nil) 108 | if err != nil { 109 | return nil, err 110 | } 111 | 112 | request.Header.Set("Authorization", "OAuth "+u.Settings.Token) 113 | request.Header.Set("Accept", "application/json") 114 | request.Header.Set("User-Agent", u.Settings.UserAgent) 115 | 116 | if body != nil { 117 | request.Header.Set("Content-Type", "application/json") 118 | } 119 | 120 | dbg("request:", fmt.Sprintf("%#v\n", request)) 121 | 122 | for tries := 0; tries <= MaxRequestRetries; tries++ { 123 | request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) 124 | response, err = HttpClient.Do(request) 125 | if err != nil { 126 | if err == io.EOF { 127 | continue 128 | } 129 | return 130 | } 131 | 132 | if response.StatusCode == http.StatusServiceUnavailable { 133 | delay := (tries + 1) * 10 // smooth out delays from 0-2 134 | time.Sleep(time.Duration(delay*delay) * time.Millisecond) 135 | continue 136 | } 137 | 138 | break 139 | } 140 | 141 | // DumpResponse(response) 142 | if err = ResponseAsError(response); err != nil { 143 | return 144 | } 145 | 146 | return 147 | } 148 | 149 | func DumpRequest(req *http.Request) { 150 | out, err := httputil.DumpRequestOut(req, true) 151 | if err != nil { 152 | panic(err) 153 | } 154 | fmt.Printf("%q\n", out) 155 | } 156 | 157 | func DumpResponse(response *http.Response) { 158 | out, err := httputil.DumpResponse(response, true) 159 | if err != nil { 160 | panic(err) 161 | } 162 | fmt.Printf("%q\n", out) 163 | } 164 | 165 | var HTTPErrorDescriptions = map[int]string{ 166 | http.StatusUnauthorized: "The OAuth token is either not provided or invalid", 167 | http.StatusNotFound: "The resource, project, or endpoint being requested doesn't exist.", 168 | http.StatusMethodNotAllowed: "This endpoint doesn't support that particular verb", 169 | http.StatusNotAcceptable: "Required fields are missing", 170 | } 171 | 172 | func ResponseAsError(response *http.Response) HTTPResponseError { 173 | if response.StatusCode == http.StatusOK || response.StatusCode == http.StatusCreated { 174 | return nil 175 | } 176 | 177 | defer response.Body.Close() 178 | desc, found := HTTPErrorDescriptions[response.StatusCode] 179 | if found { 180 | return resErr{response: response, error: response.Status + ": " + desc} 181 | } 182 | 183 | out := map[string]interface{}{} 184 | err := json.NewDecoder(response.Body).Decode(&out) 185 | if err != nil { 186 | return resErr{response: response, error: fmt.Sprint(response.Status, ": ", err.Error())} 187 | } 188 | if msg, ok := out["msg"]; ok { 189 | return resErr{response: response, error: fmt.Sprint(response.Status, ": ", msg)} 190 | } 191 | 192 | return resErr{response: response, error: response.Status + ": Unknown API Response"} 193 | } 194 | 195 | type HTTPResponseError interface { 196 | Error() string 197 | Response() *http.Response 198 | } 199 | 200 | type resErr struct { 201 | error string 202 | response *http.Response 203 | } 204 | 205 | func (h resErr) Error() string { return h.error } 206 | func (h resErr) Response() *http.Response { return h.response } 207 | -------------------------------------------------------------------------------- /cache/cache.go: -------------------------------------------------------------------------------- 1 | // IronCache (cloud k/v store) client library 2 | package cache 3 | 4 | import ( 5 | "bytes" 6 | "encoding/gob" 7 | "encoding/json" 8 | "fmt" 9 | "time" 10 | 11 | "github.com/iron-io/iron_go/api" 12 | "github.com/iron-io/iron_go/config" 13 | ) 14 | 15 | var ( 16 | JSON = Codec{Marshal: json.Marshal, Unmarshal: json.Unmarshal} 17 | Gob = Codec{Marshal: gobMarshal, Unmarshal: gobUnmarshal} 18 | ) 19 | 20 | type Cache struct { 21 | Settings config.Settings 22 | Name string 23 | } 24 | 25 | type Item struct { 26 | // Value is the Item's value 27 | Value interface{} 28 | // Object is the Item's value for use with a Codec. 29 | Object interface{} 30 | // Number of seconds until expiration. The zero value defaults to 7 days, 31 | // maximum is 30 days. 32 | Expiration time.Duration 33 | // Caches item only if the key is currently cached. 34 | Replace bool 35 | // Caches item only if the key isn't currently cached. 36 | Add bool 37 | } 38 | 39 | // New returns a struct ready to make requests with. 40 | // The cacheName argument is used as namespace. 41 | func New(cacheName string) *Cache { 42 | return &Cache{Settings: config.Config("iron_cache"), Name: cacheName} 43 | } 44 | 45 | func (c *Cache) caches(suffix ...string) *api.URL { 46 | return api.Action(c.Settings, "caches", suffix...) 47 | } 48 | 49 | func (c *Cache) ListCaches(page, perPage int) (caches []*Cache, err error) { 50 | out := []struct { 51 | Project_id string 52 | Name string 53 | }{} 54 | 55 | err = c.caches(). 56 | QueryAdd("page", "%d", page). 57 | QueryAdd("per_page", "%d", perPage). 58 | Req("GET", nil, &out) 59 | if err != nil { 60 | return 61 | } 62 | 63 | caches = make([]*Cache, 0, len(out)) 64 | for _, item := range out { 65 | caches = append(caches, &Cache{ 66 | Settings: c.Settings, 67 | Name: item.Name, 68 | }) 69 | } 70 | 71 | return 72 | } 73 | 74 | func (c *Cache) ServerVersion() (version string, err error) { 75 | out := map[string]string{} 76 | err = api.VersionAction(c.Settings).Req("GET", nil, &out) 77 | if err != nil { 78 | return 79 | } 80 | return out["version"], nil 81 | } 82 | 83 | func (c *Cache) Clear() (err error) { 84 | return c.caches(c.Name, "clear").Req("POST", nil, nil) 85 | } 86 | 87 | // Put adds an Item to the cache, overwriting any existing key of the same name. 88 | func (c *Cache) Put(key string, item *Item) (err error) { 89 | in := struct { 90 | Value interface{} `json:"value"` 91 | ExpiresIn int `json:"expires_in,omitempty"` 92 | Replace bool `json:"replace,omitempty"` 93 | Add bool `json:"add,omitempty"` 94 | }{ 95 | Value: item.Value, 96 | ExpiresIn: int(item.Expiration.Seconds()), 97 | Replace: item.Replace, 98 | Add: item.Add, 99 | } 100 | 101 | return c.caches(c.Name, "items", key).Req("PUT", &in, nil) 102 | } 103 | 104 | func anyToString(value interface{}) (str interface{}, err error) { 105 | switch v := value.(type) { 106 | case string: 107 | str = v 108 | case uint, uint8, uint16, uint32, uint64, int, int8, int16, int32, int64: 109 | str = v 110 | case float32, float64: 111 | str = v 112 | case bool: 113 | str = v 114 | case fmt.Stringer: 115 | str = v.String() 116 | default: 117 | var bytes []byte 118 | if bytes, err = json.Marshal(value); err == nil { 119 | str = string(bytes) 120 | } 121 | } 122 | 123 | return 124 | } 125 | 126 | func (c *Cache) Set(key string, value interface{}, ttl ...int) (err error) { 127 | str, err := anyToString(value) 128 | if err == nil { 129 | if len(ttl) > 0 { 130 | err = c.Put(key, &Item{Value: str, Expiration: time.Duration(ttl[0]) * time.Second}) 131 | } else { 132 | err = c.Put(key, &Item{Value: str}) 133 | } 134 | } 135 | return 136 | } 137 | func (c *Cache) Add(key string, value ...interface{}) (err error) { 138 | str, err := anyToString(value) 139 | if err == nil { 140 | err = c.Put(key, &Item{ 141 | Value: str, Expiration: time.Duration(123) * time.Second, Add: true, 142 | }) 143 | } 144 | return 145 | } 146 | func (c *Cache) Replace(key string, value ...interface{}) (err error) { 147 | str, err := anyToString(value) 148 | if err == nil { 149 | err = c.Put(key, &Item{ 150 | Value: str, Expiration: time.Duration(123) * time.Second, Replace: true, 151 | }) 152 | } 153 | return 154 | } 155 | 156 | // Increment increments the corresponding item's value. 157 | func (c *Cache) Increment(key string, amount int64) (value interface{}, err error) { 158 | in := map[string]int64{"amount": amount} 159 | 160 | out := struct { 161 | Message string `json:"msg"` 162 | Value interface{} `json:"value"` 163 | }{} 164 | if err = c.caches(c.Name, "items", key, "increment").Req("POST", &in, &out); err == nil { 165 | value = out.Value 166 | } 167 | return 168 | } 169 | 170 | // Get gets an item from the cache. 171 | func (c *Cache) Get(key string) (value interface{}, err error) { 172 | out := struct { 173 | Cache string `json:"cache"` 174 | Key string `json:"key"` 175 | Value interface{} `json:"value"` 176 | }{} 177 | if err = c.caches(c.Name, "items", key).Req("GET", nil, &out); err == nil { 178 | value = out.Value 179 | } 180 | return 181 | } 182 | 183 | func (c *Cache) GetMeta(key string) (value map[string]interface{}, err error) { 184 | value = map[string]interface{}{} 185 | err = c.caches(c.Name, "items", key).Req("GET", nil, &value) 186 | return 187 | } 188 | 189 | // Delete removes an item from the cache. 190 | func (c *Cache) Delete(key string) (err error) { 191 | return c.caches(c.Name, "items", key).Req("DELETE", nil, nil) 192 | } 193 | 194 | type Codec struct { 195 | Marshal func(interface{}) ([]byte, error) 196 | Unmarshal func([]byte, interface{}) error 197 | } 198 | 199 | func (cd Codec) Put(c *Cache, key string, item *Item) (err error) { 200 | bytes, err := cd.Marshal(item.Object) 201 | if err != nil { 202 | return 203 | } 204 | 205 | item.Value = string(bytes) 206 | 207 | return c.Put(key, item) 208 | } 209 | 210 | func (cd Codec) Get(c *Cache, key string, object interface{}) (err error) { 211 | value, err := c.Get(key) 212 | if err != nil { 213 | return 214 | } 215 | 216 | err = cd.Unmarshal([]byte(value.(string)), object) 217 | if err != nil { 218 | return 219 | } 220 | 221 | return 222 | } 223 | 224 | func gobMarshal(v interface{}) ([]byte, error) { 225 | writer := bytes.Buffer{} 226 | enc := gob.NewEncoder(&writer) 227 | err := enc.Encode(v) 228 | return writer.Bytes(), err 229 | } 230 | 231 | func gobUnmarshal(marshalled []byte, v interface{}) error { 232 | reader := bytes.NewBuffer(marshalled) 233 | dec := gob.NewDecoder(reader) 234 | return dec.Decode(v) 235 | } 236 | -------------------------------------------------------------------------------- /cache/cache_examples_test.go: -------------------------------------------------------------------------------- 1 | package cache_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/iron-io/iron_go/cache" 6 | ) 7 | 8 | func p(a ...interface{}) { fmt.Println(a...) } 9 | 10 | func Example1StoringData() { 11 | // For configuration info, see http://dev.iron.io/articles/configuration 12 | c := cache.New("test_cache") 13 | 14 | // Numbers will get stored as numbers 15 | c.Set("number_item", 42) 16 | 17 | // Strings get stored as strings 18 | c.Set("string_item", "Hello, IronCache") 19 | 20 | // Objects and dicts get JSON-encoded and stored as strings 21 | c.Set("complex_item", map[string]interface{}{ 22 | "test": "this is a dict", 23 | "args": []string{"apples", "oranges"}, 24 | }) 25 | 26 | p("all stored") 27 | // Output: 28 | // all stored 29 | } 30 | 31 | func Example2Incrementing() { 32 | c := cache.New("test_cache") 33 | 34 | p(c.Increment("number_item", 10)) 35 | p(c.Get("number_item")) 36 | 37 | p(c.Increment("string_item", 10)) 38 | 39 | p(c.Increment("complex_item", 10)) 40 | 41 | // Output: 42 | // 43 | // 52 44 | // 400 Bad Request: Cannot increment or decrement non-numeric value 45 | // 400 Bad Request: Cannot increment or decrement non-numeric value 46 | } 47 | 48 | func Example3Decrementing() { 49 | c := cache.New("test_cache") 50 | 51 | p(c.Increment("number_item", -10)) 52 | p(c.Get("number_item")) 53 | 54 | p(c.Increment("string_item", -10)) 55 | 56 | p(c.Increment("complex_item", -10)) 57 | 58 | // Output: 59 | // 60 | // 42 61 | // 400 Bad Request: Cannot increment or decrement non-numeric value 62 | // 400 Bad Request: Cannot increment or decrement non-numeric value 63 | } 64 | 65 | func Example4RetrievingData() { 66 | c := cache.New("test_cache") 67 | 68 | value, err := c.Get("number_item") 69 | fmt.Printf("%#v (%#v)\n", value, err) 70 | 71 | value, err = c.Get("string_item") 72 | fmt.Printf("%#v (%#v)\n", value, err) 73 | 74 | // JSON is returned as strings 75 | value, err = c.Get("complex_item") 76 | fmt.Printf("%#v (%#v)\n", value, err) 77 | 78 | // You can use the JSON codec to deserialize it. 79 | obj := struct { 80 | Args []string 81 | Test string 82 | }{} 83 | err = cache.JSON.Get(c, "complex_item", &obj) 84 | fmt.Printf("%#v (%#v)\n", obj, err) 85 | // Output: 86 | // 42 () 87 | // "Hello, IronCache" () 88 | // "{\"args\":[\"apples\",\"oranges\"],\"test\":\"this is a dict\"}" () 89 | // struct { Args []string; Test string }{Args:[]string{"apples", "oranges"}, Test:"this is a dict"} () 90 | } 91 | 92 | func Example5DeletingData() { 93 | c := cache.New("test_cache") 94 | 95 | // Immediately delete an item 96 | c.Delete("string_item") 97 | 98 | p(c.Get("string_item")) 99 | // Output: 100 | // 404 Not Found: The resource, project, or endpoint being requested doesn't exist. 101 | } 102 | -------------------------------------------------------------------------------- /cache/cache_test.go: -------------------------------------------------------------------------------- 1 | package cache_test 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | 7 | "github.com/iron-io/iron_go/cache" 8 | . "github.com/jeffh/go.bdd" 9 | ) 10 | 11 | func TestEverything(t *testing.T) {} 12 | 13 | func init() { 14 | defer PrintSpecReport() 15 | 16 | Describe("IronCache", func() { 17 | c := cache.New("cachename") 18 | 19 | It("Lists all caches", func() { 20 | _, err := c.ListCaches(0, 100) // can't check the caches value just yet. 21 | Expect(err, ToBeNil) 22 | }) 23 | 24 | It("Puts a value into the cache", func() { 25 | err := c.Put("keyname", &cache.Item{ 26 | Value: "value", 27 | Expiration: 2 * time.Second, 28 | }) 29 | Expect(err, ToBeNil) 30 | }) 31 | 32 | It("Gets a value from the cache", func() { 33 | value, err := c.Get("keyname") 34 | Expect(err, ToBeNil) 35 | Expect(value, ToEqual, "value") 36 | }) 37 | 38 | It("Gets meta-information about an item", func() { 39 | err := c.Put("forever", &cache.Item{Value: "and ever", Expiration: 0}) 40 | Expect(err, ToBeNil) 41 | value, err := c.GetMeta("forever") 42 | Expect(err, ToBeNil) 43 | Expect(value["key"], ToEqual, "forever") 44 | Expect(value["value"], ToEqual, "and ever") 45 | Expect(value["cache"], ToEqual, "cachename") 46 | Expect(value["expires"], ToEqual, "9999-01-01T00:00:00Z") 47 | Expect(value["flags"], ToEqual, 0.0) 48 | }) 49 | 50 | It("Sets numeric items", func() { 51 | err := c.Set("number", 42) 52 | Expect(err, ToBeNil) 53 | value, err := c.Get("number") 54 | Expect(err, ToBeNil) 55 | Expect(value.(float64), ToEqual, 42.0) 56 | }) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // config helper for cache, mq, and worker 2 | package config 3 | 4 | import ( 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "os" 9 | "path/filepath" 10 | "runtime" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // Contains the configuration for an iron.io service. 16 | // An empty instance is not usable 17 | type Settings struct { 18 | Token string `json:"token,omitempty"` 19 | ProjectId string `json:"project_id,omitempty"` 20 | Host string `json:"host,omitempty"` 21 | Scheme string `json:"scheme,omitempty"` 22 | Port uint16 `json:"port,omitempty"` 23 | ApiVersion string `json:"api_version,omitempty"` 24 | UserAgent string `json:"user_agent,omitempty"` 25 | } 26 | 27 | var ( 28 | debug = false 29 | goVersion = runtime.Version() 30 | Presets = map[string]Settings{ 31 | "worker": Settings{ 32 | Scheme: "https", 33 | Port: 443, 34 | ApiVersion: "2", 35 | Host: "worker-aws-us-east-1.iron.io", 36 | UserAgent: "iron_go/worker 2.0 (Go " + goVersion + ")", 37 | }, 38 | "mq": Settings{ 39 | Scheme: "https", 40 | Port: 443, 41 | ApiVersion: "1", 42 | Host: "mq-aws-us-east-1.iron.io", 43 | UserAgent: "iron_go/mq 1.0 (Go " + goVersion + ")", 44 | }, 45 | "cache": Settings{ 46 | Scheme: "https", 47 | Port: 443, 48 | ApiVersion: "1", 49 | Host: "cache-aws-us-east-1.iron.io", 50 | UserAgent: "iron_go/cache 1.0 (Go " + goVersion + ")", 51 | }, 52 | } 53 | ) 54 | 55 | func dbg(v ...interface{}) { 56 | if debug { 57 | fmt.Fprintln(os.Stderr, v...) 58 | } 59 | } 60 | 61 | // ManualConfig gathers configuration from env variables, json config files 62 | // and finally overwrites it with specified instance of Settings. 63 | func ManualConfig(fullProduct string, configuration *Settings) (settings Settings) { 64 | return config(fullProduct, "", configuration) 65 | } 66 | 67 | // Config gathers configuration from env variables and json config files. 68 | // Examples of fullProduct are "iron_worker", "iron_cache", "iron_mq". 69 | func Config(fullProduct string) (settings Settings) { 70 | return config(fullProduct, "", nil) 71 | } 72 | 73 | // Like Config, but useful for keeping multiple dev environment information in 74 | // one iron.json config file. If env="", works same as Config. 75 | // 76 | // e.g. 77 | // { 78 | // "production": { 79 | // "token": ..., 80 | // "project_id": ... 81 | // }, 82 | // "test": { 83 | // ... 84 | // } 85 | // } 86 | func ConfigWithEnv(fullProduct, env string) (settings Settings) { 87 | return config(fullProduct, env, nil) 88 | } 89 | 90 | func config(fullProduct, env string, configuration *Settings) Settings { 91 | if os.Getenv("IRON_CONFIG_DEBUG") != "" { 92 | debug = true 93 | dbg("debugging of config enabled") 94 | } 95 | pair := strings.SplitN(fullProduct, "_", 2) 96 | if len(pair) != 2 { 97 | panic("Invalid product name, has to use prefix.") 98 | } 99 | family, product := pair[0], pair[1] 100 | 101 | base, found := Presets[product] 102 | 103 | if !found { 104 | base = Settings{ 105 | Scheme: "https", 106 | Port: 443, 107 | ApiVersion: "1", 108 | Host: product + "-aws-us-east-1.iron.io", 109 | UserAgent: "iron_go", 110 | } 111 | } 112 | 113 | base.globalConfig(family, product, env) 114 | base.globalEnv(family, product) 115 | base.productEnv(family, product) 116 | base.localConfig(family, product, env) 117 | base.manualConfig(configuration) 118 | 119 | return base 120 | } 121 | 122 | func (s *Settings) globalConfig(family, product, env string) { 123 | home, err := homeDir() 124 | if err != nil { 125 | fmt.Println("Error getting home directory:", err) 126 | return 127 | } 128 | path := filepath.Join(home, ".iron.json") 129 | s.UseConfigFile(family, product, path, env) 130 | } 131 | 132 | // The environment variables the scheme looks for are all of the same formula: 133 | // the camel-cased product name is switched to an underscore (“IronWorker” 134 | // becomes “iron_worker”) and converted to be all capital letters. For the 135 | // global environment variables, “IRON” is used by itself. The value being 136 | // loaded is then joined by an underscore to the name, and again capitalised. 137 | // For example, to retrieve the OAuth token, the client looks for “IRON_TOKEN”. 138 | func (s *Settings) globalEnv(family, product string) { 139 | eFamily := strings.ToUpper(family) + "_" 140 | s.commonEnv(eFamily) 141 | } 142 | 143 | // In the case of product-specific variables (which override global variables), 144 | // it would be “IRON_WORKER_TOKEN” (for IronWorker). 145 | func (s *Settings) productEnv(family, product string) { 146 | eProduct := strings.ToUpper(family) + "_" + strings.ToUpper(product) + "_" 147 | s.commonEnv(eProduct) 148 | } 149 | 150 | func (s *Settings) localConfig(family, product, env string) { 151 | s.UseConfigFile(family, product, "iron.json", env) 152 | } 153 | 154 | func (s *Settings) manualConfig(settings *Settings) { 155 | if settings != nil { 156 | s.UseSettings(settings) 157 | } 158 | } 159 | 160 | func (s *Settings) commonEnv(prefix string) { 161 | if token := os.Getenv(prefix + "TOKEN"); token != "" { 162 | s.Token = token 163 | dbg("env has TOKEN:", s.Token) 164 | } 165 | if pid := os.Getenv(prefix + "PROJECT_ID"); pid != "" { 166 | s.ProjectId = pid 167 | dbg("env has PROJECT_ID:", s.ProjectId) 168 | } 169 | if host := os.Getenv(prefix + "HOST"); host != "" { 170 | s.Host = host 171 | dbg("env has HOST:", s.Host) 172 | } 173 | if scheme := os.Getenv(prefix + "SCHEME"); scheme != "" { 174 | s.Scheme = scheme 175 | dbg("env has SCHEME:", s.Scheme) 176 | } 177 | if port := os.Getenv(prefix + "PORT"); port != "" { 178 | n, err := strconv.ParseUint(port, 10, 16) 179 | if err != nil { 180 | panic(err) 181 | } 182 | s.Port = uint16(n) 183 | dbg("env has PORT:", s.Port) 184 | } 185 | if vers := os.Getenv(prefix + "API_VERSION"); vers != "" { 186 | s.ApiVersion = vers 187 | dbg("env has API_VERSION:", s.ApiVersion) 188 | } 189 | } 190 | 191 | // Load and merge the given JSON config file. 192 | func (s *Settings) UseConfigFile(family, product, path, env string) { 193 | content, err := ioutil.ReadFile(path) 194 | if err != nil { 195 | dbg("tried to", err, ": skipping") 196 | return 197 | } 198 | 199 | data := map[string]interface{}{} 200 | err = json.Unmarshal(content, &data) 201 | if err != nil { 202 | panic("Invalid JSON in " + path + ": " + err.Error()) 203 | } 204 | 205 | dbg("config in", path, "found") 206 | 207 | if env != "" { 208 | envdata, ok := data[env].(map[string]interface{}) 209 | if !ok { 210 | return // bail, they specified an env but we couldn't find one, so error out. 211 | } 212 | data = envdata 213 | } 214 | s.UseConfigMap(data) 215 | 216 | ipData, found := data[family+"_"+product] 217 | if found { 218 | pData := ipData.(map[string]interface{}) 219 | s.UseConfigMap(pData) 220 | } 221 | } 222 | 223 | // Merge the given data into the settings. 224 | func (s *Settings) UseConfigMap(data map[string]interface{}) { 225 | if token, found := data["token"]; found { 226 | s.Token = token.(string) 227 | dbg("config has token:", s.Token) 228 | } 229 | if projectId, found := data["project_id"]; found { 230 | s.ProjectId = projectId.(string) 231 | dbg("config has project_id:", s.ProjectId) 232 | } 233 | if host, found := data["host"]; found { 234 | s.Host = host.(string) 235 | dbg("config has host:", s.Host) 236 | } 237 | if prot, found := data["scheme"]; found { 238 | s.Scheme = prot.(string) 239 | dbg("config has scheme:", s.Scheme) 240 | } 241 | if port, found := data["port"]; found { 242 | s.Port = uint16(port.(float64)) 243 | dbg("config has port:", s.Port) 244 | } 245 | if vers, found := data["api_version"]; found { 246 | s.ApiVersion = vers.(string) 247 | dbg("config has api_version:", s.ApiVersion) 248 | } 249 | if agent, found := data["user_agent"]; found { 250 | s.UserAgent = agent.(string) 251 | dbg("config has user_agent:", s.UserAgent) 252 | } 253 | } 254 | 255 | // Merge the given instance into the settings. 256 | func (s *Settings) UseSettings(settings *Settings) { 257 | if settings.Token != "" { 258 | s.Token = settings.Token 259 | } 260 | if settings.ProjectId != "" { 261 | s.ProjectId = settings.ProjectId 262 | } 263 | if settings.Host != "" { 264 | s.Host = settings.Host 265 | } 266 | if settings.Scheme != "" { 267 | s.Scheme = settings.Scheme 268 | } 269 | if settings.ApiVersion != "" { 270 | s.ApiVersion = settings.ApiVersion 271 | } 272 | if settings.UserAgent != "" { 273 | s.UserAgent = settings.UserAgent 274 | } 275 | if settings.Port > 0 { 276 | s.Port = settings.Port 277 | } 278 | } 279 | -------------------------------------------------------------------------------- /config/config_test.go: -------------------------------------------------------------------------------- 1 | package config_test 2 | 3 | import ( 4 | "github.com/iron-io/iron_go/config" 5 | . "github.com/jeffh/go.bdd" 6 | "testing" 7 | ) 8 | 9 | func init() { 10 | defer PrintSpecReport() 11 | Describe("gets config", func() { 12 | It("gets default configs", func() { 13 | s := config.Config("iron_undefined") 14 | Expect(s.Host, ToEqual, "undefined-aws-us-east-1.iron.io") 15 | }) 16 | }) 17 | } 18 | 19 | func TestEverything(t *testing.T) {} 20 | -------------------------------------------------------------------------------- /config/homedir.go: -------------------------------------------------------------------------------- 1 | //The MIT License (MIT) 2 | 3 | //Copyright (c) 2013 Mitchell Hashimoto 4 | 5 | //Permission is hereby granted, free of charge, to any person obtaining a copy 6 | //of this software and associated documentation files (the "Software"), to deal 7 | //in the Software without restriction, including without limitation the rights 8 | //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | //copies of the Software, and to permit persons to whom the Software is 10 | //furnished to do so, subject to the following conditions: 11 | 12 | //The above copyright notice and this permission notice shall be included in 13 | //all copies or substantial portions of the Software. 14 | 15 | //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | //THE SOFTWARE. 22 | // 23 | // This file is a wholesale copy of https://github.com/mitchellh/go-homedir@1f6da4a72e57d4e7edd4a7295a585e0a3999a2d4 24 | // with Dir() renamed to homeDir() and Expand() deleted. 25 | package config 26 | 27 | import ( 28 | "bytes" 29 | "errors" 30 | "os" 31 | "os/exec" 32 | "runtime" 33 | "strings" 34 | ) 35 | 36 | // homeDir returns the home directory for the executing user. 37 | // 38 | // This uses an OS-specific method for discovering the home directory. 39 | // An error is returned if a home directory cannot be detected. 40 | func homeDir() (string, error) { 41 | if runtime.GOOS == "windows" { 42 | return dirWindows() 43 | } 44 | 45 | // Unix-like system, so just assume Unix 46 | return dirUnix() 47 | } 48 | 49 | func dirUnix() (string, error) { 50 | // First prefer the HOME environmental variable 51 | if home := os.Getenv("HOME"); home != "" { 52 | return home, nil 53 | } 54 | 55 | // If that fails, try the shell 56 | var stdout bytes.Buffer 57 | cmd := exec.Command("sh", "-c", "eval echo ~$USER") 58 | cmd.Stdout = &stdout 59 | if err := cmd.Run(); err != nil { 60 | return "", err 61 | } 62 | 63 | result := strings.TrimSpace(stdout.String()) 64 | if result == "" { 65 | return "", errors.New("blank output when reading home directory") 66 | } 67 | 68 | return result, nil 69 | } 70 | 71 | func dirWindows() (string, error) { 72 | drive := os.Getenv("HOMEDRIVE") 73 | path := os.Getenv("HOMEPATH") 74 | home := drive + path 75 | if drive == "" || path == "" { 76 | home = os.Getenv("USERPROFILE") 77 | } 78 | if home == "" { 79 | return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank") 80 | } 81 | 82 | return home, nil 83 | } 84 | -------------------------------------------------------------------------------- /examples/codepackages/info/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | This code sample demonstrates how to get a list of existing tasks 3 | 4 | http://dev.iron.io/worker/reference/api/ 5 | http://dev.iron.io/worker/reference/api/#get_info_about_a_code_package 6 | */ 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "github.com/iron-io/iron_go/api" 13 | "github.com/iron-io/iron_go/config" 14 | "io/ioutil" 15 | "log" 16 | "text/template" 17 | "time" 18 | ) 19 | 20 | type ( 21 | Code struct { 22 | Id string `json:"id"` 23 | ProjectId string `json:"project_id"` 24 | Name string `json:"name"` 25 | Runtime string `json:"runtime"` 26 | LatestChecksum string `json:"latest_checksum"` 27 | Revision int `json:"rev"` 28 | LatestHistoryId string `json:"latest_history_id"` 29 | LatestChange time.Time `json:"latest_change"` 30 | } 31 | ) 32 | 33 | func main() { 34 | // Create your configuration for iron_worker 35 | // Find these value in credentials 36 | config := config.Config("iron_worker") 37 | config.ProjectId = "your_project_id" 38 | config.Token = "your_token" 39 | 40 | // Capture info for this code 41 | codeId := "522d160a91c530531f6f528d" 42 | 43 | // Create your endpoint url for tasks 44 | url := api.Action(config, "codes", codeId) 45 | log.Printf("Url: %s\n", url.URL.String()) 46 | 47 | // Post the request to Iron.io 48 | resp, err := url.Request("GET", nil) 49 | defer resp.Body.Close() 50 | if err != nil { 51 | log.Println(err) 52 | return 53 | } 54 | 55 | // Check the status code 56 | if resp.StatusCode != 200 { 57 | log.Printf("%v\n", resp) 58 | return 59 | } 60 | 61 | // Capture the response 62 | body, err := ioutil.ReadAll(resp.Body) 63 | if err != nil { 64 | log.Println(err) 65 | return 66 | } 67 | 68 | // Unmarshall to struct 69 | code := &Code{} 70 | err = json.Unmarshal(body, code) 71 | if err != nil { 72 | log.Printf("%v\n", err) 73 | return 74 | } 75 | 76 | // Unmarshall to map 77 | results := map[string]interface{}{} 78 | err = json.Unmarshal(body, &results) 79 | if err != nil { 80 | log.Printf("%v\n", err) 81 | return 82 | } 83 | 84 | // Pretty print the response 85 | prettyPrint(code) 86 | } 87 | 88 | func prettyPrint(code *Code) { 89 | prettyTemplate := template.Must(template.New("pretty").Parse(prettyPrintFormat())) 90 | 91 | display := new(bytes.Buffer) 92 | 93 | prettyTemplate.Execute(display, code) 94 | log.Printf("%s,\n", display.String()) 95 | } 96 | 97 | func prettyPrintFormat() string { 98 | return `{ 99 | "id": "{{.Id}}", 100 | "project_id": "{{.ProjectId}}", 101 | "name": "{{.Name}}", 102 | "runtime": "{{.Runtime}}", 103 | "latest_checksum": "{{.LatestChecksum}}", 104 | "rev": {{.Revision}}, 105 | "latest_history_id": "{{.LatestHistoryId}}", 106 | "latest_change": "{{.LatestChange}}", 107 | }` 108 | } 109 | -------------------------------------------------------------------------------- /examples/codepackages/list/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | This code sample demonstrates how to get a list of existing tasks 3 | 4 | http://dev.iron.io/worker/reference/api/ 5 | http://dev.iron.io/worker/reference/api/#list_code_packages 6 | */ 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "github.com/iron-io/iron_go/api" 14 | "github.com/iron-io/iron_go/config" 15 | "io/ioutil" 16 | "log" 17 | "text/template" 18 | "time" 19 | ) 20 | 21 | type ( 22 | CodeResponse struct { 23 | Codes []Code `json:"codes"` 24 | } 25 | 26 | Code struct { 27 | Id string `json:"id"` 28 | ProjectId string `json:"project_id"` 29 | Name string `json:"name"` 30 | Runtime string `json:"runtime"` 31 | LatestChecksum string `json:"latest_checksum"` 32 | Revision int `json:"rev"` 33 | LatestHistoryId string `json:"latest_history_id"` 34 | LatestChange time.Time `json:"latest_change"` 35 | } 36 | ) 37 | 38 | func main() { 39 | // Create your configuration for iron_worker 40 | // Find these value in credentials 41 | config := config.Config("iron_worker") 42 | config.ProjectId = "your_project_id" 43 | config.Token = "your_token" 44 | 45 | // Create your endpoint url for tasks 46 | url := api.ActionEndpoint(config, "codes") 47 | log.Printf("Url: %s\n", url.URL.String()) 48 | 49 | // Post the request to Iron.io 50 | resp, err := url.Request("GET", nil) 51 | defer resp.Body.Close() 52 | if err != nil { 53 | log.Println(err) 54 | return 55 | } 56 | 57 | // Check the status code 58 | if resp.StatusCode != 200 { 59 | log.Printf("%v\n", resp) 60 | return 61 | } 62 | 63 | // Capture the response 64 | body, err := ioutil.ReadAll(resp.Body) 65 | if err != nil { 66 | log.Println(err) 67 | return 68 | } 69 | 70 | // Unmarshall to struct 71 | codeResponse := &CodeResponse{} 72 | err = json.Unmarshal(body, codeResponse) 73 | if err != nil { 74 | log.Printf("%v\n", err) 75 | return 76 | } 77 | 78 | // Or you can Unmarshall to map 79 | results := map[string]interface{}{} 80 | err = json.Unmarshal(body, &results) 81 | if err != nil { 82 | log.Printf("%v\n", err) 83 | return 84 | } 85 | 86 | // Pretty print the response 87 | prettyPrint(codeResponse) 88 | } 89 | 90 | func prettyPrint(codeResponse *CodeResponse) { 91 | prettyTemplate := template.Must(template.New("pretty").Parse(prettyPrintFormat())) 92 | 93 | codes := "\n" 94 | display := new(bytes.Buffer) 95 | 96 | for _, code := range codeResponse.Codes { 97 | display.Reset() 98 | prettyTemplate.Execute(display, code) 99 | codes += fmt.Sprintf("%s,\n", display.String()) 100 | } 101 | 102 | log.Printf(codes) 103 | } 104 | 105 | func prettyPrintFormat() string { 106 | return `{ 107 | "id": "{{.Id}}", 108 | "project_id": "{{.ProjectId}}", 109 | "name": "{{.Name}}", 110 | "runtime": "{{.Runtime}}", 111 | "latest_checksum": "{{.LatestChecksum}}", 112 | "rev": {{.Revision}}, 113 | "latest_history_id": "{{.LatestHistoryId}}", 114 | "latest_change": "{{.LatestChange}}", 115 | }` 116 | } 117 | -------------------------------------------------------------------------------- /examples/tasks/info/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | This code sample demonstrates how to get a list of existing tasks 3 | 4 | http://dev.iron.io/worker/reference/api/ 5 | http://dev.iron.io/worker/reference/api/#get_info_about_a_task 6 | */ 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "github.com/iron-io/iron_go/api" 13 | "github.com/iron-io/iron_go/config" 14 | "io/ioutil" 15 | "log" 16 | "text/template" 17 | "time" 18 | ) 19 | 20 | type ( 21 | Task struct { 22 | Id string `json:"id"` 23 | ProjectId string `json:"project_id"` 24 | CodeId string `json:"code_id"` 25 | CodeHistoryId string `json:"code_history_id"` 26 | Status string `json:"status"` 27 | CodeName string `json:"code_name"` 28 | CodeRevision string `json:"code_rev"` 29 | StartTime time.Time `json:"start_time"` 30 | EndTime time.Time `json:"end_time"` 31 | Duration int `json:"duration"` 32 | Timeout int `json:"timeout"` 33 | Payload string `json:"payload"` 34 | UpdatedAt time.Time `json:"updated_at"` 35 | CreatedAt time.Time `json:"created_at"` 36 | } 37 | ) 38 | 39 | func main() { 40 | // Create your configuration for iron_worker 41 | // Find these value in credentials 42 | config := config.Config("iron_worker") 43 | config.ProjectId = "your_project_id" 44 | config.Token = "your_token" 45 | 46 | // Capture info for this task 47 | taskId := "52b45b17a31186632b00da4c" 48 | 49 | // Create your endpoint url for tasks 50 | url := api.Action(config, "tasks", taskId) 51 | log.Printf("Url: %s\n", url.URL.String()) 52 | 53 | // Post the request to Iron.io 54 | resp, err := url.Request("GET", nil) 55 | defer resp.Body.Close() 56 | if err != nil { 57 | log.Println(err) 58 | return 59 | } 60 | 61 | // Check the status code 62 | if resp.StatusCode != 200 { 63 | log.Printf("%v\n", resp) 64 | return 65 | } 66 | 67 | // Capture the response 68 | body, err := ioutil.ReadAll(resp.Body) 69 | if err != nil { 70 | log.Println(err) 71 | return 72 | } 73 | 74 | // Unmarshall to struct 75 | task := &Task{} 76 | err = json.Unmarshal(body, task) 77 | if err != nil { 78 | log.Printf("%v\n", err) 79 | return 80 | } 81 | 82 | // Or you can Unmarshall to map 83 | results := map[string]interface{}{} 84 | err = json.Unmarshal(body, &results) 85 | if err != nil { 86 | log.Printf("%v\n", err) 87 | return 88 | } 89 | 90 | // Pretty print the response 91 | prettyPrint(task) 92 | } 93 | 94 | func prettyPrint(task *Task) { 95 | prettyTemplate := template.Must(template.New("pretty").Parse(prettyPrintFormat())) 96 | 97 | display := new(bytes.Buffer) 98 | 99 | prettyTemplate.Execute(display, task) 100 | log.Printf("%s,\n", display.String()) 101 | } 102 | 103 | func prettyPrintFormat() string { 104 | return `{ 105 | "id": "{{.Id}}", 106 | "project_id": "{{.ProjectId}}", 107 | "code_id": "{{.CodeId}}", 108 | "code_history_id": "{{.CodeHistoryId}}", 109 | "status": "{{.Status}}", 110 | "code_name": "{{.CodeName}}", 111 | "code_revision": "{{.CodeRevision}}", 112 | "start_time": "{{.StartTime}}", 113 | "end_time": "{{.EndTime}}", 114 | "duration": {{.Duration}}, 115 | "timeout": {{.Timeout}}, 116 | "payload": {{.Payload}}, 117 | "created_at": "{{.CreatedAt}}", 118 | "updated_at": "{{.UpdatedAt}}", 119 | }` 120 | } 121 | -------------------------------------------------------------------------------- /examples/tasks/list/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | This code sample demonstrates how to get a list of existing tasks 3 | 4 | http://dev.iron.io/worker/reference/api/ 5 | http://dev.iron.io/worker/reference/api/#list_tasks 6 | */ 7 | package main 8 | 9 | import ( 10 | "bytes" 11 | "encoding/json" 12 | "fmt" 13 | "github.com/iron-io/iron_go/api" 14 | "github.com/iron-io/iron_go/config" 15 | "io/ioutil" 16 | "log" 17 | "text/template" 18 | "time" 19 | ) 20 | 21 | type ( 22 | TaskResponse struct { 23 | Tasks []Task `json:"tasks"` 24 | } 25 | 26 | Task struct { 27 | Id string `json:"id"` 28 | CreatedAt time.Time `json:"created_at"` 29 | UpdatedAt time.Time `json:"updated_at"` 30 | ProjectId string `json:"project_id"` 31 | CodeId string `json:"code_id"` 32 | Status string `json:"status"` 33 | Message string `json:"msg"` 34 | CodeName string `json:"code_name"` 35 | StartTime time.Time `json:"start_time"` 36 | EndTime time.Time `json:"end_time"` 37 | Duration int `json:"duration"` 38 | RunTimes int `json:"run_times"` 39 | Timeout int `json:"timeout"` 40 | Percent int `json:"percent"` 41 | } 42 | ) 43 | 44 | func main() { 45 | // Create your configuration for iron_worker 46 | // Find these value in credentials 47 | config := config.Config("iron_worker") 48 | config.ProjectId = "your_project_id" 49 | config.Token = "your_token" 50 | 51 | // Create your endpoint url for tasks 52 | url := api.ActionEndpoint(config, "tasks") 53 | url.QueryAdd("code_name", "%s", "task") 54 | log.Printf("Url: %s\n", url.URL.String()) 55 | 56 | // Post the request to Iron.io 57 | resp, err := url.Request("GET", nil) 58 | defer resp.Body.Close() 59 | if err != nil { 60 | log.Println(err) 61 | return 62 | } 63 | 64 | // Check the status code 65 | if resp.StatusCode != 200 { 66 | log.Printf("%v\n", resp) 67 | return 68 | } 69 | 70 | // Capture the response 71 | body, err := ioutil.ReadAll(resp.Body) 72 | if err != nil { 73 | log.Println(err) 74 | return 75 | } 76 | 77 | // Unmarshall to struct 78 | taskResponse := &TaskResponse{} 79 | err = json.Unmarshal(body, taskResponse) 80 | if err != nil { 81 | log.Printf("%v\n", err) 82 | return 83 | } 84 | 85 | // Or you can Unmarshall to map 86 | results := map[string]interface{}{} 87 | err = json.Unmarshal(body, &results) 88 | if err != nil { 89 | log.Printf("%v\n", err) 90 | return 91 | } 92 | 93 | // Pretty print the response 94 | prettyPrint(taskResponse) 95 | } 96 | 97 | func prettyPrint(taskResponse *TaskResponse) { 98 | prettyTemplate := template.Must(template.New("pretty").Parse(prettyPrintFormat())) 99 | 100 | tasks := "\n" 101 | display := new(bytes.Buffer) 102 | 103 | for _, task := range taskResponse.Tasks { 104 | display.Reset() 105 | prettyTemplate.Execute(display, task) 106 | tasks += fmt.Sprintf("%s,\n", display.String()) 107 | } 108 | 109 | log.Printf(tasks) 110 | } 111 | 112 | func prettyPrintFormat() string { 113 | return `{ 114 | "id": "{{.Id}}", 115 | "created_at": "{{.CreatedAt}}", 116 | "updated_at": "{{.UpdatedAt}}", 117 | "project_id": "{{.ProjectId}}", 118 | "code_id": "{{.CodeId}}", 119 | "status": "{{.Status}}", 120 | "msg": "{{.Message}}", 121 | "code_name": "{{.CodeName}}", 122 | "start_time": "{{.StartTime}}", 123 | "end_time": "{{.EndTime}}", 124 | "duration": {{.Duration}}, 125 | "run_times": {{.RunTimes}}, 126 | "timeout": {{.Timeout}}, 127 | "percent": {{.Percent}} 128 | }` 129 | } 130 | -------------------------------------------------------------------------------- /examples/tasks/log/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | This code sample demonstrates how to get the log for a task 3 | 4 | http://dev.iron.io/worker/reference/api/ 5 | http://dev.iron.io/worker/reference/api/#get_a_tasks_log 6 | */ 7 | package main 8 | 9 | import ( 10 | "github.com/iron-io/iron_go/api" 11 | "github.com/iron-io/iron_go/config" 12 | "io/ioutil" 13 | "log" 14 | "time" 15 | ) 16 | 17 | type ( 18 | Task struct { 19 | Id string `json:"id"` 20 | ProjectId string `json:"project_id"` 21 | CodeId string `json:"code_id"` 22 | CodeHistoryId string `json:"code_history_id"` 23 | Status string `json:"status"` 24 | CodeName string `json:"code_name"` 25 | CodeRevision string `json:"code_rev"` 26 | StartTime time.Time `json:"start_time"` 27 | EndTime time.Time `json:"end_time"` 28 | Duration int `json:"duration"` 29 | Timeout int `json:"timeout"` 30 | Payload string `json:"payload"` 31 | UpdatedAt time.Time `json:"updated_at"` 32 | CreatedAt time.Time `json:"created_at"` 33 | } 34 | ) 35 | 36 | func main() { 37 | // Create your configuration for iron_worker 38 | // Find these value in credentials 39 | config := config.Config("iron_worker") 40 | config.ProjectId = "your_project_id" 41 | config.Token = "your_token" 42 | 43 | // Capture info for this task 44 | taskId := "52b45b17a31186632b00da4c" 45 | 46 | // Create your endpoint url for tasks 47 | url := api.Action(config, "tasks", taskId, "log") 48 | log.Printf("Url: %s\n", url.URL.String()) 49 | 50 | // Post the request to Iron.io 51 | resp, err := url.Request("GET", nil) 52 | defer resp.Body.Close() 53 | if err != nil { 54 | log.Println(err) 55 | return 56 | } 57 | 58 | // Check the status code 59 | if resp.StatusCode != 200 { 60 | log.Printf("%v\n", resp) 61 | return 62 | } 63 | 64 | // Capture the response 65 | body, err := ioutil.ReadAll(resp.Body) 66 | if err != nil { 67 | log.Println(err) 68 | return 69 | } 70 | 71 | // Display the log 72 | log.Printf("\n%s\n", string(body)) 73 | } 74 | -------------------------------------------------------------------------------- /examples/tasks/queue/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | This code sample demonstrates how to queue a worker from your your existing 3 | task list. 4 | 5 | http://dev.iron.io/worker/reference/api/ 6 | http://dev.iron.io/worker/reference/api/#queue_a_task 7 | */ 8 | package main 9 | 10 | import ( 11 | "bytes" 12 | "encoding/json" 13 | "fmt" 14 | "github.com/iron-io/iron_go/api" 15 | "github.com/iron-io/iron_go/config" 16 | "io/ioutil" 17 | "log" 18 | "text/template" 19 | ) 20 | 21 | type ( 22 | TaskResponse struct { 23 | Message string `json:"msg"` 24 | Tasks []Task `json:"tasks"` 25 | } 26 | 27 | Task struct { 28 | Id string `json:"id"` 29 | } 30 | ) 31 | 32 | // payload defines a sample payload document 33 | var payload = `{"tasks":[ 34 | { 35 | "code_name" : "Worker-Name", 36 | "timeout" : 20, 37 | "payload" : "{ \"key\" : \"value", \"key\" : \"value\" }" 38 | }]}` 39 | 40 | func main() { 41 | // Create your configuration for iron_worker 42 | // Find these value in credentials 43 | config := config.Config("iron_worker") 44 | config.ProjectId = "your_project_id" 45 | config.Token = "your_token" 46 | 47 | // Create your endpoint url for tasks 48 | url := api.ActionEndpoint(config, "tasks") 49 | log.Printf("Url: %s\n", url.URL.String()) 50 | 51 | // Convert the payload to a slice of bytes 52 | postData := bytes.NewBufferString(payload) 53 | 54 | // Post the request to Iron.io 55 | resp, err := url.Request("POST", postData) 56 | defer resp.Body.Close() 57 | if err != nil { 58 | log.Println(err) 59 | return 60 | } 61 | 62 | // Capture the response 63 | body, err := ioutil.ReadAll(resp.Body) 64 | if err != nil { 65 | log.Println(err) 66 | return 67 | } 68 | 69 | // Unmarshall to struct 70 | taskResponse := &TaskResponse{} 71 | err = json.Unmarshal(body, taskResponse) 72 | if err != nil { 73 | log.Printf("%v\n", err) 74 | return 75 | } 76 | 77 | // Or you can Unmarshall to map 78 | results := map[string]interface{}{} 79 | err = json.Unmarshal(body, &results) 80 | if err != nil { 81 | log.Printf("%v\n", err) 82 | return 83 | } 84 | 85 | // Pretty print the response 86 | prettyPrint(taskResponse) 87 | } 88 | 89 | func prettyPrint(taskResponse *TaskResponse) { 90 | prettyTemplate := template.Must(template.New("pretty").Parse(prettyPrintFormat())) 91 | 92 | tasks := "\n" 93 | tasks += "\"msg\": " + taskResponse.Message + "\n" 94 | display := new(bytes.Buffer) 95 | 96 | for _, task := range taskResponse.Tasks { 97 | display.Reset() 98 | prettyTemplate.Execute(display, task) 99 | tasks += fmt.Sprintf("%s,\n", display.String()) 100 | } 101 | 102 | log.Printf(tasks) 103 | } 104 | 105 | func prettyPrintFormat() string { 106 | return `{ 107 | "id": "{{.Id}}", 108 | }` 109 | } 110 | -------------------------------------------------------------------------------- /mq/mq.go: -------------------------------------------------------------------------------- 1 | // IronMQ (elastic message queue) client library 2 | package mq 3 | 4 | import ( 5 | "errors" 6 | "time" 7 | 8 | "github.com/iron-io/iron_go/api" 9 | "github.com/iron-io/iron_go/config" 10 | ) 11 | 12 | type Queue struct { 13 | Settings config.Settings 14 | Name string 15 | } 16 | 17 | type QueueSubscriber struct { 18 | URL string `json:"url"` 19 | Headers map[string]string `json:"headers,omitempty"` 20 | } 21 | 22 | type QueueInfo struct { 23 | Id string `json:"id,omitempty"` 24 | Name string `json:"name,omitempty"` 25 | PushType string `json:"push_type,omitempty"` 26 | Reserved int `json:"reserved,omitempty"` 27 | RetriesDelay int `json:"retries_delay,omitempty"` 28 | Retries int `json:"retries,omitempty"` 29 | Size int `json:"size,omitempty"` 30 | Subscribers []QueueSubscriber `json:"subscribers,omitempty"` 31 | Alerts []Alert `json:"alerts,omitempty"` 32 | TotalMessages int `json:"total_messages,omitempty"` 33 | ErrorQueue string `json:"error_queue,omitempty"` 34 | } 35 | 36 | type Message struct { 37 | Id string `json:"id,omitempty"` 38 | Body string `json:"body"` 39 | // Timeout is the amount of time in seconds allowed for processing the 40 | // message. 41 | Timeout int64 `json:"timeout,omitempty"` 42 | // Delay is the amount of time in seconds to wait before adding the message 43 | // to the queue. 44 | Delay int64 `json:"delay,omitempty"` 45 | ReservedCount int64 `json:"reserved_count,omitempty"` 46 | q Queue 47 | } 48 | 49 | type PushStatus struct { 50 | Retried int `json:"retried"` 51 | StatusCode int `json:"status_code"` 52 | Status string `json:"status"` 53 | } 54 | 55 | type Subscriber struct { 56 | Retried int `json:"retried"` 57 | StatusCode int `json:"status_code"` 58 | Status string `json:"status"` 59 | URL string `json:"url"` 60 | } 61 | 62 | type Alert struct { 63 | Type string `json:"type"` 64 | Direction string `json:direction` 65 | Trigger int `json:trigger` 66 | Queue string `queue` 67 | } 68 | 69 | func New(queueName string) *Queue { 70 | return &Queue{Settings: config.Config("iron_mq"), Name: queueName} 71 | } 72 | 73 | // ConfigNew uses the specified settings over configuration specified in an iron.json file or 74 | // environment variables to return a Queue object capable of acquiring information about or 75 | // modifying the queue specified by queueName. 76 | func ConfigNew(queueName string, settings *config.Settings) Queue { 77 | return Queue{Settings: config.ManualConfig("iron_mq", settings), Name: queueName} 78 | } 79 | 80 | func ListSettingsQueues(settings config.Settings, page int, perPage int) (queues []Queue, err error) { 81 | out := []struct { 82 | Id string 83 | Project_id string 84 | Name string 85 | }{} 86 | 87 | q := New("") 88 | q.Settings = settings 89 | err = q.queues(). 90 | QueryAdd("page", "%d", page). 91 | QueryAdd("per_page", "%d", perPage). 92 | Req("GET", nil, &out) 93 | if err != nil { 94 | return 95 | } 96 | 97 | queues = make([]Queue, 0, len(out)) 98 | for _, item := range out { 99 | queues = append(queues, Queue{ 100 | Settings: q.Settings, 101 | Name: item.Name, 102 | }) 103 | } 104 | 105 | return 106 | } 107 | 108 | func ListProjectQueues(projectId string, token string, page int, perPage int) (queues []Queue, err error) { 109 | settings := config.Config("iron_mq") 110 | settings.ProjectId = projectId 111 | settings.Token = token 112 | return ListSettingsQueues(settings, page, perPage) 113 | } 114 | 115 | func ListQueues(page, perPage int) (queues []Queue, err error) { 116 | settings := config.Config("iron_mq") 117 | return ListProjectQueues(settings.ProjectId, settings.Token, page, perPage) 118 | } 119 | 120 | func (q Queue) queues(s ...string) *api.URL { return api.Action(q.Settings, "queues", s...) } 121 | 122 | // This method is left to support backward compatibility. 123 | // This method is replaced by func ListQueues(page, perPage int) (queues []Queue, err error) 124 | func (q Queue) ListQueues(page, perPage int) (queues []Queue, err error) { 125 | return ListQueues(page, perPage) 126 | } 127 | 128 | func (q Queue) Info() (QueueInfo, error) { 129 | qi := QueueInfo{} 130 | err := q.queues(q.Name).Req("GET", nil, &qi) 131 | return qi, err 132 | } 133 | 134 | func (q Queue) Update(qi QueueInfo) (QueueInfo, error) { 135 | out := QueueInfo{} 136 | err := q.queues(q.Name).Req("POST", qi, &out) 137 | return out, err 138 | } 139 | 140 | func (q Queue) Delete() (bool, error) { 141 | err := q.queues(q.Name).Req("DELETE", nil, nil) 142 | success := err == nil 143 | return success, err 144 | } 145 | 146 | type Subscription struct { 147 | PushType string 148 | Retries int 149 | RetriesDelay int 150 | } 151 | 152 | // RemoveSubscribers removes subscribers. 153 | func (q Queue) RemoveSubscribers(subscribers ...string) (err error) { 154 | qi := QueueInfo{Subscribers: make([]QueueSubscriber, len(subscribers))} 155 | for i, subscriber := range subscribers { 156 | qi.Subscribers[i].URL = subscriber 157 | } 158 | return q.queues(q.Name, "subscribers").Req("DELETE", &qi, nil) 159 | } 160 | 161 | // AddSubscribers adds subscribers. 162 | func (q Queue) AddSubscribers(subscribers ...string) (err error) { 163 | qi := QueueInfo{Subscribers: make([]QueueSubscriber, len(subscribers))} 164 | for i, subscriber := range subscribers { 165 | qi.Subscribers[i].URL = subscriber 166 | } 167 | return q.queues(q.Name, "subscribers").Req("POST", &qi, nil) 168 | } 169 | 170 | func (q Queue) PushString(body string) (id string, err error) { 171 | ids, err := q.PushStrings(body) 172 | if err != nil { 173 | return 174 | } 175 | return ids[0], nil 176 | } 177 | 178 | // Push adds one or more messages to the end of the queue using IronMQ's defaults: 179 | // timeout - 60 seconds 180 | // delay - none 181 | // 182 | // Identical to PushMessages with Message{Timeout: 60, Delay: 0} 183 | func (q Queue) PushStrings(bodies ...string) (ids []string, err error) { 184 | msgs := make([]*Message, 0, len(bodies)) 185 | for _, body := range bodies { 186 | msgs = append(msgs, &Message{Body: body}) 187 | } 188 | 189 | return q.PushMessages(msgs...) 190 | } 191 | 192 | func (q Queue) PushMessage(msg *Message) (id string, err error) { 193 | ids, err := q.PushMessages(msg) 194 | if err != nil { 195 | return 196 | } 197 | return ids[0], nil 198 | } 199 | 200 | func (q Queue) PushMessages(msgs ...*Message) (ids []string, err error) { 201 | in := struct { 202 | Messages []*Message `json:"messages"` 203 | }{Messages: msgs} 204 | 205 | out := struct { 206 | IDs []string `json:"ids"` 207 | Msg string `json:"msg"` 208 | }{} 209 | 210 | err = q.queues(q.Name, "messages").Req("POST", &in, &out) 211 | return out.IDs, err 212 | } 213 | 214 | // Get reserves a message from the queue. 215 | // The message will not be deleted, but will be reserved until the timeout 216 | // expires. If the timeout expires before the message is deleted, the message 217 | // will be placed back onto the queue. 218 | // As a result, be sure to Delete a message after you're done with it. 219 | func (q Queue) Get() (msg *Message, err error) { 220 | msgs, err := q.GetN(1) 221 | if err != nil { 222 | return 223 | } 224 | 225 | if len(msgs) > 0 { 226 | msg = msgs[0] 227 | } else { 228 | err = errors.New("Couldn't get a single message") 229 | } 230 | 231 | return 232 | } 233 | 234 | // get N messages 235 | func (q Queue) GetN(n int) (msgs []*Message, err error) { 236 | return q.GetNWithTimeoutAndWait(n, 0, 0) 237 | } 238 | 239 | func (q Queue) GetNWithTimeout(n, timeout int) (msgs []*Message, err error) { 240 | return q.GetNWithTimeoutAndWait(n, timeout, 0) 241 | } 242 | 243 | func (q Queue) GetNWithTimeoutAndWait(n, timeout, wait int) (msgs []*Message, err error) { 244 | out := struct { 245 | Messages []*Message `json:"messages"` 246 | }{} 247 | 248 | err = q.queues(q.Name, "messages"). 249 | QueryAdd("n", "%d", n). 250 | QueryAdd("timeout", "%d", timeout). 251 | QueryAdd("wait", "%d", wait). 252 | Req("GET", nil, &out) 253 | if err != nil { 254 | return 255 | } 256 | 257 | for _, msg := range out.Messages { 258 | msg.q = q 259 | } 260 | 261 | return out.Messages, nil 262 | } 263 | 264 | func (q Queue) Peek() (msg *Message, err error) { 265 | msgs, err := q.PeekN(1) 266 | if err != nil { 267 | return 268 | } 269 | 270 | if len(msgs) > 0 { 271 | msg = msgs[0] 272 | } else { 273 | err = errors.New("Couldn't get a single message") 274 | } 275 | 276 | return 277 | } 278 | 279 | // peek N messages 280 | func (q Queue) PeekN(n int) (msgs []*Message, err error) { 281 | msgs, err = q.PeekNWithTimeout(n, 0) 282 | 283 | return 284 | } 285 | 286 | func (q Queue) PeekNWithTimeout(n, timeout int) (msgs []*Message, err error) { 287 | out := struct { 288 | Messages []*Message `json:"messages"` 289 | }{} 290 | 291 | err = q.queues(q.Name, "messages", "peek"). 292 | QueryAdd("n", "%d", n). 293 | QueryAdd("timeout", "%d", timeout). 294 | Req("GET", nil, &out) 295 | if err != nil { 296 | return 297 | } 298 | 299 | for _, msg := range out.Messages { 300 | msg.q = q 301 | } 302 | 303 | return out.Messages, nil 304 | } 305 | 306 | // Delete all messages in the queue 307 | func (q Queue) Clear() (err error) { 308 | return q.queues(q.Name, "clear").Req("POST", nil, nil) 309 | } 310 | 311 | // Delete message from queue 312 | func (q Queue) DeleteMessage(msgId string) (err error) { 313 | return q.queues(q.Name, "messages", msgId).Req("DELETE", nil, nil) 314 | } 315 | 316 | func (q Queue) DeleteMessages(messages []*Message) error { 317 | values := make([]string, len(messages)) 318 | 319 | for i, val := range messages { 320 | values[i] = val.Id 321 | } 322 | in := struct { 323 | Ids []string `json:"ids"` 324 | }{ 325 | Ids: values, 326 | } 327 | return q.queues(q.Name, "messages").Req("DELETE", in, nil) 328 | } 329 | 330 | // Reset timeout of message to keep it reserved 331 | func (q Queue) TouchMessage(msgId string) (err error) { 332 | return q.queues(q.Name, "messages", msgId, "touch").Req("POST", nil, nil) 333 | } 334 | 335 | // Put message back in the queue, message will be available after +delay+ seconds. 336 | func (q Queue) ReleaseMessage(msgId string, delay int64) (err error) { 337 | in := struct { 338 | Delay int64 `json:"delay"` 339 | }{Delay: delay} 340 | return q.queues(q.Name, "messages", msgId, "release").Req("POST", &in, nil) 341 | } 342 | 343 | func (q Queue) MessageSubscribers(msgId string) ([]*Subscriber, error) { 344 | out := struct { 345 | Subscribers []*Subscriber `json:"subscribers"` 346 | }{} 347 | err := q.queues(q.Name, "messages", msgId, "subscribers").Req("GET", nil, &out) 348 | return out.Subscribers, err 349 | } 350 | 351 | func (q Queue) MessageSubscribersPollN(msgId string, n int) ([]*Subscriber, error) { 352 | subs, err := q.MessageSubscribers(msgId) 353 | for { 354 | time.Sleep(100 * time.Millisecond) 355 | subs, err = q.MessageSubscribers(msgId) 356 | if err != nil { 357 | return subs, err 358 | } 359 | if len(subs) >= n && actualPushStatus(subs) { 360 | return subs, nil 361 | } 362 | } 363 | return subs, err 364 | } 365 | 366 | func actualPushStatus(subs []*Subscriber) bool { 367 | for _, sub := range subs { 368 | if sub.Status == "queued" { 369 | return false 370 | } 371 | } 372 | 373 | return true 374 | } 375 | 376 | func (q Queue) AddAlerts(alerts ...*Alert) (err error) { 377 | in := struct { 378 | Alerts []*Alert `json:"alerts"` 379 | }{Alerts: alerts} 380 | return q.queues(q.Name, "alerts").Req("POST", &in, nil) 381 | } 382 | 383 | func (q Queue) UpdateAlerts(alerts ...*Alert) (err error) { 384 | in := struct { 385 | Alerts []*Alert `json:"alerts"` 386 | }{Alerts: alerts} 387 | return q.queues(q.Name, "alerts").Req("PUT", &in, nil) 388 | } 389 | 390 | func (q Queue) RemoveAllAlerts() (err error) { 391 | return q.queues(q.Name, "alerts").Req("DELETE", nil, nil) 392 | } 393 | 394 | type AlertInfo struct { 395 | Id string `json:"id"` 396 | } 397 | 398 | func (q Queue) RemoveAlerts(alertIds ...string) (err error) { 399 | in := struct { 400 | Alerts []AlertInfo `json:"alerts"` 401 | }{Alerts: make([]AlertInfo, len(alertIds))} 402 | for i, alertId := range alertIds { 403 | (in.Alerts[i]).Id = alertId 404 | } 405 | return q.queues(q.Name, "alerts").Req("DELETE", &in, nil) 406 | } 407 | 408 | func (q Queue) RemoveAlert(alertId string) (err error) { 409 | return q.queues(q.Name, "alerts", alertId).Req("DELETE", nil, nil) 410 | } 411 | 412 | // Delete message from queue 413 | func (m Message) Delete() (err error) { 414 | return m.q.DeleteMessage(m.Id) 415 | } 416 | 417 | // Reset timeout of message to keep it reserved 418 | func (m Message) Touch() (err error) { 419 | return m.q.TouchMessage(m.Id) 420 | } 421 | 422 | // Put message back in the queue, message will be available after +delay+ seconds. 423 | func (m Message) Release(delay int64) (err error) { 424 | return m.q.ReleaseMessage(m.Id, delay) 425 | } 426 | 427 | func (m Message) Subscribers() (interface{}, error) { 428 | return m.q.MessageSubscribers(m.Id) 429 | } 430 | -------------------------------------------------------------------------------- /mq/mq_examples_test.go: -------------------------------------------------------------------------------- 1 | package mq_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/iron-io/iron_go/mq" 6 | ) 7 | 8 | var p = fmt.Println 9 | 10 | func assert(b bool, msg ...interface{}) { 11 | if !b { 12 | panic(fmt.Sprintln(msg...)) 13 | } 14 | } 15 | 16 | func Example1PushingMessagesToTheQueue() { 17 | // use a queue named "test_queue" to push/get messages 18 | q := mq.New("test_queue") 19 | 20 | total := 0 21 | 22 | id, err := q.PushString("Hello, World!") 23 | assert(err == nil, err) 24 | assert(len(id) > 1, len(id)) 25 | total++ 26 | 27 | // You can also pass multiple messages in a single call. 28 | ids, err := q.PushStrings("Message 1", "Message 2") 29 | assert(err == nil, err) 30 | assert(len(ids) == 2, len(ids)) 31 | total += len(ids) 32 | 33 | // To control parameters like timeout and delay, construct your own message. 34 | id, err = q.PushMessage(&mq.Message{Timeout: 60, Delay: 0, Body: "Hi there"}) 35 | assert(err == nil, err) 36 | assert(len(id) > 10, len(id)) 37 | total++ 38 | 39 | // And finally, all that can be done in bulk as well. 40 | ids, err = q.PushMessages( 41 | &mq.Message{Timeout: 60, Delay: 0, Body: "The first"}, 42 | &mq.Message{Timeout: 60, Delay: 0, Body: "The second"}, 43 | &mq.Message{Timeout: 60, Delay: 0, Body: "The third"}, 44 | &mq.Message{Timeout: 60, Delay: 0, Body: "The fifth"}, 45 | ) 46 | assert(err == nil, err) 47 | assert(len(ids) == 4, len(ids)) 48 | total += len(ids) 49 | 50 | p("pushed a total of", total, "messages") 51 | 52 | // Output: 53 | // pushed a total of 8 messages 54 | } 55 | 56 | func Example2GettingMessagesOffTheQueue() { 57 | q := mq.New("test_queue") 58 | 59 | // get a single message 60 | msg, err := q.Get() 61 | assert(err == nil, err) 62 | fmt.Printf("The message says: %q\n", msg.Body) 63 | 64 | // when we're done handling a message, we have to delete it, or it 65 | // will be put back into the queue after a timeout. 66 | 67 | // get 5 messages 68 | msgs, err := q.GetN(5) 69 | assert(err == nil, err) 70 | fmt.Println("Got", len(msgs), "messages from", q.Name) 71 | 72 | for _, m := range append(msgs, msg) { 73 | m.Delete() 74 | } 75 | 76 | // Output: 77 | // The message says: "Hello, World!" 78 | // Got 5 messages from test_queue 79 | } 80 | 81 | func Example3ClearQueue() { 82 | q := mq.New("test_queue") 83 | 84 | info, err := q.Info() 85 | 86 | p(err) 87 | p("Before Clean(); Name:", info.Name, "Size:", info.Size) 88 | 89 | err = q.Clear() 90 | p(err) 91 | 92 | info, err = q.Info() 93 | 94 | p(err) 95 | p("After Clean(); Name:", info.Name, "Size:", info.Size) 96 | 97 | // Output: 98 | // 99 | // Before Clean(); Name: test_queue Size: 2 100 | // 101 | // 102 | // After Clean(); Name: test_queue Size: 0 103 | } 104 | -------------------------------------------------------------------------------- /mq/mq_test.go: -------------------------------------------------------------------------------- 1 | package mq_test 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "testing" 7 | "time" 8 | 9 | "github.com/iron-io/iron_go/mq" 10 | . "github.com/jeffh/go.bdd" 11 | ) 12 | 13 | func TestUrl(t *testing.T) { 14 | fmt.Println("Testing URL with spaces") 15 | mq := mq.New("MyProject - Prod") 16 | id, err := mq.PushString("hello") 17 | if err != nil { 18 | t.Fatal("No good", err) 19 | } 20 | fmt.Println("id:", id) 21 | info, err := mq.Info() 22 | if err != nil { 23 | fmt.Println("ERROR:", err) 24 | t.Fatal("No good", err) 25 | } 26 | fmt.Println(info) 27 | } 28 | 29 | func TestEverything(t *testing.T) { 30 | defer PrintSpecReport() 31 | 32 | qname := "queuename3" 33 | 34 | Describe("IronMQ", func() { 35 | It("Deletes all existing messages", func() { 36 | c := mq.New(qname) 37 | c.PushString("hello") // just to ensure queue exists 38 | Expect(c.Clear(), ToBeNil) 39 | 40 | info, err := c.Info() 41 | Expect(err, ToBeNil) 42 | Expect(info.Size, ToEqual, 0x0) 43 | }) 44 | 45 | It("Pushes ands gets a message", func() { 46 | c := mq.New(qname) 47 | id1, err := c.PushString("just a little test") 48 | Expect(err, ToBeNil) 49 | defer c.DeleteMessage(id1) 50 | 51 | msg, err := c.Get() 52 | Expect(err, ToBeNil) 53 | 54 | Expect(msg, ToNotBeNil) 55 | Expect(msg.Id, ToDeepEqual, id1) 56 | Expect(msg.Body, ToDeepEqual, "just a little test") 57 | }) 58 | 59 | It("Delete messages", func() { 60 | q := mq.New(qname) 61 | 62 | strings := []string{} 63 | for n := 0; n < 100; n++ { 64 | strings = append(strings, fmt.Sprint("test: ", n)) 65 | } 66 | 67 | _, err := q.PushStrings(strings...) 68 | Expect(err, ToBeNil) 69 | 70 | info, err := q.Info() 71 | Expect(err, ToBeNil) 72 | Expect(info.Size, ToEqual, 100) 73 | 74 | msgs, err := q.GetN(100) 75 | Expect(err, ToBeNil) 76 | 77 | Expect(q.DeleteMessages(msgs), ToBeNil) 78 | 79 | info, err = q.Info() 80 | Expect(err, ToBeNil) 81 | Expect(info.Size, ToEqual, 0) 82 | }) 83 | 84 | It("clears the queue", func() { 85 | q := mq.New(qname) 86 | 87 | strings := []string{} 88 | for n := 0; n < 100; n++ { 89 | strings = append(strings, fmt.Sprint("test: ", n)) 90 | } 91 | 92 | _, err := q.PushStrings(strings...) 93 | Expect(err, ToBeNil) 94 | 95 | info, err := q.Info() 96 | Expect(err, ToBeNil) 97 | Expect(info.Size, ToEqual, 100) 98 | 99 | Expect(q.Clear(), ToBeNil) 100 | 101 | info, err = q.Info() 102 | Expect(err, ToBeNil) 103 | Expect(info.Size, ToEqual, 0) 104 | }) 105 | 106 | It("Lists all queues", func() { 107 | c := mq.New(qname) 108 | queues, err := c.ListQueues(0, 100) // can't check the caches value just yet. 109 | Expect(err, ToBeNil) 110 | found := false 111 | for _, queue := range queues { 112 | if queue.Name == qname { 113 | found = true 114 | break 115 | } 116 | } 117 | Expect(found, ToEqual, true) 118 | }) 119 | 120 | It("releases a message", func() { 121 | c := mq.New(qname) 122 | 123 | id, err := c.PushString("trying") 124 | Expect(err, ToBeNil) 125 | 126 | msg, err := c.Get() 127 | Expect(err, ToBeNil) 128 | 129 | err = msg.Release(3) 130 | Expect(err, ToBeNil) 131 | 132 | msg, err = c.Get() 133 | Expect(msg, ToBeNil) 134 | 135 | time.Sleep(3 * time.Second) 136 | 137 | msg, err = c.Get() 138 | Expect(err, ToBeNil) 139 | Expect(msg.Id, ToEqual, id) 140 | }) 141 | 142 | It("updates a queue", func() { 143 | c := mq.New("pushqueue") 144 | info, err := c.Info() 145 | qi := mq.QueueInfo{PushType: "multicast"} 146 | rc, err := c.Update(qi) 147 | Expect(err, ToBeNil) 148 | Expect(info.Id, ToEqual, rc.Id) 149 | }) 150 | It("Adds and removes subscribers", func() { 151 | queue := mq.New("addSubscribersTest-" + strconv.Itoa(time.Now().Nanosecond())) 152 | defer queue.Delete() 153 | qi := mq.QueueInfo{PushType: "multicast"} 154 | qi, err := queue.Update(qi) 155 | Expect(qi.PushType, ToEqual, "multicast") 156 | Expect(err, ToBeNil) 157 | err = queue.AddSubscribers("http://server1") 158 | Expect(err, ToBeNil) 159 | info, err := queue.Info() 160 | Expect(err, ToBeNil) 161 | Expect(len(info.Subscribers), ToEqual, 1) 162 | err = queue.AddSubscribers("http://server2", "http://server3") 163 | Expect(err, ToBeNil) 164 | info, err = queue.Info() 165 | Expect(err, ToBeNil) 166 | Expect(len(info.Subscribers), ToEqual, 3) 167 | err = queue.RemoveSubscribers("http://server2") 168 | Expect(err, ToBeNil) 169 | info, err = queue.Info() 170 | Expect(err, ToBeNil) 171 | Expect(len(info.Subscribers), ToEqual, 2) 172 | err = queue.RemoveSubscribers("http://server1", "http://server3") 173 | Expect(err, ToBeNil) 174 | info, err = queue.Info() 175 | Expect(err, ToBeNil) 176 | Expect(len(info.Subscribers), ToEqual, 0) 177 | 178 | }) 179 | }) 180 | } 181 | 182 | func init() { 183 | 184 | } 185 | -------------------------------------------------------------------------------- /worker/methods.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "archive/zip" 5 | "bytes" 6 | "encoding/json" 7 | "io/ioutil" 8 | "mime/multipart" 9 | "net/http" 10 | "time" 11 | 12 | "github.com/iron-io/iron_go/api" 13 | ) 14 | 15 | type Schedule struct { 16 | CodeName string `json:"code_name"` 17 | Delay *time.Duration `json:"delay"` 18 | EndAt *time.Time `json:"end_at"` 19 | MaxConcurrency *int `json:"max_concurrency"` 20 | Name string `json:"name"` 21 | Payload string `json:"payload"` 22 | Priority *int `json:"priority"` 23 | RunEvery *int `json:"run_every"` 24 | RunTimes *int `json:"run_times"` 25 | StartAt *time.Time `json:"start_at"` 26 | Cluster string `json:"cluster"` 27 | Label string `json:"label"` 28 | } 29 | 30 | type ScheduleInfo struct { 31 | CodeName string `json:"code_name"` 32 | CreatedAt time.Time `json:"created_at"` 33 | EndAt time.Time `json:"end_at"` 34 | Id string `json:"id"` 35 | LastRunTime time.Time `json:"last_run_time"` 36 | MaxConcurrency int `json:"max_concurrency"` 37 | Msg string `json:"msg"` 38 | NextStart time.Time `json:"next_start"` 39 | ProjectId string `json:"project_id"` 40 | RunCount int `json:"run_count"` 41 | RunTimes int `json:"run_times"` 42 | StartAt time.Time `json:"start_at"` 43 | Status string `json:"status"` 44 | UpdatedAt time.Time `json:"updated_at"` 45 | } 46 | 47 | type Task struct { 48 | CodeName string `json:"code_name"` 49 | Payload string `json:"payload"` 50 | Priority int `json:"priority"` 51 | Timeout *time.Duration `json:"timeout"` 52 | Delay *time.Duration `json:"delay"` 53 | Cluster string `json:"cluster"` 54 | Label string `json:"label"` 55 | } 56 | 57 | type TaskInfo struct { 58 | CodeHistoryId string `json:"code_history_id"` 59 | CodeId string `json:"code_id"` 60 | CodeName string `json:"code_name"` 61 | CodeRev string `json:"code_rev"` 62 | Id string `json:"id"` 63 | Payload string `json:"payload"` 64 | ProjectId string `json:"project_id"` 65 | Status string `json:"status"` 66 | Msg string `json:"msg,omitempty"` 67 | ScheduleId string `json:"schedule_id"` 68 | Duration int `json:"duration"` 69 | RunTimes int `json:"run_times"` 70 | Timeout int `json:"timeout"` 71 | Percent int `json:"percent,omitempty"` 72 | CreatedAt time.Time `json:"created_at"` 73 | UpdatedAt time.Time `json:"updated_at"` 74 | StartTime time.Time `json:"start_time"` 75 | EndTime time.Time `json:"end_time"` 76 | } 77 | 78 | type CodeSource map[string][]byte // map[pathInZip]code 79 | 80 | type Code struct { 81 | Id string `json:"id,omitempty"` 82 | Name string `json:"name"` 83 | Runtime string `json:"runtime,omitempty"` 84 | FileName string `json:"file_name,omitempty"` 85 | Config string `json:"config,omitempty"` 86 | MaxConcurrency int `json:"max_concurrency,omitempty"` 87 | Retries int `json:"retries,omitempty"` 88 | Stack string `json:"stack,omitempty"` 89 | Image string `json:"image,omitempty"` 90 | Command string `json:"command,omitempty"` 91 | RetriesDelay time.Duration `json:"-"` 92 | Source CodeSource `json:"-"` 93 | Host string `json:"host,omitempty"` 94 | } 95 | 96 | type CodeInfo struct { 97 | Id string `json:"id"` 98 | LatestChecksum string `json:"latest_checksum,omitempty"` 99 | LatestHistoryId string `json:"latest_history_id"` 100 | Name string `json:"name"` 101 | ProjectId string `json:"project_id"` 102 | Runtime *string `json:"runtime,omitempty"` 103 | Rev int `json:"rev"` 104 | CreatedAt time.Time `json:"created_at"` 105 | UpdatedAt time.Time `json:"updated_at"` 106 | LatestChange time.Time `json:"latest_change"` 107 | } 108 | 109 | // CodePackageList lists code packages. 110 | // 111 | // The page argument decides the page of code packages you want to retrieve, starting from 0, maximum is 100. 112 | // 113 | // The perPage argument determines the number of code packages to return. Note 114 | // this is a maximum value, so there may be fewer packages returned if there 115 | // aren’t enough results. If this is < 1, 1 will be the default. Maximum is 100. 116 | func (w *Worker) CodePackageList(page, perPage int) (codes []CodeInfo, err error) { 117 | out := map[string][]CodeInfo{} 118 | 119 | err = w.codes(). 120 | QueryAdd("page", "%d", page). 121 | QueryAdd("per_page", "%d", perPage). 122 | Req("GET", nil, &out) 123 | if err != nil { 124 | return 125 | } 126 | 127 | return out["codes"], nil 128 | } 129 | 130 | // CodePackageUpload uploads a code package 131 | func (w *Worker) CodePackageUpload(code Code) (id string, err error) { 132 | client := http.Client{} 133 | 134 | body := &bytes.Buffer{} 135 | mWriter := multipart.NewWriter(body) 136 | 137 | // write meta-data 138 | mMetaWriter, err := mWriter.CreateFormField("data") 139 | if err != nil { 140 | return 141 | } 142 | jEncoder := json.NewEncoder(mMetaWriter) 143 | err = jEncoder.Encode(map[string]interface{}{ 144 | "name": code.Name, 145 | "runtime": code.Runtime, 146 | "file_name": code.FileName, 147 | "config": code.Config, 148 | "max_concurrency": code.MaxConcurrency, 149 | "retries": code.Retries, 150 | "retries_delay": code.RetriesDelay.Seconds(), 151 | }) 152 | if err != nil { 153 | return 154 | } 155 | 156 | // write the zip 157 | mFileWriter, err := mWriter.CreateFormFile("file", "worker.zip") 158 | if err != nil { 159 | return 160 | } 161 | zWriter := zip.NewWriter(mFileWriter) 162 | 163 | for sourcePath, sourceText := range code.Source { 164 | fWriter, err := zWriter.Create(sourcePath) 165 | if err != nil { 166 | return "", err 167 | } 168 | fWriter.Write([]byte(sourceText)) 169 | } 170 | 171 | zWriter.Close() 172 | 173 | // done with multipart 174 | mWriter.Close() 175 | 176 | req, err := http.NewRequest("POST", w.codes().URL.String(), body) 177 | if err != nil { 178 | return 179 | } 180 | 181 | req.Header.Set("Accept", "application/json") 182 | req.Header.Set("Accept-Encoding", "gzip/deflate") 183 | req.Header.Set("Authorization", "OAuth "+w.Settings.Token) 184 | req.Header.Set("Content-Type", mWriter.FormDataContentType()) 185 | req.Header.Set("User-Agent", w.Settings.UserAgent) 186 | 187 | // dumpRequest(req) NOTE: never do this here, it breaks stuff 188 | response, err := client.Do(req) 189 | if err != nil { 190 | return 191 | } 192 | if err = api.ResponseAsError(response); err != nil { 193 | return 194 | } 195 | 196 | // dumpResponse(response) 197 | 198 | data := struct { 199 | Id string `json:"id"` 200 | Msg string `json:"msg"` 201 | StatusCode int `json:"status_code"` 202 | }{} 203 | err = json.NewDecoder(response.Body).Decode(&data) 204 | if err != nil { 205 | return 206 | } 207 | 208 | return data.Id, err 209 | } 210 | 211 | // CodePackageInfo gets info about a code package 212 | func (w *Worker) CodePackageInfo(codeId string) (code CodeInfo, err error) { 213 | out := CodeInfo{} 214 | err = w.codes(codeId).Req("GET", nil, &out) 215 | return out, err 216 | } 217 | 218 | // CodePackageDelete deletes a code package 219 | func (w *Worker) CodePackageDelete(codeId string) (err error) { 220 | return w.codes(codeId).Req("DELETE", nil, nil) 221 | } 222 | 223 | // CodePackageDownload downloads a code package 224 | func (w *Worker) CodePackageDownload(codeId string) (code Code, err error) { 225 | out := Code{} 226 | err = w.codes(codeId, "download").Req("GET", nil, &out) 227 | return out, err 228 | } 229 | 230 | // CodePackageRevisions lists the revisions of a code pacakge 231 | func (w *Worker) CodePackageRevisions(codeId string) (code Code, err error) { 232 | out := Code{} 233 | err = w.codes(codeId, "revisions").Req("GET", nil, &out) 234 | return out, err 235 | } 236 | 237 | func (w *Worker) TaskList() (tasks []TaskInfo, err error) { 238 | out := map[string][]TaskInfo{} 239 | err = w.tasks().Req("GET", nil, &out) 240 | if err != nil { 241 | return 242 | } 243 | return out["tasks"], nil 244 | } 245 | 246 | type TaskListParams struct { 247 | CodeName string 248 | Label string 249 | Page int 250 | PerPage int 251 | FromTime time.Time 252 | ToTime time.Time 253 | Statuses []string 254 | } 255 | 256 | func (w *Worker) FilteredTaskList(params TaskListParams) (tasks []TaskInfo, err error) { 257 | out := map[string][]TaskInfo{} 258 | url := w.tasks() 259 | 260 | url.QueryAdd("code_name", "%s", params.CodeName) 261 | 262 | if params.Label != "" { 263 | url.QueryAdd("label", "%s", params.Label) 264 | } 265 | 266 | if params.Page > 0 { 267 | url.QueryAdd("page", "%d", params.Page) 268 | } 269 | 270 | if params.PerPage > 0 { 271 | url.QueryAdd("per_page", "%d", params.PerPage) 272 | } 273 | 274 | if fromTimeSeconds := params.FromTime.Unix(); fromTimeSeconds > 0 { 275 | url.QueryAdd("from_time", "%d", fromTimeSeconds) 276 | } 277 | 278 | if toTimeSeconds := params.ToTime.Unix(); toTimeSeconds > 0 { 279 | url.QueryAdd("to_time", "%d", toTimeSeconds) 280 | } 281 | 282 | for _, status := range params.Statuses { 283 | url.QueryAdd(status, "%d", true) 284 | } 285 | 286 | err = url.Req("GET", nil, &out) 287 | 288 | if err != nil { 289 | return 290 | } 291 | 292 | return out["tasks"], nil 293 | } 294 | 295 | // TaskQueue queues a task 296 | func (w *Worker) TaskQueue(tasks ...Task) (taskIds []string, err error) { 297 | outTasks := make([]map[string]interface{}, 0, len(tasks)) 298 | 299 | for _, task := range tasks { 300 | thisTask := map[string]interface{}{ 301 | "code_name": task.CodeName, 302 | "payload": task.Payload, 303 | "priority": task.Priority, 304 | "cluster": task.Cluster, 305 | "label": task.Label, 306 | } 307 | if task.Timeout != nil { 308 | thisTask["timeout"] = (*task.Timeout).Seconds() 309 | } 310 | if task.Delay != nil { 311 | thisTask["delay"] = int64((*task.Delay).Seconds()) 312 | } 313 | 314 | outTasks = append(outTasks, thisTask) 315 | } 316 | 317 | in := map[string][]map[string]interface{}{"tasks": outTasks} 318 | out := struct { 319 | Tasks []struct { 320 | Id string `json:"id"` 321 | } `json:"tasks"` 322 | Msg string `json:"msg"` 323 | }{} 324 | 325 | err = w.tasks().Req("POST", &in, &out) 326 | if err != nil { 327 | return 328 | } 329 | 330 | taskIds = make([]string, 0, len(out.Tasks)) 331 | for _, task := range out.Tasks { 332 | taskIds = append(taskIds, task.Id) 333 | } 334 | 335 | return 336 | } 337 | 338 | // TaskInfo gives info about a given task 339 | func (w *Worker) TaskInfo(taskId string) (task TaskInfo, err error) { 340 | out := TaskInfo{} 341 | err = w.tasks(taskId).Req("GET", nil, &out) 342 | return out, err 343 | } 344 | 345 | func (w *Worker) TaskLog(taskId string) (log []byte, err error) { 346 | response, err := w.tasks(taskId, "log").Request("GET", nil) 347 | if err != nil { 348 | return 349 | } 350 | 351 | log, err = ioutil.ReadAll(response.Body) 352 | return 353 | } 354 | 355 | // TaskCancel cancels a Task 356 | func (w *Worker) TaskCancel(taskId string) (err error) { 357 | _, err = w.tasks(taskId, "cancel").Request("POST", nil) 358 | return err 359 | } 360 | 361 | // TaskProgress sets a Task's Progress 362 | func (w *Worker) TaskProgress(taskId string, progress int, msg string) (err error) { 363 | payload := map[string]interface{}{ 364 | "msg": msg, 365 | "percent": progress, 366 | } 367 | 368 | err = w.tasks(taskId, "progress").Req("POST", payload, nil) 369 | return 370 | } 371 | 372 | // TaskQueueWebhook queues a Task from a Webhook 373 | func (w *Worker) TaskQueueWebhook() (err error) { return } 374 | 375 | // ScheduleList lists Scheduled Tasks 376 | func (w *Worker) ScheduleList() (schedules []ScheduleInfo, err error) { 377 | out := map[string][]ScheduleInfo{} 378 | err = w.schedules().Req("GET", nil, &out) 379 | if err != nil { 380 | return 381 | } 382 | return out["schedules"], nil 383 | } 384 | 385 | // Schedule a Task 386 | func (w *Worker) Schedule(schedules ...Schedule) (scheduleIds []string, err error) { 387 | outSchedules := make([]map[string]interface{}, 0, len(schedules)) 388 | 389 | for _, schedule := range schedules { 390 | sm := map[string]interface{}{ 391 | "code_name": schedule.CodeName, 392 | "name": schedule.Name, 393 | "payload": schedule.Payload, 394 | "label": schedule.Label, 395 | "cluster": schedule.Cluster, 396 | } 397 | if schedule.Delay != nil { 398 | sm["delay"] = (*schedule.Delay).Seconds() 399 | } 400 | if schedule.EndAt != nil { 401 | sm["end_at"] = *schedule.EndAt 402 | } 403 | if schedule.MaxConcurrency != nil { 404 | sm["max_concurrency"] = *schedule.MaxConcurrency 405 | } 406 | if schedule.Priority != nil { 407 | sm["priority"] = *schedule.Priority 408 | } 409 | if schedule.RunEvery != nil { 410 | sm["run_every"] = *schedule.RunEvery 411 | } 412 | if schedule.RunTimes != nil { 413 | sm["run_times"] = *schedule.RunTimes 414 | } 415 | if schedule.StartAt != nil { 416 | sm["start_at"] = *schedule.StartAt 417 | } 418 | outSchedules = append(outSchedules, sm) 419 | } 420 | 421 | in := map[string][]map[string]interface{}{"schedules": outSchedules} 422 | out := struct { 423 | Schedules []struct { 424 | Id string `json:"id"` 425 | } `json:"schedules"` 426 | Msg string `json:"msg"` 427 | }{} 428 | 429 | err = w.schedules().Req("POST", &in, &out) 430 | if err != nil { 431 | return 432 | } 433 | 434 | scheduleIds = make([]string, 0, len(out.Schedules)) 435 | 436 | for _, schedule := range out.Schedules { 437 | scheduleIds = append(scheduleIds, schedule.Id) 438 | } 439 | 440 | return 441 | } 442 | 443 | // ScheduleInfo gets info about a scheduled task 444 | func (w *Worker) ScheduleInfo(scheduleId string) (info ScheduleInfo, err error) { 445 | info = ScheduleInfo{} 446 | err = w.schedules(scheduleId).Req("GET", nil, &info) 447 | return info, nil 448 | } 449 | 450 | // ScheduleCancel cancels a scheduled task 451 | func (w *Worker) ScheduleCancel(scheduleId string) (err error) { 452 | _, err = w.schedules(scheduleId, "cancel").Request("POST", nil) 453 | return 454 | } 455 | -------------------------------------------------------------------------------- /worker/remote.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "io" 7 | "io/ioutil" 8 | "os" 9 | ) 10 | 11 | var ( 12 | TaskDir string 13 | envFlag string 14 | payloadFlag string 15 | TaskId string 16 | configFlag string 17 | ) 18 | 19 | // call this to parse flags before using the other methods. 20 | func ParseFlags() { 21 | flag.StringVar(&TaskDir, "d", "", "task dir") 22 | flag.StringVar(&envFlag, "e", "", "environment type") 23 | flag.StringVar(&payloadFlag, "payload", "", "payload file") 24 | flag.StringVar(&TaskId, "id", "", "task id") 25 | flag.StringVar(&configFlag, "config", "", "config file") 26 | flag.Parse() 27 | if os.Getenv("TASK_ID") != "" { 28 | TaskId = os.Getenv("TASK_ID") 29 | } 30 | if os.Getenv("TASK_DIR") != "" { 31 | TaskDir = os.Getenv("TASK_DIR") 32 | } 33 | if os.Getenv("PAYLOAD_FILE") != "" { 34 | payloadFlag = os.Getenv("PAYLOAD_FILE") 35 | } 36 | if os.Getenv("CONFIG_FILE") != "" { 37 | configFlag = os.Getenv("CONFIG_FILE") 38 | } 39 | } 40 | 41 | func PayloadReader() (io.ReadCloser, error) { 42 | return os.Open(payloadFlag) 43 | } 44 | 45 | func PayloadFromJSON(v interface{}) error { 46 | reader, err := PayloadReader() 47 | if err != nil { 48 | return err 49 | } 50 | defer reader.Close() 51 | return json.NewDecoder(reader).Decode(v) 52 | } 53 | 54 | func PayloadAsString() (string, error) { 55 | reader, err := PayloadReader() 56 | if err != nil { 57 | return "", err 58 | } 59 | defer reader.Close() 60 | 61 | b, err := ioutil.ReadAll(reader) 62 | if err != nil { 63 | return "", err 64 | } 65 | return string(b), nil 66 | } 67 | 68 | func ConfigReader() (io.ReadCloser, error) { 69 | return os.Open(configFlag) 70 | } 71 | 72 | func ConfigFromJSON(v interface{}) error { 73 | reader, err := ConfigReader() 74 | if err != nil { 75 | return err 76 | } 77 | defer reader.Close() 78 | return json.NewDecoder(reader).Decode(v) 79 | } 80 | 81 | func ConfigAsString() (string, error) { 82 | reader, err := ConfigReader() 83 | if err != nil { 84 | return "", err 85 | } 86 | defer reader.Close() 87 | 88 | b, err := ioutil.ReadAll(reader) 89 | if err != nil { 90 | return "", err 91 | } 92 | return string(b), nil 93 | } 94 | 95 | func IronTaskId() string { 96 | return TaskId 97 | } 98 | 99 | func IronTaskDir() string { 100 | return TaskDir 101 | } 102 | -------------------------------------------------------------------------------- /worker/worker.go: -------------------------------------------------------------------------------- 1 | // IronWorker (elastic computing) client library 2 | package worker 3 | 4 | import ( 5 | "time" 6 | 7 | "github.com/iron-io/iron_go/api" 8 | "github.com/iron-io/iron_go/config" 9 | ) 10 | 11 | type Worker struct { 12 | Settings config.Settings 13 | } 14 | 15 | func New() *Worker { 16 | return &Worker{Settings: config.Config("iron_worker")} 17 | } 18 | 19 | func (w *Worker) codes(s ...string) *api.URL { return api.Action(w.Settings, "codes", s...) } 20 | func (w *Worker) tasks(s ...string) *api.URL { return api.Action(w.Settings, "tasks", s...) } 21 | func (w *Worker) schedules(s ...string) *api.URL { return api.Action(w.Settings, "schedules", s...) } 22 | 23 | // exponential sleep between retries, replace this with your own preferred strategy 24 | func sleepBetweenRetries(previousDuration time.Duration) time.Duration { 25 | if previousDuration >= 60*time.Second { 26 | return previousDuration 27 | } 28 | return previousDuration + previousDuration 29 | } 30 | 31 | var GoCodeRunner = []byte(`#!/bin/sh 32 | root() { 33 | while [ $# -gt 0 ]; do 34 | if [ "$1" = "-d" ]; then 35 | printf "%s\n" "$2" 36 | break 37 | fi 38 | done 39 | } 40 | cd "$(root "$@")" 41 | chmod +x worker 42 | ./worker "$@" 43 | `) 44 | 45 | // WaitForTask returns a channel that will receive the completed task and is closed afterwards. 46 | // If an error occured during the wait, the channel will be closed. 47 | func (w *Worker) WaitForTask(taskId string) chan TaskInfo { 48 | out := make(chan TaskInfo) 49 | go func() { 50 | defer close(out) 51 | retryDelay := 100 * time.Millisecond 52 | 53 | for { 54 | info, err := w.TaskInfo(taskId) 55 | if err != nil { 56 | return 57 | } 58 | 59 | if info.Status == "queued" || info.Status == "running" { 60 | time.Sleep(retryDelay) 61 | retryDelay = sleepBetweenRetries(retryDelay) 62 | } else { 63 | out <- info 64 | return 65 | } 66 | } 67 | }() 68 | 69 | return out 70 | } 71 | 72 | func (w *Worker) WaitForTaskLog(taskId string) chan []byte { 73 | out := make(chan []byte) 74 | 75 | go func() { 76 | defer close(out) 77 | retryDelay := 100 * time.Millisecond 78 | 79 | for { 80 | log, err := w.TaskLog(taskId) 81 | if err != nil { 82 | e, ok := err.(api.HTTPResponseError) 83 | if ok && e.Response().StatusCode == 404 { 84 | time.Sleep(retryDelay) 85 | retryDelay = sleepBetweenRetries(retryDelay) 86 | continue 87 | } 88 | return 89 | } 90 | out <- log 91 | return 92 | } 93 | }() 94 | return out 95 | } 96 | 97 | func clamp(value, min, max int) int { 98 | if value < min { 99 | return min 100 | } else if value > max { 101 | return max 102 | } 103 | return value 104 | } 105 | -------------------------------------------------------------------------------- /worker/worker_test.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "testing" 7 | "time" 8 | 9 | // "github.com/iron-io/iron_go/worker" 10 | . "github.com/jeffh/go.bdd" 11 | ) 12 | 13 | func TestEverything(*testing.T) { 14 | defer PrintSpecReport() 15 | 16 | Describe("iron.io worker", func() { 17 | w := New() 18 | 19 | It("Prepares the specs by deleting all existing code packages", func() { 20 | codes, err := w.CodePackageList(0, 100) 21 | Expect(err, ToBeNil) 22 | for _, code := range codes { 23 | err = w.CodePackageDelete(code.Id) 24 | Expect(err, ToBeNil) 25 | } 26 | 27 | codes, err = w.CodePackageList(0, 100) 28 | Expect(err, ToBeNil) 29 | Expect(len(codes), ToEqual, 0) 30 | }) 31 | 32 | It("Creates a code package", func() { 33 | tempDir, err := ioutil.TempDir("", "iron-worker") 34 | Expect(err, ToBeNil) 35 | defer os.RemoveAll(tempDir) 36 | 37 | fd, err := os.Create(tempDir + "/main.go") 38 | Expect(err, ToBeNil) 39 | 40 | n, err := fd.WriteString(`package main; func main(){ println("Hello world!") }`) 41 | Expect(err, ToBeNil) 42 | Expect(n, ToEqual, 52) 43 | 44 | Expect(fd.Close(), ToBeNil) 45 | 46 | pkg, err := NewGoCodePackage("GoFun", fd.Name()) 47 | Expect(err, ToBeNil) 48 | 49 | id, err := w.CodePackageUpload(pkg) 50 | Expect(err, ToBeNil) 51 | 52 | info, err := w.CodePackageInfo(id) 53 | Expect(err, ToBeNil) 54 | Expect(info.Id, ToEqual, id) 55 | Expect(info.Name, ToEqual, "GoFun") 56 | Expect(info.Rev, ToEqual, 1) 57 | }) 58 | 59 | It("Queues a Task", func() { 60 | ids, err := w.TaskQueue(Task{CodeName: "GoFun"}) 61 | Expect(err, ToBeNil) 62 | 63 | id := ids[0] 64 | info, err := w.TaskInfo(id) 65 | Expect(err, ToBeNil) 66 | Expect(info.CodeName, ToEqual, "GoFun") 67 | 68 | select { 69 | case info = <-w.WaitForTask(id): 70 | Expect(info.Status, ToEqual, "complete") 71 | case <-time.After(5 * time.Second): 72 | panic("info timed out") 73 | } 74 | 75 | log, err := w.TaskLog(id) 76 | Expect(err, ToBeNil) 77 | Expect(log, ToDeepEqual, []byte("Hello world!\n")) 78 | }) 79 | 80 | It("Cancels a task", func() { 81 | delay := 10 * time.Second 82 | ids, err := w.TaskQueue(Task{CodeName: "GoFun", Delay: &delay}) 83 | Expect(err, ToBeNil) 84 | 85 | id := ids[0] 86 | err = w.TaskCancel(id) 87 | Expect(err, ToBeNil) 88 | 89 | info, err := w.TaskInfo(id) 90 | Expect(info.Status, ToEqual, "cancelled") 91 | }) 92 | 93 | It("Queues a lot of tasks and lists them", func() { 94 | delay := 100 * time.Second 95 | ids, err := w.TaskQueue(Task{CodeName: "GoFun", Delay: &delay}) 96 | Expect(err, ToBeNil) 97 | firstId := ids[0] 98 | time.Sleep(1 * time.Second) 99 | 100 | ids, err = w.TaskQueue(Task{CodeName: "GoFun", Delay: &delay}) 101 | Expect(err, ToBeNil) 102 | secondId := ids[0] 103 | 104 | tasks, err := w.TaskList() 105 | Expect(err, ToBeNil) 106 | 107 | Expect(tasks[0].CreatedAt.After(tasks[1].CreatedAt), ToEqual, true) 108 | Expect(tasks[0].Id, ToEqual, secondId) 109 | Expect(tasks[1].Id, ToEqual, firstId) 110 | }) 111 | 112 | It("Schedules a Task ", func() { 113 | delay := 10 * time.Second 114 | ids, err := w.Schedule(Schedule{ 115 | Name: "ScheduledGoFun", 116 | CodeName: "GoFun", 117 | Payload: "foobar", 118 | Delay: &delay, 119 | }) 120 | 121 | Expect(err, ToBeNil) 122 | id := ids[0] 123 | 124 | info, err := w.ScheduleInfo(id) 125 | Expect(err, ToBeNil) 126 | Expect(info.CodeName, ToEqual, "GoFun") 127 | Expect(info.Status, ToEqual, "scheduled") 128 | }) 129 | 130 | It("Cancels a scheduled task", func() { 131 | delay := 10 * time.Second 132 | ids, err := w.Schedule(Schedule{ 133 | Name: "ScheduledGoFun", 134 | CodeName: "GoFun", 135 | Payload: "foobar", 136 | Delay: &delay, 137 | }) 138 | 139 | Expect(err, ToBeNil) 140 | id := ids[0] 141 | 142 | err = w.ScheduleCancel(id) 143 | Expect(err, ToBeNil) 144 | 145 | info, err := w.ScheduleInfo(id) 146 | Expect(err, ToBeNil) 147 | Expect(info.CodeName, ToEqual, "GoFun") 148 | Expect(info.Status, ToEqual, "cancelled") 149 | }) 150 | }) 151 | } 152 | --------------------------------------------------------------------------------