├── .gitignore ├── LICENSE ├── README.md ├── config ├── config.exs ├── dev.exs ├── prod.exs └── test.exs ├── doc ├── Error.html ├── FleetApi.Api.html ├── FleetApi.Direct.html ├── FleetApi.Error.html ├── FleetApi.Etcd.html ├── FleetApi.Machine.html ├── FleetApi.Request.html ├── FleetApi.Unit.html ├── FleetApi.UnitOption.html ├── FleetApi.UnitState.html ├── FleetApi.html ├── README.html ├── css │ ├── elixir.css │ ├── full_list.css │ └── style.css ├── exceptions_list.html ├── index.html ├── js │ ├── app.js │ ├── full_list.js │ ├── highlight.pack.js │ └── jquery.js ├── modules_list.html ├── overview.html └── protocols_list.html ├── fixture └── custom_cassettes │ ├── delete_unit.json │ ├── etcd_delete_unit.json │ ├── etcd_get_api_discovery.json │ ├── etcd_get_unit.json │ ├── etcd_list_machines.json │ ├── etcd_list_unit_states.json │ ├── etcd_list_units.json │ ├── etcd_list_units_empty.json │ ├── etcd_list_units_multiple_calls.json │ ├── etcd_list_units_null.json │ ├── etcd_list_units_weird_response.json │ ├── etcd_response_503_response.json │ ├── etcd_response_no_nodes.json │ ├── etcd_set_unit_new.json │ ├── etcd_set_unit_update.json │ ├── get_api_discovery.json │ ├── get_unit.json │ ├── get_unit_empty_options.json │ ├── get_unit_missing_options.json │ ├── get_unit_null_options.json │ ├── list_machines.json │ ├── list_machines_empty.json │ ├── list_machines_null.json │ ├── list_machines_weird_response.json │ ├── list_unit_states.json │ ├── list_unit_states_empty.json │ ├── list_unit_states_null.json │ ├── list_unit_states_weird_response.json │ ├── list_units.json │ ├── list_units_empty.json │ ├── list_units_null.json │ ├── list_units_weird_response.json │ ├── set_unit_new.json │ └── set_unit_update.json ├── lib ├── fleet_api.ex └── fleet_api │ ├── api.ex │ ├── direct.ex │ ├── error.ex │ ├── etcd.ex │ ├── machine.ex │ ├── request.ex │ ├── unit.ex │ ├── unit_option.ex │ └── unit_state.ex ├── mix.exs ├── mix.lock └── test ├── direct_test.exs ├── etcd_test.exs ├── fleet_api_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | *.beam 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jordan Day 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 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FleetApi 2 | 3 | An elixir wrapper for the [Fleet API](https://github.com/coreos/fleet/blob/master/Documentation/api-v1.md). Connect to the API running on one of your fleet cluster nodes using either a direct node URL or an [etcd](https://etcd.io) etcd token. 4 | 5 | [![Build Status](https://semaphoreci.com/api/v1/projects/d90076a2-33bc-458a-88e1-1a36cf82040a/375538/badge.png)](https://semaphoreci.com/jordanday/fleet-api) [![Hex pm](http://img.shields.io/hexpm/v/fleet_api.svg?style=flat)](https://hex.pm/packages/fleet_api) 6 | 7 | ## Usage 8 | ### etcd token 9 | 10 | *Note that this is a config value you can set to override the port used to connect to the Fleet REST API when using an etcd token.* 11 | In your app's config, you can set 12 | 13 | ```elixir 14 | config :fleet_api, :etcd 15 | fix_port_number: true, 16 | api_port: 4001 17 | ``` 18 | To get the api to use the correct port, regardless of what might be stored in etcd. 19 | 20 | ```elixir 21 | {:ok, pid} = FleetApi.Etcd.start_link("your etcd token") 22 | {:ok, units} = FleetApi.Etcd.list_units(pid) 23 | 24 | [%FleetApi.Unit{currentState: "launched", desiredState: "launched", 25 | machineID: "820c30c0867844129d63f4409871ba39", name: "subgun-http.service", 26 | options: [%FleetApi.UnitOption{name: "Description", section: "Unit", 27 | value: "subgun"}, 28 | %FleetApi.UnitOption{name: "ExecStartPre", section: "Service", 29 | value: "-/usr/bin/docker kill subgun-%i"}, 30 | %FleetApi.UnitOption{name: "ExecStartPre", section: "Service", 31 | value: "-/usr/bin/docker rm subgun-%i"}...] 32 | ``` 33 | 34 | ### Direct node URL 35 | 36 | ```elixir 37 | {:ok, pid} = FleetApi.Direct.start_link("http://your-node-host-or-ip:7002") 38 | {:ok, units} = FleetApi.Direct.list_units(pid) 39 | 40 | [%FleetApi.Unit{currentState: "launched", desiredState: "launched", 41 | machineID: "820c30c0867844129d63f4409871ba39", name: "subgun-http.service", 42 | options: [%FleetApi.UnitOption{name: "Description", section: "Unit", 43 | value: "subgun"}, 44 | %FleetApi.UnitOption{name: "ExecStartPre", section: "Service", 45 | value: "-/usr/bin/docker kill subgun-%i"}, 46 | %FleetApi.UnitOption{name: "ExecStartPre", section: "Service", 47 | value: "-/usr/bin/docker rm subgun-%i"}...] 48 | ``` 49 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for third- 9 | # party users, it should be done in your mix.exs file. 10 | 11 | # Sample configuration: 12 | # 13 | # config :logger, :console, 14 | # level: :info, 15 | # format: "$date $time [$level] $metadata$message\n", 16 | # metadata: [:user_id] 17 | 18 | # It is also possible to import configuration files, relative to this 19 | # directory. For example, you can emulate configuration per environment 20 | # by uncommenting the line below and defining dev.exs, test.exs and such. 21 | # Configuration from the imported file will override the ones defined 22 | # here (which is why it is important to import them last). 23 | # 24 | 25 | # For some reason, when the nodes register themselves with etcd, they seem to 26 | # give an incorrect port number. Manually override the port used by setting 27 | # `fix_port_number` to true, and providing the correct port in `port_number`. 28 | config :fleet_api, :etcd, 29 | fix_port_number: true, 30 | api_port: 7002 31 | 32 | import_config "#{Mix.env}.exs" 33 | -------------------------------------------------------------------------------- /config/dev.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # config :fleet_api, 4 | # proxy: {"your-proxy.com", 80} -------------------------------------------------------------------------------- /config/prod.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | use Mix.Config 2 | 3 | # config :fleet_api, 4 | # proxy: {"your-proxy.com", 80} -------------------------------------------------------------------------------- /doc/Error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | Error 31 | 32 |

33 | 34 | 45 | 46 | 47 |
48 |

Wraps error messages received from the Fleet API.

49 | 50 |
51 | 52 | 53 | 54 | Source 55 | 56 | 57 | 58 |

Summary

59 | 60 | 61 | 62 | 63 | 64 | 65 |
from_map(error_map)
66 | 67 | 68 | 69 | 70 | 71 |
72 |

Functions

73 |
74 |
75 | from_map(error_map) 76 | 81 |
82 | 83 |
84 | 85 |
86 | 87 | Source 88 | 89 |
90 | 91 |
92 | 93 | 94 | 95 | 96 | 97 |
98 | 99 | 100 | -------------------------------------------------------------------------------- /doc/FleetApi.Api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Api 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Api 31 | 32 |

33 | 34 | 41 | 42 | 43 | 44 | 45 | Source 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /doc/FleetApi.Direct.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Direct 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Direct 31 | 32 |

33 | 34 | 45 | 46 | 47 |
48 |

Accesses the Fleet API via a directly-identified node URL.

49 | 50 |
51 | 52 | 53 | 54 | Source 55 | 56 | 57 | 58 |

Summary

59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 97 | 98 | 99 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
delete_unit(pid, unit_name)

Callback implementation for FleetApi.delete_unit/2

64 |
get_api_discovery(pid)

Callback implementation for FleetApi.get_api_discovery/1

71 |
get_node_url(pid)

Retrieves the Fleet node URL based on the URL provided when the GenServer 78 | was started

79 |
get_unit(pid, unit_name)

Callback implementation for FleetApi.get_unit/2

86 |
list_machines(pid)

Callback implementation for FleetApi.list_machines/1

93 |
list_unit_states(pid, opts \\ [])

Callback implementation for FleetApi.list_unit_states/2

100 |
list_units(pid)

Callback implementation for FleetApi.list_units/1

107 |
set_unit(pid, unit_name, unit)

Callback implementation for FleetApi.set_unit/3

114 |
start_link(node_url)
123 | 124 | 125 | 126 | 127 | 128 |
129 |

Functions

130 |
131 |
132 | delete_unit(pid, unit_name) 133 | 138 |
139 | 140 |
141 |

Callback implementation for FleetApi.delete_unit/2.

142 | 143 |
144 | 145 | Source 146 | 147 |
148 |
149 |
150 | get_api_discovery(pid) 151 | 156 |
157 | 158 |
159 |

Callback implementation for FleetApi.get_api_discovery/1.

160 | 161 |
162 | 163 | Source 164 | 165 |
166 |
167 |
168 | get_node_url(pid) 169 | 174 |
175 | 176 |

Specs:

177 |
    178 | 179 |
  • get_node_url(pid) :: String.t
  • 180 | 181 |
182 | 183 |
184 |

Retrieves the Fleet node URL based on the URL provided when the GenServer 185 | was started.

186 | 187 |
188 | 189 | Source 190 | 191 |
192 |
193 |
194 | get_unit(pid, unit_name) 195 | 200 |
201 | 202 |
203 |

Callback implementation for FleetApi.get_unit/2.

204 | 205 |
206 | 207 | Source 208 | 209 |
210 |
211 |
212 | list_machines(pid) 213 | 218 |
219 | 220 |
221 |

Callback implementation for FleetApi.list_machines/1.

222 | 223 |
224 | 225 | Source 226 | 227 |
228 |
229 |
230 | list_unit_states(pid, opts \\ []) 231 | 236 |
237 | 238 |
239 |

Callback implementation for FleetApi.list_unit_states/2.

240 | 241 |
242 | 243 | Source 244 | 245 |
246 |
247 |
248 | list_units(pid) 249 | 254 |
255 | 256 |
257 |

Callback implementation for FleetApi.list_units/1.

258 | 259 |
260 | 261 | Source 262 | 263 |
264 |
265 |
266 | set_unit(pid, unit_name, unit) 267 | 272 |
273 | 274 |
275 |

Callback implementation for FleetApi.set_unit/3.

276 | 277 |
278 | 279 | Source 280 | 281 |
282 |
283 |
284 | start_link(node_url) 285 | 290 |
291 | 292 |
293 | 294 |
295 | 296 | Source 297 | 298 |
299 | 300 |
301 | 302 | 303 | 304 | 305 | 306 |
307 | 308 | 309 | -------------------------------------------------------------------------------- /doc/FleetApi.Error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Error 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Error 31 | 32 |

33 | 34 | 47 | 48 | 49 |
50 |

Defines a FleetApi.Error struct, representing erros that may be returned 51 | when making Fleet API calls.

52 |

The following fields are public:

53 | 59 | 60 |
61 | 62 | 63 | 64 | Source 65 | 66 | 67 | 68 |

Summary

69 | 70 | 71 | 72 | 73 | 74 | 75 |
from_map(error_map)
76 | 77 | 78 | 79 |
80 |

Types

81 |
82 |

83 | t :: %FleetApi.Error{code: term, message: term} 84 |

85 | 86 |
87 | 88 |
89 | 90 | 91 | 92 |
93 |

Functions

94 |
95 |
96 | from_map(error_map) 97 | 102 |
103 | 104 |

Specs:

105 | 110 | 111 |
112 | 113 |
114 | 115 | Source 116 | 117 |
118 | 119 |
120 | 121 | 122 | 123 | 124 | 125 |
126 | 127 | 128 | -------------------------------------------------------------------------------- /doc/FleetApi.Etcd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Etcd 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Etcd 31 | 32 |

33 | 34 | 45 | 46 | 47 |
48 |

Accesses the Fleet API via a URL discovered through etcd.

49 | 50 |
51 | 52 | 53 | 54 | Source 55 | 56 | 57 | 58 |

Summary

59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 72 | 73 | 74 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 87 | 88 | 89 | 90 | 91 | 92 | 94 | 95 | 96 | 97 | 98 | 99 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 |
delete_unit(pid, unit_name)

Callback implementation for FleetApi.delete_unit/2

64 |
get_api_discovery(pid)

Callback implementation for FleetApi.get_api_discovery/1

71 |
get_node_url(pid)

Retrieves a Fleet node URL based on the information stored in etcd, using the 78 | etcd token specified when the GenServer was started

79 |
get_unit(pid, unit_name)

Callback implementation for FleetApi.get_unit/2

86 |
list_machines(pid)

Callback implementation for FleetApi.list_machines/1

93 |
list_unit_states(pid, opts \\ [])

Callback implementation for FleetApi.list_unit_states/2

100 |
list_units(pid)

Callback implementation for FleetApi.list_units/1

107 |
set_unit(pid, unit_name, unit)

Callback implementation for FleetApi.set_unit/3

114 |
start_link(etcd_token)
123 | 124 | 125 | 126 | 127 | 128 |
129 |

Functions

130 |
131 |
132 | delete_unit(pid, unit_name) 133 | 138 |
139 | 140 |
141 |

Callback implementation for FleetApi.delete_unit/2.

142 | 143 |
144 | 145 | Source 146 | 147 |
148 |
149 |
150 | get_api_discovery(pid) 151 | 156 |
157 | 158 |
159 |

Callback implementation for FleetApi.get_api_discovery/1.

160 | 161 |
162 | 163 | Source 164 | 165 |
166 |
167 |
168 | get_node_url(pid) 169 | 174 |
175 | 176 |

Specs:

177 |
    178 | 179 |
  • get_node_url(pid) :: String.t | {:error, any}
  • 180 | 181 |
182 | 183 |
184 |

Retrieves a Fleet node URL based on the information stored in etcd, using the 185 | etcd token specified when the GenServer was started.

186 | 187 |
188 | 189 | Source 190 | 191 |
192 |
193 |
194 | get_unit(pid, unit_name) 195 | 200 |
201 | 202 |
203 |

Callback implementation for FleetApi.get_unit/2.

204 | 205 |
206 | 207 | Source 208 | 209 |
210 |
211 |
212 | list_machines(pid) 213 | 218 |
219 | 220 |
221 |

Callback implementation for FleetApi.list_machines/1.

222 | 223 |
224 | 225 | Source 226 | 227 |
228 |
229 |
230 | list_unit_states(pid, opts \\ []) 231 | 236 |
237 | 238 |
239 |

Callback implementation for FleetApi.list_unit_states/2.

240 | 241 |
242 | 243 | Source 244 | 245 |
246 |
247 |
248 | list_units(pid) 249 | 254 |
255 | 256 |
257 |

Callback implementation for FleetApi.list_units/1.

258 | 259 |
260 | 261 | Source 262 | 263 |
264 |
265 |
266 | set_unit(pid, unit_name, unit) 267 | 272 |
273 | 274 |
275 |

Callback implementation for FleetApi.set_unit/3.

276 | 277 |
278 | 279 | Source 280 | 281 |
282 |
283 |
284 | start_link(etcd_token) 285 | 290 |
291 | 292 |
293 | 294 |
295 | 296 | Source 297 | 298 |
299 | 300 |
301 | 302 | 303 | 304 | 305 | 306 |
307 | 308 | 309 | -------------------------------------------------------------------------------- /doc/FleetApi.Machine.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Machine 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Machine 31 | 32 |

33 | 34 | 47 | 48 | 49 |
50 |

Defines a FleetApi.Machine struct, representing a host in the Fleet 51 | cluster. It uses the host’s machine-id 52 | as a unique identifier.

53 |

The following fields are public:

54 | 62 | 63 |
64 | 65 | 66 | 67 | Source 68 | 69 | 70 | 71 |

Summary

72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | 86 |
from_map(machine_map)
reachable?(machine, port \\ 7002)

Checks if this machine is responding to requests by attempting to access the 81 | API discovery endpoint of the Fleet API

82 |
87 | 88 | 89 | 90 |
91 |

Types

92 |
93 |

94 | t :: %FleetApi.Machine{id: term, metadata: term, primaryIP: term} 95 |

96 | 97 |
98 | 99 |
100 | 101 | 102 | 103 |
104 |

Functions

105 |
106 |
107 | from_map(machine_map) 108 | 113 |
114 | 115 |

Specs:

116 | 121 | 122 |
123 | 124 |
125 | 126 | Source 127 | 128 |
129 |
130 |
131 | reachable?(machine, port \\ 7002) 132 | 137 |
138 | 139 |

Specs:

140 | 145 | 146 |
147 |

Checks if this machine is responding to requests by attempting to access the 148 | API discovery endpoint of the Fleet API.

149 | 150 |
151 | 152 | Source 153 | 154 |
155 | 156 |
157 | 158 | 159 | 160 | 161 | 162 |
163 | 164 | 165 | -------------------------------------------------------------------------------- /doc/FleetApi.Request.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Request 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Request 31 | 32 |

33 | 34 | 41 | 42 | 43 | 44 | 45 | Source 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /doc/FleetApi.Unit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.Unit 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.Unit 31 | 32 |

33 | 34 | 47 | 48 | 49 |
50 |

Defines a FleetApi.Unit struct, representing a service in a fleet cluster.

51 |

The following fields are public:

52 | 64 | 65 |
66 | 67 | 68 | 69 | Source 70 | 71 | 72 | 73 |

Summary

74 | 75 | 76 | 77 | 78 | 79 | 80 |
from_map(unit_map)
81 | 82 | 83 | 84 |
85 |

Types

86 |
87 |

88 | t :: %FleetApi.Unit{currentState: term, desiredState: term, machineID: term, name: term, options: term} 89 |

90 | 91 |
92 | 93 |
94 | 95 | 96 | 97 |
98 |

Functions

99 |
100 |
101 | from_map(unit_map) 102 | 107 |
108 | 109 |

Specs:

110 | 115 | 116 |
117 | 118 |
119 | 120 | Source 121 | 122 |
123 | 124 |
125 | 126 | 127 | 128 | 129 | 130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /doc/FleetApi.UnitOption.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.UnitOption 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.UnitOption 31 | 32 |

33 | 34 | 47 | 48 | 49 |
50 |

Defines a FleetApi.UnitOption struct, representing one segment of the information used to describe a unit.

51 |

The following fields are public:

52 | 60 | 61 |
62 | 63 | 64 | 65 | Source 66 | 67 | 68 | 69 |

Summary

70 | 71 | 72 | 73 | 74 | 75 | 76 |
from_map(option_map)
77 | 78 | 79 | 80 |
81 |

Types

82 |
83 |

84 | t :: %FleetApi.UnitOption{name: term, section: term, value: term} 85 |

86 | 87 |
88 | 89 |
90 | 91 | 92 | 93 |
94 |

Functions

95 |
96 |
97 | from_map(option_map) 98 | 103 |
104 | 105 |

Specs:

106 | 111 | 112 |
113 | 114 |
115 | 116 | Source 117 | 118 |
119 | 120 |
121 | 122 | 123 | 124 | 125 | 126 |
127 | 128 | 129 | -------------------------------------------------------------------------------- /doc/FleetApi.UnitState.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi.UnitState 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi.UnitState 31 | 32 |

33 | 34 | 47 | 48 | 49 |
50 |

Defines a FleetApi.UnitState struct, representing the current state of a particular unit.

51 |

The following fields are public:

52 | 67 | 68 |
69 | 70 | 71 | 72 | Source 73 | 74 | 75 | 76 |

Summary

77 | 78 | 79 | 80 | 81 | 82 | 83 |
from_map(state_map)
84 | 85 | 86 | 87 |
88 |

Types

89 |
90 |

91 | t :: %FleetApi.UnitState{hash: term, machineID: term, name: term, systemdActiveState: term, systemdLoadState: term, systemdSubState: term} 92 |

93 | 94 |
95 | 96 |
97 | 98 | 99 | 100 |
101 |

Functions

102 |
103 |
104 | from_map(state_map) 105 | 110 |
111 | 112 |

Specs:

113 | 118 | 119 |
120 | 121 |
122 | 123 | Source 124 | 125 |
126 | 127 |
128 | 129 | 130 | 131 | 132 | 133 |
134 | 135 | 136 | -------------------------------------------------------------------------------- /doc/FleetApi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | FleetApi 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 26 | 27 |
28 | 29 |

30 | FleetApi 31 | 32 | behaviour 33 | 34 |

35 | 36 | 45 | 46 | 47 |
48 |

This module contains callback declarations for interacting with a Fleet API endpoint.

49 | 50 |
51 | 52 | 53 | 54 | Source 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 |
67 |

Callbacks

68 |
69 |
70 | delete_unit/2 71 | 76 |
77 | 78 |

Specs:

79 |
    80 | 81 |
  • delete_unit(pid, unit_name :: String.t) :: :ok | {:error, any}
  • 82 | 83 |
84 | 85 |
86 |

Remove a unit from the Fleet cluster.

87 | 88 |
89 | 90 | Source 91 | 92 |
93 |
94 |
95 | get_api_discovery/1 96 | 101 |
102 | 103 |

Specs:

104 |
    105 | 106 |
  • get_api_discovery(pid) :: {:ok, Map.t} | {:error, any}
  • 107 | 108 |
109 | 110 |
111 |

Retrieve the API Discovery document JSON for the Fleet API.

112 | 113 |
114 | 115 | Source 116 | 117 |
118 |
119 |
120 | get_unit/2 121 | 126 |
127 | 128 |

Specs:

129 | 134 | 135 |
136 |

Retrieve the details for a specific unit in the Fleet cluster.

137 | 138 |
139 | 140 | Source 141 | 142 |
143 |
144 |
145 | list_machines/1 146 | 151 |
152 | 153 |

Specs:

154 |
    155 | 156 |
  • list_machines(pid) :: {:ok, [FleetApi.Machine.t]} | {:error, any}
  • 157 | 158 |
159 | 160 |
161 |

Retrieve the list of nodes currently in the Fleet cluster.

162 | 163 |
164 | 165 | Source 166 | 167 |
168 |
169 |
170 | list_unit_states/2 171 | 176 |
177 | 178 |

Specs:

179 | 184 | 185 |
186 |

Get the detailed state information for all the units in the Fleet cluster.

187 |

You may optionally provide options machineID and/or unitName to filter 188 | the response to a particular host or unit.

189 | 190 |
191 | 192 | Source 193 | 194 |
195 |
196 |
197 | list_units/1 198 | 203 |
204 | 205 |

Specs:

206 |
    207 | 208 |
  • list_units(pid) :: {:ok, [FleetApi.Unit.t]} | {:error, any}
  • 209 | 210 |
211 | 212 |
213 |

Retrieve the list of units that the Fleet cluster currently knows about.

214 | 215 |
216 | 217 | Source 218 | 219 |
220 |
221 |
222 | set_unit/3 223 | 228 |
229 | 230 |

Specs:

231 | 236 | 237 |
238 |

Adds or updates a unit in the Fleet cluster. If the cluster doesn’t contain a 239 | unit with the given name, then a new unit is added to it. If a unit with the 240 | given name exists, it is updated with the new unit definition.

241 | 242 |
243 | 244 | Source 245 | 246 |
247 | 248 |
249 | 250 |
251 | 252 | 253 | -------------------------------------------------------------------------------- /doc/README.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | README 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 |
23 | 24 |

FleetApi

25 |

An elixir wrapper for the Fleet API. Connect to the API running on one of your fleet cluster nodes using either a direct node URL or an etcd etcd token.

26 |

Build Status

27 |

Usage

28 |

etcd token

29 |

Note that this is a config value you can set to override the port used to connect to the Fleet API when using an etcd token. 30 | In your app’s config, you can set

31 |
config :fleet_api, :etcd
32 |   fix_port_number: true,
33 |   api_port: 4001
34 |

To get the api to use the correct port, regardless of what might be stored in etcd.

35 |
{:ok, pid} = FleetApi.Etcd.start_link("your etcd token")
36 | {:ok, units} = FleetApi.Etcd.list_units(pid)
37 | 
38 | [%FleetApi.Unit{currentState: "launched", desiredState: "launched",
39 |   machineID: "820c30c0867844129d63f4409871ba39", name: "subgun-http.service",
40 |   options: [%FleetApi.UnitOption{name: "Description", section: "Unit",
41 |     value: "subgun"},
42 |    %FleetApi.UnitOption{name: "ExecStartPre", section: "Service",
43 |     value: "-/usr/bin/docker kill subgun-%i"},
44 |    %FleetApi.UnitOption{name: "ExecStartPre", section: "Service",
45 |     value: "-/usr/bin/docker rm subgun-%i"}...]
46 |

Direct node URL

47 |
{:ok, pid} = FleetApi.Direct.start_link("http://your-node-host-or-ip:7002")
48 | {:ok, units} = FleetApi.Direct.list_units(pid)
49 | 
50 | [%FleetApi.Unit{currentState: "launched", desiredState: "launched",
51 |   machineID: "820c30c0867844129d63f4409871ba39", name: "subgun-http.service",
52 |   options: [%FleetApi.UnitOption{name: "Description", section: "Unit",
53 |     value: "subgun"},
54 |    %FleetApi.UnitOption{name: "ExecStartPre", section: "Service",
55 |     value: "-/usr/bin/docker kill subgun-%i"},
56 |    %FleetApi.UnitOption{name: "ExecStartPre", section: "Service",
57 |     value: "-/usr/bin/docker rm subgun-%i"}...]
58 | 59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /doc/css/elixir.css: -------------------------------------------------------------------------------- 1 | /* 2 | github.com style (c) Vasily Polovnyov 3 | */ 4 | 5 | .hljs { 6 | overflow-x: auto; 7 | color: #333; 8 | padding: 0.5em; 9 | border: #ffe0bb dotted 1px; 10 | background: #fffde8; 11 | display: block; 12 | -webkit-text-size-adjust: none; 13 | } 14 | 15 | .hljs-comment, 16 | .diff .hljs-header, 17 | .hljs-javadoc { 18 | color: #998; 19 | font-style: italic; 20 | } 21 | 22 | .hljs-keyword, 23 | .css .rule .hljs-keyword, 24 | .hljs-winutils, 25 | .nginx .hljs-title, 26 | .hljs-subst, 27 | .hljs-request, 28 | .hljs-status { 29 | color: #333; 30 | font-weight: bold; 31 | } 32 | 33 | .hljs-number, 34 | .hljs-hexcolor, 35 | .ruby .hljs-constant { 36 | color: #008080; 37 | } 38 | 39 | .hljs-string, 40 | .hljs-tag .hljs-value, 41 | .hljs-phpdoc, 42 | .hljs-dartdoc, 43 | .tex .hljs-formula { 44 | color: #d14; 45 | } 46 | 47 | .hljs-title, 48 | .hljs-id, 49 | .scss .hljs-preprocessor { 50 | color: #900; 51 | font-weight: bold; 52 | } 53 | 54 | .hljs-list .hljs-keyword, 55 | .hljs-subst { 56 | font-weight: normal; 57 | } 58 | 59 | .hljs-class .hljs-title, 60 | .hljs-type, 61 | .vhdl .hljs-literal, 62 | .tex .hljs-command { 63 | color: #445588; 64 | font-weight: bold; 65 | } 66 | 67 | .hljs-tag, 68 | .hljs-tag .hljs-title, 69 | .hljs-rules .hljs-property, 70 | .django .hljs-tag .hljs-keyword { 71 | color: #000080; 72 | font-weight: normal; 73 | } 74 | 75 | .hljs-attribute, 76 | .hljs-variable, 77 | .lisp .hljs-body { 78 | color: #008080; 79 | } 80 | 81 | .hljs-regexp { 82 | color: #009926; 83 | } 84 | 85 | .hljs-symbol, 86 | .ruby .hljs-symbol .hljs-string, 87 | .lisp .hljs-keyword, 88 | .clojure .hljs-keyword, 89 | .scheme .hljs-keyword, 90 | .tex .hljs-special, 91 | .hljs-prompt { 92 | color: #990073; 93 | } 94 | 95 | .hljs-built_in { 96 | color: #0086b3; 97 | } 98 | 99 | .hljs-preprocessor, 100 | .hljs-pragma, 101 | .hljs-pi, 102 | .hljs-doctype, 103 | .hljs-shebang, 104 | .hljs-cdata { 105 | color: #999; 106 | font-weight: bold; 107 | } 108 | 109 | .hljs-deletion { 110 | background: #fdd; 111 | } 112 | 113 | .hljs-addition { 114 | background: #dfd; 115 | } 116 | 117 | .diff .hljs-change { 118 | background: #0086b3; 119 | } 120 | 121 | .hljs-chunk { 122 | color: #aaa; 123 | } 124 | -------------------------------------------------------------------------------- /doc/css/full_list.css: -------------------------------------------------------------------------------- 1 | /*** DOCUMENT STRUCTURE: list_template.eex *** 2 | body.frames 3 | section#content [.in_search] 4 | h1#full_list_header 5 | h2#sub_list_header 6 | div#nav 7 | div#search [.loading] > input#search_field 8 | ul#full_list 9 | li.node [.collpased, .search_uncollapsed, .found] 10 | a.toggle 11 | a.object_link 12 | span.node_name 13 | li.docs [.collpased, .search_uncollapsed, .found] 14 | a.toggle 15 | a.object_link 16 | span.node_name 17 | ... 18 | div.no_results 19 | */ 20 | 21 | /* DOCUMENT STYLES */ 22 | body { 23 | font: 13px "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; 24 | height: 101%; 25 | margin: 0; 26 | overflow-x: hidden; 27 | } 28 | h1 { 29 | font-size: 1.4em; 30 | margin: 0; 31 | padding: 12px 10px 0; 32 | } 33 | a:link, a:visited { 34 | color: #05a; 35 | text-decoration: none; 36 | } 37 | li { 38 | color: #888; 39 | cursor: pointer; 40 | } 41 | li:hover { 42 | background: #ddd; 43 | } 44 | span.node_name { 45 | font-size: 0.8em; 46 | } 47 | 48 | /*** LEFT FRAME ***/ 49 | .frames li { 50 | white-space: nowrap; 51 | cursor: default; 52 | } 53 | 54 | /* HEADERS */ 55 | .frames h1 { 56 | margin-top: 0; 57 | } 58 | .frames h2 { 59 | font-size: 0.9em; 60 | margin: 5px 10px 15px; 61 | } 62 | 63 | /* NAVIGATION BAR */ 64 | .nav { 65 | margin: 0 0 10px 5px; 66 | font-size: 0.9em; 67 | color: #aaa; 68 | } 69 | .nav a:link, 70 | .nav a:visited { 71 | color: #358; 72 | } 73 | .nav a:hover { 74 | background: transparent; 75 | color: #5af; 76 | } 77 | .nav span { 78 | border-left: 1px solid #ccc; 79 | padding: 0 3px 0 5px; 80 | } 81 | .nav span:first-child { 82 | border-left: 0; 83 | border-radius: 3px; 84 | } 85 | .nav span.selected { 86 | text-decoration: underline; 87 | } 88 | 89 | /* SEARCH BOX */ 90 | #search { 91 | font-size: 0.9em; 92 | color: #888; 93 | margin: 3px; margin-left: 10px; 94 | padding-left: 0; padding-right: 24px; 95 | } 96 | #search_field { 97 | width: 180px; 98 | margin-right:35px; 99 | border:2px solid #d8d8e5; 100 | padding:2px 4px; 101 | -moz-box-sizing: border-box; 102 | } 103 | #search.loading { 104 | background: url(data:image/gif;base64,R0lGODlhEAAQAPYAAP///wAAAPr6+pKSkoiIiO7u7sjIyNjY2J6engAAAI6OjsbGxjIyMlJSUuzs7KamppSUlPLy8oKCghwcHLKysqSkpJqamvT09Pj4+KioqM7OzkRERAwMDGBgYN7e3ujo6Ly8vCoqKjY2NkZGRtTU1MTExDw8PE5OTj4+PkhISNDQ0MrKylpaWrS0tOrq6nBwcKysrLi4uLq6ul5eXlxcXGJiYoaGhuDg4H5+fvz8/KKiohgYGCwsLFZWVgQEBFBQUMzMzDg4OFhYWBoaGvDw8NbW1pycnOLi4ubm5kBAQKqqqiQkJCAgIK6urnJyckpKSjQ0NGpqatLS0sDAwCYmJnx8fEJCQlRUVAoKCggICLCwsOTk5ExMTPb29ra2tmZmZmhoaNzc3KCgoBISEiIiIgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCAAAACwAAAAAEAAQAAAHaIAAgoMgIiYlg4kACxIaACEJCSiKggYMCRselwkpghGJBJEcFgsjJyoAGBmfggcNEx0flBiKDhQFlIoCCA+5lAORFb4AJIihCRbDxQAFChAXw9HSqb60iREZ1omqrIPdJCTe0SWI09GBACH5BAkIAAAALAAAAAAQABAAAAdrgACCgwc0NTeDiYozCQkvOTo9GTmDKy8aFy+NOBA7CTswgywJDTIuEjYFIY0JNYMtKTEFiRU8Pjwygy4ws4owPyCKwsMAJSTEgiQlgsbIAMrO0dKDGMTViREZ14kYGRGK38nHguHEJcvTyIEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDAggPg4iJAAMJCRUAJRIqiRGCBI0WQEEJJkWDERkYAAUKEBc4Po1GiKKJHkJDNEeKig4URLS0ICImJZAkuQAhjSi/wQyNKcGDCyMnk8u5rYrTgqDVghgZlYjcACTA1sslvtHRgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCQARAtOUoQRGRiFD0kJUYWZhUhKT1OLhR8wBaaFBzQ1NwAlkIszCQkvsbOHL7Y4q4IuEjaqq0ZQD5+GEEsJTDCMmIUhtgk1lo6QFUwJVDKLiYJNUd6/hoEAIfkECQgAAAAsAAAAABAAEAAAB2iAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4uen4ICCA+IkIsDCQkVACWmhwSpFqAABQoQF6ALTkWFnYMrVlhWvIKTlSAiJiVVPqlGhJkhqShHV1lCW4cMqSkAR1ofiwsjJyqGgQAh+QQJCAAAACwAAAAAEAAQAAAHZ4AAgoOEhYaCJSWHgxGDJCSMhREZGIYYGY2ElYebi56fhyWQniSKAKKfpaCLFlAPhl0gXYNGEwkhGYREUywag1wJwSkHNDU3D0kJYIMZQwk8MjPBLx9eXwuETVEyAC/BOKsuEjYFhoEAIfkECQgAAAAsAAAAABAAEAAAB2eAAIKDhIWGgiUlh4MRgyQkjIURGRiGGBmNhJWHm4ueICImip6CIQkJKJ4kigynKaqKCyMnKqSEK05StgAGQRxPYZaENqccFgIID4KXmQBhXFkzDgOnFYLNgltaSAAEpxa7BQoQF4aBACH5BAkIAAAALAAAAAAQABAAAAdogACCg4SFggJiPUqCJSWGgkZjCUwZACQkgxGEXAmdT4UYGZqCGWQ+IjKGGIUwPzGPhAc0NTewhDOdL7Ykji+dOLuOLhI2BbaFETICx4MlQitdqoUsCQ2vhKGjglNfU0SWmILaj43M5oEAOwAAAAAAAAAAAA==) no-repeat 188px center; 105 | min-height:18px; 106 | } 107 | #content #no_results { 108 | margin-left: 7px; 109 | padding: 7px 12px; 110 | } 111 | 112 | /* FULL LIST OF CONTENTS */ 113 | #full_list { 114 | list-style: none; 115 | margin-left: 0; 116 | padding: 0; 117 | } 118 | #full_list ul { 119 | margin:0; 120 | padding: 0; 121 | } 122 | #full_list li { 123 | margin: 0; 124 | padding: 5px 5px 5px 0; 125 | font-size: 1.1em; 126 | list-style: none; 127 | } 128 | #full_list li.node { 129 | padding-left: 25px; 130 | } 131 | #full_list li.docs { 132 | padding:0; 133 | } 134 | #full_list li.docs li { 135 | padding-left: 25px; 136 | } 137 | #full_list li span.node_name { 138 | display: none; 139 | } 140 | #full_list .no_padding { 141 | padding-left:0; 142 | } 143 | /* while searching */ 144 | .in_search #full_list ul { 145 | margin-left:0;} 146 | .in_search #full_list li { 147 | display: none; 148 | } 149 | .in_search #full_list li.found { 150 | display: list-item; 151 | } 152 | .in_search #full_list li a.toggle { 153 | display: none; 154 | } 155 | .in_search #full_list li span.node_name { 156 | display: block; 157 | } 158 | /* collapsed menu */ 159 | #full_list .search_uncollapsed, 160 | #full_list .search_uncollapsed ul { 161 | display:block !important; 162 | } 163 | #full_list ul.collapsed ul, 164 | #full_list ul.collapsed li, 165 | #full_list li.collapsed ul, 166 | #full_list li.collapsed li { 167 | display: none; 168 | } 169 | #full_list ul.search_uncollapsed li.found, 170 | #full_list li.search_uncollapsed li.found { 171 | display: list-item; 172 | } 173 | li.deprecated { 174 | text-decoration: line-through; 175 | font-style: italic; 176 | } 177 | li.r1 { 178 | background: #f0f0f0; 179 | } 180 | li.r2 { 181 | background: #fafafa; 182 | } 183 | /* link properties */ 184 | li a.toggle { 185 | display: block; 186 | float: left; 187 | position: relative; 188 | left: -5px; 189 | top: 4px; 190 | width: 10px; 191 | height: 9px; 192 | margin-left: -10px; 193 | text-indent: -999px; 194 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAASCAYAAABb0P4QAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAK8AAACvABQqw0mAAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTM5jWRgMAAAAVdEVYdENyZWF0aW9uIFRpbWUAMy8xNC8wOeNZPpQAAAE2SURBVDiNrZTBccIwEEXfelIAHUA6CZ24BGaWO+FuzZAK4k6gg5QAdGAq+Bxs2Yqx7BzyL7Llp/VfzZeQhCTc/ezuGzKKnKSzpCxXJM8fwNXda3df5RZETlIt6YUzSQDs93sl8w3wBZxCCE10GM1OcWbWjB2mWgEH4Mfdyxm3PSepBHibgQE2wLe7r4HjEidpnXMYdQPKEMJcsZ4zs2POYQOcaPfwMVOo58zsAdMt18BuoVDPxUJRacELbXv3hUIX2vYmOUvi8C8ydz/ThjXrqKqqLbDIAdsCKBd+Wo7GWa7o9qzOQHVVVXeAbs+yHHCH4aTsaCOQqunmUy1yBUAXkdMIfMlgF5EXLo2OpV/c/Up7jG4hhHcYLgWzAZXUc2b2ixsfvc/RmNNfOXD3Q/oeL9axJE1yT9IOoUu6MGUkAAAAAElFTkSuQmCC) no-repeat bottom left; 195 | cursor: default; 196 | } 197 | li.collapsed a.toggle { 198 | opacity: 0.5; 199 | cursor: default; 200 | background-position: top left; 201 | } 202 | li.clicked { 203 | background: #05a; 204 | color: #ccc; 205 | } 206 | li.clicked a:link, li.clicked a:visited { 207 | color: #eee; 208 | } 209 | li.clicked a.toggle { 210 | opacity: 0.5; 211 | background-position: bottom right; 212 | } 213 | li.collapsed.clicked a.toggle { 214 | background-position: top right; 215 | } 216 | -------------------------------------------------------------------------------- /doc/css/style.css: -------------------------------------------------------------------------------- 1 | /*** DOCUMENT STRUCTURE: module_template.eex *** 2 | body 3 | section#content 4 | div.breadcrumbs 5 | 6 | h1 7 | small 8 | 9 | ul.summary_links 10 | li > a 11 | ... 12 | 13 | section.docstring#moduledoc 14 | a.view_source 15 | 16 | h2#summary > span.detail_header_links > a.to_top_link 17 | table.summary 18 | tr 19 | td.summary_signature > a 20 | td.summary_synopsis > p 21 | ... 22 | 23 | section.details_list#types_details 24 | h2 > a.to_top_link 25 | div.type_detail 26 | p.typespec > a 27 | ... 28 | 29 | section.details_list#functions_details 30 | h2 31 | section.detail 32 | div.detail_header 33 | span.signature > strong 34 | div.detail_header_links 35 | span.detail_type 36 | a.detail_link 37 | a.to_top_link 38 | ul.spec 39 | li > a 40 | ... 41 | section.docstring 42 | a.view_source 43 | ... 44 | 45 | */ 46 | 47 | /* DOCUMENT STYLES */ 48 | body { 49 | font: 13px "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; 50 | padding: 0 20px; 51 | } 52 | 53 | a:link, 54 | a:visited { 55 | color: #05a; 56 | text-decoration: none; 57 | } 58 | a:hover { 59 | color: #27c; 60 | } 61 | 62 | h1 { 63 | font-size: 25px; 64 | border-top: 0; 65 | margin-top: 0; 66 | padding-top: 4px; 67 | } 68 | h1 small { 69 | color: #888; 70 | font-size: 18px; 71 | } 72 | h2 { 73 | padding: 0; 74 | padding-bottom: 3px; 75 | border-bottom: 1px #aaa solid; 76 | font-size: 1.4em; 77 | margin: 1.8em 0 0.5em; 78 | } 79 | 80 | .clear { 81 | clear: both; 82 | } 83 | 84 | table { 85 | border: 1px solid #aaa; 86 | border-collapse: collapse; 87 | margin-top: 1em; 88 | } 89 | table th { 90 | background: #fafafa; 91 | } 92 | table th, 93 | table td { 94 | border: 1px solid #ddd; 95 | padding: 0.4em 1em 0.4em 0.4em; 96 | } 97 | table tr:nth-child(odd) { 98 | background: #f0f0f0; 99 | } 100 | table tr:nth-child(even) { 101 | background: #fafafa; 102 | } 103 | 104 | /* OTHERS */ 105 | body.frames { 106 | padding: 0 5px; 107 | } 108 | li.r1 { 109 | background: #f0f0f0; 110 | } 111 | li.r2 { 112 | background: #fafafa; 113 | } 114 | div.breadcrumbs { 115 | padding-bottom: 0.5em; 116 | } 117 | 118 | /* SUMMARY LINKS */ 119 | ul.summary_links { 120 | margin: 0 0 1em 0; 121 | padding: 0em; 122 | } 123 | ul.summary_links li { 124 | display: inline-block; 125 | list-style-type: none; 126 | width: 7em; 127 | text-align: center; 128 | background: #f0f0f0; 129 | } 130 | 131 | /* DOCSTRING */ 132 | section.docstring, 133 | p.docstring { 134 | margin-right: 6em; 135 | } 136 | .docstring h1, 137 | .docstring h2, 138 | .docstring h3, 139 | .docstring h4 { 140 | padding: 0; 141 | border: 0; 142 | } 143 | .docstring h1 { 144 | font-size: 1.3em; 145 | } 146 | .docstring h2 { 147 | font-size: 1.2em; 148 | } 149 | .docstring h3, 150 | .docstring h4 { 151 | font-size: 1em; 152 | padding-top: 10px; 153 | } 154 | .docstring ul { 155 | padding-left: 20px; 156 | } 157 | .docstring li > p { 158 | margin: 0; 159 | } 160 | 161 | /* SUMMARY */ 162 | div.detail_header_links { 163 | float: right; 164 | } 165 | a.to_top_link { 166 | padding-left: 0.3em; 167 | font-size: 1em; 168 | font-weight: normal; 169 | } 170 | table.summary { 171 | border: 0; 172 | border-collapse: separate; 173 | } 174 | table.summary tr:nth-child(odd) { 175 | background: #f0f0f0; 176 | } 177 | table.summary tr:nth-child(even) { 178 | background: #fafafa; 179 | } 180 | table.summary tr td { 181 | border: 0; 182 | padding-top: 0.5em; padding-bottom: 0.5em; 183 | } 184 | td.summary_signature { 185 | padding-right: 0.5em; 186 | } 187 | td.summary_synopsis { 188 | padding-left: 0.5em; 189 | } 190 | td.summary_synopsis p { 191 | margin: 0; 192 | } 193 | 194 | /* DETAILS LIST */ 195 | .spec, .typespec { 196 | font: bold 1em Courier, monospace; 197 | } 198 | ul.spec { 199 | padding: 6px 10px 6px 25px; 200 | list-style-type: none; 201 | } 202 | .type_detail { 203 | margin-top: 15px; 204 | padding-top: 0; 205 | } 206 | .type_detail > div.typespec_doc { 207 | margin-left: 3em; 208 | } 209 | .detail { 210 | border-top: 1px dotted #aaa; 211 | margin-top: 15px; 212 | padding-top: 0; 213 | } 214 | .detail:nth-child(2) { 215 | border: 0; 216 | } 217 | div.detail_header { 218 | background: #e5e8ff; 219 | border: 1px solid #d8d8e5; 220 | border-radius: 3px; 221 | margin-top: 18px; 222 | padding: 6px 10px; 223 | } 224 | span.signature { 225 | font: normal 1.1em Monaco, Consolas, Courier, monospace; 226 | } 227 | span.detail_type { 228 | font-style: italic; 229 | font-size: 0.9em; 230 | } 231 | a.detail_link { 232 | padding-left: 0.3em; 233 | } 234 | -------------------------------------------------------------------------------- /doc/exceptions_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Exceptions 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 |
19 |

20 | 21 | fleet_api v0.0.15 22 | 23 |

24 | 25 |

26 | 27 | README - 28 | 29 | Overview 30 |

31 | 32 | 37 | 38 | 39 | 40 | 43 | 44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fleet_api v0.0.15 Documentation 7 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /doc/js/app.js: -------------------------------------------------------------------------------- 1 | function fixOutsideWorldLinks() { 2 | $('a').each(function() { 3 | if (window.location.host != this.host) this.target = '_parent'; 4 | }); 5 | } 6 | 7 | $(fixOutsideWorldLinks); 8 | -------------------------------------------------------------------------------- /doc/js/full_list.js: -------------------------------------------------------------------------------- 1 | var inSearch = null; 2 | var defaultSearchItemTimeOut = 0; //set to "0" if not testing 3 | var searchIndex = 0; 4 | var searchCache = []; 5 | var searchString = ''; 6 | var regexSearchString = ''; 7 | var caseSensitiveMatch = false; 8 | var ignoreKeyCodeMin = 8; 9 | var ignoreKeyCodeMax = 46; 10 | var commandKey = 91; 11 | 12 | RegExp.escape = function(text) { 13 | return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); 14 | } 15 | 16 | function fullListSearch() { 17 | // generate cache 18 | searchCache = []; 19 | $('#full_list li').each(function() { 20 | var link = $(this).find('a.object_link:first'); 21 | if ( link.attr('title') ) { 22 | var fullName = link.attr('title').split(' ')[0]; 23 | searchCache.push({name:link.text(), fullName:fullName, node:$(this), link:link}); 24 | } 25 | }); 26 | 27 | $('#search input').keypress(function (e) { 28 | if (e.which == 13) { 29 | $('#full_list li.found:first').find('a.object_link:first').click(); 30 | } 31 | }); 32 | 33 | //$('#search input').keyup(function(evnt) { 34 | $('#search input').bind("keyup search reset change propertychange input paste", function(evnt) { 35 | if ((evnt.keyCode > ignoreKeyCodeMin && evnt.keyCode < ignoreKeyCodeMax) 36 | || evnt.keyCode == commandKey) { 37 | return; 38 | } 39 | 40 | $('#search').addClass('loading'); 41 | searchString = this.value; 42 | caseSensitiveMatch = searchString.match(/[A-Z]/) != null; 43 | regexSearchString = RegExp.escape(searchString); 44 | if (searchString === "") { 45 | showAllResults(); 46 | } 47 | else { 48 | if (inSearch) { 49 | clearTimeout(inSearch); 50 | } 51 | searchIndex = 0; 52 | lastRowClass = ''; 53 | $('#content').addClass('in_search'); 54 | $('#no_results').text(''); 55 | searchItem(); 56 | } 57 | }); 58 | 59 | $('#search input').focus(); 60 | } 61 | 62 | function showAllResults() { 63 | clearTimeout(inSearch); 64 | inSearch = defaultSearchItemTimeOut; 65 | $('.search_uncollapsed').removeClass('search_uncollapsed'); 66 | $('#content').removeClass('in_search'); 67 | $('#full_list li').removeClass('found').each(function() { 68 | var link = $(this).find('a.object_link:first'); 69 | link.text(link.text()); 70 | }); 71 | if (clicked) { 72 | clicked.parents('li').each(function() { 73 | $(this).removeClass('collapsed').prev().removeClass('collapsed'); 74 | }); 75 | } 76 | $('#no_results').text(''); 77 | $('#search').removeClass('loading'); 78 | highlight(); 79 | } 80 | 81 | var lastRowClass = ''; 82 | function searchItem() { 83 | for (var i = 0; i < searchCache.length / 50; i++) { 84 | var item = searchCache[searchIndex]; 85 | var searchName = (searchString.indexOf('.') != -1 ? item.fullName : item.name); 86 | var matchString = regexSearchString; 87 | var matchRegexp = new RegExp(matchString, caseSensitiveMatch ? "" : "i"); 88 | if (searchName.match(matchRegexp) == null) { 89 | item.node.removeClass('found'); 90 | } 91 | else { 92 | item.node.addClass('found'); 93 | item.node.parents('li').addClass('search_uncollapsed'); 94 | item.node.removeClass(lastRowClass).addClass(lastRowClass == 'r1' ? 'r2' : 'r1'); 95 | lastRowClass = item.node.hasClass('r1') ? 'r1' : 'r2'; 96 | item.link.html(item.name.replace(matchRegexp, "$&")); 97 | } 98 | 99 | if (searchCache.length === searchIndex + 1) { 100 | searchDone(); 101 | return; 102 | } 103 | else { 104 | searchIndex++; 105 | } 106 | } 107 | inSearch = setTimeout('searchItem()', defaultSearchItemTimeOut); 108 | } 109 | 110 | function searchDone() { 111 | highlight(true); 112 | if ($('#full_list li.found').size() === 0) { 113 | $('#no_results').text('No results were found.').hide().fadeIn(); 114 | } 115 | else { 116 | $('#no_results').text(''); 117 | } 118 | 119 | $('#search').removeClass('loading'); 120 | clearTimeout(inSearch); 121 | inSearch = null; 122 | } 123 | 124 | clicked = null; 125 | function linkList() { 126 | $('#full_list li, #full_list li a:last').click(function(evt) { 127 | if ($(this).hasClass('toggle')) { 128 | return true; 129 | } 130 | 131 | if (this.tagName.toLowerCase() == "li") { 132 | var toggle = $(this).children('a.toggle'); 133 | if (toggle.size() > 0 && evt.pageX < toggle.offset().left) { 134 | toggle.click(); 135 | return false; 136 | } 137 | } 138 | 139 | if (clicked) { 140 | clicked.removeClass('clicked'); 141 | } 142 | 143 | var win = window.top.frames.main ? window.top.frames.main : window.parent; 144 | if (this.tagName.toLowerCase() == "a") { 145 | clicked = $(this).parent('li').addClass('clicked'); 146 | win.location = this.href; 147 | } 148 | else { 149 | clicked = $(this).addClass('clicked'); 150 | win.location = $(this).find('a:last').attr('href'); 151 | } 152 | 153 | return false; 154 | }); 155 | } 156 | 157 | function collapse() { 158 | $('#full_list a.toggle').click(function() { 159 | $(this).parent().toggleClass('collapsed').next().toggleClass('collapsed'); 160 | highlight(); 161 | return false; 162 | }); 163 | 164 | $('#full_list > li.node').each(function() { 165 | $(this).addClass('collapsed').next('li.docs').addClass('collapsed'); 166 | }); 167 | 168 | highlight(); 169 | } 170 | 171 | function highlight(no_padding) { 172 | var n = 1; 173 | $('#full_list a.object_link:visible').each(function() { 174 | var next = n == 1 ? 2 : 1; 175 | var li = $(this).parent(); 176 | li.removeClass("r" + next).addClass("r" + n); 177 | no_padding ? li.addClass("no_padding") : li.removeClass("no_padding"); 178 | n = next; 179 | }); 180 | } 181 | 182 | function escapeShortcut() { 183 | $(document).keydown(function(evt) { 184 | if (evt.which == 27) { 185 | $('#search_frame', window.top.document).slideUp(100); 186 | $('#search a', window.top.document).removeClass('active inactive'); 187 | $(window.top).focus(); 188 | } 189 | }); 190 | } 191 | 192 | $(escapeShortcut); 193 | $(fullListSearch); 194 | $(linkList); 195 | $(collapse); -------------------------------------------------------------------------------- /doc/modules_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Modules 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 |
19 |

20 | 21 | fleet_api v0.0.15 22 | 23 |

24 | 25 |

26 | 27 | README - 28 | 29 | Overview 30 |

31 | 32 | 37 | 38 | 39 | 40 | 302 | 303 |
304 |
305 | 306 | 307 | -------------------------------------------------------------------------------- /doc/overview.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Overview 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 21 | 22 |
23 | 24 | 25 |

fleet_api v0.0.15

26 | 27 | 34 | 35 | 36 |

Modules summary

37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 50 | 51 | 52 | 53 | 54 | 55 | 58 | 59 | 60 | 61 | 62 | 63 | 65 | 66 | 67 | 68 | 69 | 70 | 74 | 75 | 76 | 77 | 78 | 79 | 81 | 82 | 83 | 84 | 85 | 86 | 88 | 89 | 90 | 91 | 92 | 93 | 95 | 96 | 97 | 98 |
FleetApi

This module contains callback declarations for interacting with a Fleet API endpoint

42 |
FleetApi.Direct

Accesses the Fleet API via a directly-identified node URL

49 |
FleetApi.Error

Defines a FleetApi.Error struct, representing erros that may be returned 56 | when making Fleet API calls

57 |
FleetApi.Etcd

Accesses the Fleet API via a URL discovered through etcd

64 |
FleetApi.Machine

Defines a FleetApi.Machine struct, representing a host in the Fleet 71 | cluster. It uses the host’s machine-id 72 | as a unique identifier

73 |
FleetApi.Unit

Defines a FleetApi.Unit struct, representing a service in a fleet cluster

80 |
FleetApi.UnitOption

Defines a FleetApi.UnitOption struct, representing one segment of the information used to describe a unit

87 |
FleetApi.UnitState

Defines a FleetApi.UnitState struct, representing the current state of a particular unit

94 |
99 | 100 | 101 | 102 | 103 | 104 |
105 | 106 | 107 | -------------------------------------------------------------------------------- /doc/protocols_list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | List of Protocols 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 18 |
19 |

20 | 21 | fleet_api v0.0.15 22 | 23 |

24 | 25 |

26 | 27 | README - 28 | 29 | Overview 30 |

31 | 32 | 37 | 38 | 39 | 40 | 43 | 44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /fixture/custom_cassettes/delete_unit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/subgun-http.service" 5 | }, 6 | "response": { 7 | "status_code": 204, 8 | "headers": { 9 | }, 10 | "body": "" 11 | } 12 | } 13 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_delete_unit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units\/subgun-http.service/" 29 | }, 30 | "response": { 31 | "status_code": 204, 32 | "headers": { 33 | }, 34 | "body": "" 35 | } 36 | } 37 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_get_api_discovery.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "{\"kind\": \"discovery#restDescription\",\"discoveryVersion\": \"v1\",\"id\": \"fleet:v1\",\"name\": \"schema\",\"version\": \"v1\",\"title\": \"fleet API\",\"description\": \"\",\"documentationLink\": \"http://github.com/coreos/fleet\",\"protocol\": \"rest\",\"icons\": {\"x16\": \"\",\"x32\": \"\"},\"labels\": [],\"baseUrl\": \"$ENDPOINT/fleet/v1/\",\"basePath\": \"/fleet/v1/\",\"rootUrl\": \"$ENDPOINT/\",\"servicePath\": \"fleet/v1/\",\"batchPath\": \"batch\",\"parameters\": {},\"auth\": {},\"schemas\": {\"Machine\": {\"id\": \"Machine\",\"type\": \"object\",\"properties\": {\"id\": {\"type\": \"string\"},\"primaryIP\": {\"type\": \"string\"},\"metadata\": {\"type\": \"object\",\"properties\": {},\"additionalProperties\": {\"type\": \"string\"}}}},\"MachinePage\": {\"id\": \"MachinePage\",\"type\": \"object\",\"properties\": {\"machines\": {\"type\": \"array\",\"items\": {\"$ref\": \"Machine\"}},\"nextPageToken\": {\"type\": \"string\"}}},\"UnitOption\": {\"id\": \"UnitOption\",\"type\": \"object\",\"properties\": {\"section\": {\"type\": \"string\"},\"name\": {\"type\": \"string\"},\"value\": {\"type\": \"string\"}}},\"Unit\": {\"id\": \"Unit\",\"type\": \"object\",\"properties\": {\"name\": {\"type\": \"string\"},\"options\": {\"type\": \"array\",\"items\": {\"$ref\": \"UnitOption\"}},\"desiredState\": {\"type\": \"string\",\"enum\": [\"inactive\",\"loaded\",\"launched\"]},\"currentState\": {\"type\": \"string\",\"enum\": [\"inactive\",\"loaded\",\"launched\"]},\"machineID\": {\"type\": \"string\",\"required\": true}}},\"UnitPage\": {\"id\": \"UnitPage\",\"type\": \"object\",\"properties\": {\"units\": {\"type\": \"array\",\"items\": {\"$ref\": \"Unit\"}},\"nextPageToken\": {\"type\": \"string\"}}},\"UnitState\": {\"id\": \"UnitState\",\"type\": \"object\",\"properties\": {\"name\": {\"type\": \"string\"},\"hash\": {\"type\": \"string\"},\"machineID\": {\"type\": \"string\"},\"systemdLoadState\": {\"type\": \"string\"},\"systemdActiveState\": {\"type\": \"string\"},\"systemdSubState\": {\"type\": \"string\"}}},\"UnitStatePage\": {\"id\": \"UnitStatePage\",\"type\": \"object\",\"properties\": {\"states\": {\"type\": \"array\",\"items\": {\"$ref\": \"UnitState\"}},\"nextPageToken\": {\"type\": \"string\"}}}},\"resources\": {\"Machines\": {\"methods\": {\"List\": {\"id\": \"fleet.Machine.List\",\"description\": \"Retrieve a page of Machine objects.\",\"httpMethod\": \"GET\",\"path\": \"machines\",\"parameters\": {\"nextPageToken\": {\"type\": \"string\",\"location\": \"query\"}},\"response\": {\"$ref\": \"MachinePage\"}}}},\"Units\": {\"methods\": {\"List\": {\"id\": \"fleet.Unit.List\",\"description\": \"Retrieve a page of Unit objects.\",\"httpMethod\": \"GET\",\"path\": \"units\",\"parameters\": {\"nextPageToken\": {\"type\": \"string\",\"location\": \"query\"}},\"response\": {\"$ref\": \"UnitPage\"}},\"Get\": {\"id\": \"fleet.Unit.Get\",\"description\": \"Retrieve a single Unit object.\",\"httpMethod\": \"GET\",\"path\": \"units/{unitName}\",\"parameters\": {\"unitName\": {\"type\": \"string\",\"location\": \"path\",\"required\": true}},\"parameterOrder\": [\"unitName\"],\"response\": {\"$ref\": \"Unit\"}},\"Delete\": {\"id\": \"fleet.Unit.Delete\",\"description\": \"Delete the referenced Unit object.\",\"httpMethod\": \"DELETE\",\"path\": \"units/{unitName}\",\"parameters\": {\"unitName\": {\"type\": \"string\",\"location\": \"path\",\"required\": true}},\"parameterOrder\": [\"unitName\"]},\"Set\": {\"id\": \"fleet.Unit.Set\",\"description\": \"Create or update a Unit.\",\"httpMethod\": \"PUT\",\"path\": \"units/{unitName}\",\"parameters\": {\"unitName\": {\"type\": \"string\",\"location\": \"path\",\"required\": true}},\"parameterOrder\": [\"unitName\"],\"request\": {\"$ref\": \"Unit\"}}}},\"UnitState\": {\"methods\": {\"List\": {\"id\": \"fleet.UnitState.List\",\"description\": \"Retrieve a page of UnitState objects.\",\"httpMethod\": \"GET\",\"path\": \"state\",\"parameters\": {\"nextPageToken\": {\"type\": \"string\",\"location\": \"query\"},\"unitName\": {\"type\": \"string\",\"location\": \"query\"},\"machineID\": {\"type\": \"string\",\"location\": \"query\"}},\"response\": {\"$ref\": \"UnitStatePage\"}}}}}}" 24 | } 25 | } 26 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_get_unit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units\/subgun-http.service/" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_machines.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/machines" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"machines\":[{\"id\":\"76ffb3a4588c46f3941c073df77be5e9\",\"primaryIP\":\"127.0.0.1\"},{\"id\":\"820c30c0867844129d63f4409871ba39\",\"primaryIP\":\"127.0.0.2\"},{\"id\":\"f439a6a2dd8f43dbad60994cc1fb68f6\",\"primaryIP\":\"127.0.0.3\"}]}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_unit_states.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/state/" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"states\":[{\"hash\":\"eef29cad431ad16c8e164400b2f3c85afd73b238\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"systemdActiveState\":\"active\",\"systemdLoadState\":\"loaded\",\"systemdSubState\":\"running\"}]}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_units.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"units\":[{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-http@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]}]}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_units_empty.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units/" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"units\":[]}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_units_multiple_calls.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.3:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.3:7002\/fleet\/v1\/units" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"units\":[{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-http@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]}]}" 36 | } 37 | }, 38 | { 39 | "request": { 40 | "url": "~r/http:\/\/127.0.0.3:7002\/fleet\/v1\/units" 41 | }, 42 | "response": { 43 | "status_code": 200, 44 | "headers": { 45 | "Content-Type": "application/json" 46 | }, 47 | "body": "{\"units\":[{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-http@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]}]}" 48 | } 49 | }, 50 | { 51 | "request": { 52 | "url": "~r/http:\/\/127.0.0.3:7002\/fleet\/v1\/units" 53 | }, 54 | "response": { 55 | "status_code": 200, 56 | "headers": { 57 | "Content-Type": "application/json" 58 | }, 59 | "body": "{\"units\":[{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-http@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]}]}" 60 | } 61 | } 62 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_units_null.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units/" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{\"units\":null}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_list_units_weird_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units/" 29 | }, 30 | "response": { 31 | "status_code": 200, 32 | "headers": { 33 | "Content-Type": "application/json" 34 | }, 35 | "body": "{}" 36 | } 37 | } 38 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_response_503_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 503, 8 | "headers": { 9 | "Content-Length": "0" 10 | }, 11 | "body": "" 12 | } 13 | }, 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_response_no_nodes.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_set_unit_new.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units\/test.service/", 29 | "headers": { 30 | "Content-Type": "application/json" 31 | }, 32 | "body": "{\"currentState\":null,\"desiredState\":\"launched\",\"machineID\":null,\"name\":\"test.service\",\"options\":[{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/sleep 3000\"}]}" 33 | }, 34 | "response": { 35 | "status_code": 201, 36 | "headers": {}, 37 | "body": "" 38 | } 39 | } 40 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/etcd_set_unit_update.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "https://discovery.etcd.io/abcd1234" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"action\":\"get\",\"node\":{\"key\":\"/_etcd/registry/abcd1234\",\"dir\":true,\"nodes\":[{\"value\":\"http://127.0.0.1:7001\"},{\"value\":\"http://127.0.0.2:7001\"},{\"value\":\"http://127.0.0.3:7001\"}],\"modifiedIndex\":381975809,\"createdIndex\":381975809}}" 12 | } 13 | }, 14 | { 15 | "request": { 16 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/discovery/" 17 | }, 18 | "response": { 19 | "status_code": 200, 20 | "headers": { 21 | "Content-Type": "application/json" 22 | }, 23 | "body": "" 24 | } 25 | }, 26 | { 27 | "request": { 28 | "url": "~r/http:\/\/127.0.0.[1-3]:7002\/fleet\/v1\/units\/test.service/", 29 | "headers": { 30 | "Content-Type": "application/json" 31 | }, 32 | "body": "{\"desiredState\":\"launched\"}" 33 | }, 34 | "response": { 35 | "status_code": 204, 36 | "headers": {}, 37 | "body": "" 38 | } 39 | } 40 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/get_api_discovery.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/discovery" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"kind\": \"discovery#restDescription\",\"discoveryVersion\": \"v1\",\"id\": \"fleet:v1\",\"name\": \"schema\",\"version\": \"v1\",\"title\": \"fleet API\",\"description\": \"\",\"documentationLink\": \"http://github.com/coreos/fleet\",\"protocol\": \"rest\",\"icons\": {\"x16\": \"\",\"x32\": \"\"},\"labels\": [],\"baseUrl\": \"$ENDPOINT/fleet/v1/\",\"basePath\": \"/fleet/v1/\",\"rootUrl\": \"$ENDPOINT/\",\"servicePath\": \"fleet/v1/\",\"batchPath\": \"batch\",\"parameters\": {},\"auth\": {},\"schemas\": {\"Machine\": {\"id\": \"Machine\",\"type\": \"object\",\"properties\": {\"id\": {\"type\": \"string\"},\"primaryIP\": {\"type\": \"string\"},\"metadata\": {\"type\": \"object\",\"properties\": {},\"additionalProperties\": {\"type\": \"string\"}}}},\"MachinePage\": {\"id\": \"MachinePage\",\"type\": \"object\",\"properties\": {\"machines\": {\"type\": \"array\",\"items\": {\"$ref\": \"Machine\"}},\"nextPageToken\": {\"type\": \"string\"}}},\"UnitOption\": {\"id\": \"UnitOption\",\"type\": \"object\",\"properties\": {\"section\": {\"type\": \"string\"},\"name\": {\"type\": \"string\"},\"value\": {\"type\": \"string\"}}},\"Unit\": {\"id\": \"Unit\",\"type\": \"object\",\"properties\": {\"name\": {\"type\": \"string\"},\"options\": {\"type\": \"array\",\"items\": {\"$ref\": \"UnitOption\"}},\"desiredState\": {\"type\": \"string\",\"enum\": [\"inactive\",\"loaded\",\"launched\"]},\"currentState\": {\"type\": \"string\",\"enum\": [\"inactive\",\"loaded\",\"launched\"]},\"machineID\": {\"type\": \"string\",\"required\": true}}},\"UnitPage\": {\"id\": \"UnitPage\",\"type\": \"object\",\"properties\": {\"units\": {\"type\": \"array\",\"items\": {\"$ref\": \"Unit\"}},\"nextPageToken\": {\"type\": \"string\"}}},\"UnitState\": {\"id\": \"UnitState\",\"type\": \"object\",\"properties\": {\"name\": {\"type\": \"string\"},\"hash\": {\"type\": \"string\"},\"machineID\": {\"type\": \"string\"},\"systemdLoadState\": {\"type\": \"string\"},\"systemdActiveState\": {\"type\": \"string\"},\"systemdSubState\": {\"type\": \"string\"}}},\"UnitStatePage\": {\"id\": \"UnitStatePage\",\"type\": \"object\",\"properties\": {\"states\": {\"type\": \"array\",\"items\": {\"$ref\": \"UnitState\"}},\"nextPageToken\": {\"type\": \"string\"}}}},\"resources\": {\"Machines\": {\"methods\": {\"List\": {\"id\": \"fleet.Machine.List\",\"description\": \"Retrieve a page of Machine objects.\",\"httpMethod\": \"GET\",\"path\": \"machines\",\"parameters\": {\"nextPageToken\": {\"type\": \"string\",\"location\": \"query\"}},\"response\": {\"$ref\": \"MachinePage\"}}}},\"Units\": {\"methods\": {\"List\": {\"id\": \"fleet.Unit.List\",\"description\": \"Retrieve a page of Unit objects.\",\"httpMethod\": \"GET\",\"path\": \"units\",\"parameters\": {\"nextPageToken\": {\"type\": \"string\",\"location\": \"query\"}},\"response\": {\"$ref\": \"UnitPage\"}},\"Get\": {\"id\": \"fleet.Unit.Get\",\"description\": \"Retrieve a single Unit object.\",\"httpMethod\": \"GET\",\"path\": \"units/{unitName}\",\"parameters\": {\"unitName\": {\"type\": \"string\",\"location\": \"path\",\"required\": true}},\"parameterOrder\": [\"unitName\"],\"response\": {\"$ref\": \"Unit\"}},\"Delete\": {\"id\": \"fleet.Unit.Delete\",\"description\": \"Delete the referenced Unit object.\",\"httpMethod\": \"DELETE\",\"path\": \"units/{unitName}\",\"parameters\": {\"unitName\": {\"type\": \"string\",\"location\": \"path\",\"required\": true}},\"parameterOrder\": [\"unitName\"]},\"Set\": {\"id\": \"fleet.Unit.Set\",\"description\": \"Create or update a Unit.\",\"httpMethod\": \"PUT\",\"path\": \"units/{unitName}\",\"parameters\": {\"unitName\": {\"type\": \"string\",\"location\": \"path\",\"required\": true}},\"parameterOrder\": [\"unitName\"],\"request\": {\"$ref\": \"Unit\"}}}},\"UnitState\": {\"methods\": {\"List\": {\"id\": \"fleet.UnitState.List\",\"description\": \"Retrieve a page of UnitState objects.\",\"httpMethod\": \"GET\",\"path\": \"state\",\"parameters\": {\"nextPageToken\": {\"type\": \"string\",\"location\": \"query\"},\"unitName\": {\"type\": \"string\",\"location\": \"query\"},\"machineID\": {\"type\": \"string\",\"location\": \"query\"}},\"response\": {\"$ref\": \"UnitStatePage\"}}}}}}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/get_unit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/subgun-http.service" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/get_unit_empty_options.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/subgun-http.service" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/get_unit_missing_options.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/subgun-http.service" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\"}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/get_unit_null_options.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/subgun-http.service" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":null}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_machines.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/machines" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"machines\":[{\"id\":\"76ffb3a4588c46f3941c073df77be5e9\",\"primaryIP\":\"127.0.0.1\"},{\"id\":\"820c30c0867844129d63f4409871ba39\",\"primaryIP\":\"127.0.0.2\"},{\"id\":\"f439a6a2dd8f43dbad60994cc1fb68f6\",\"primaryIP\":\"127.0.0.3\"}]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_machines_empty.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/machines" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"machines\":[]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_machines_null.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/machines" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"machines\":null}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_machines_weird_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/machines" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_unit_states.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/state" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"states\":[{\"hash\":\"eef29cad431ad16c8e164400b2f3c85afd73b238\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"systemdActiveState\":\"active\",\"systemdLoadState\":\"loaded\",\"systemdSubState\":\"running\"}]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_unit_states_empty.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/state" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"states\":[]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_unit_states_null.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/state" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"states\":null}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_unit_states_weird_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/state" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_units.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"units\":[{\"currentState\":\"launched\",\"desiredState\":\"launched\",\"machineID\":\"820c30c0867844129d63f4409871ba39\",\"name\":\"subgun-http.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-http@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-%i\"},{\"name\":\"Conflicts\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@*.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]},{\"currentState\":\"inactive\",\"desiredState\":\"launched\",\"machineID\":\"76ffb3a4588c46f3941c073df77be5e9\",\"name\":\"subgun-presence@.service\",\"options\":[{\"name\":\"Description\",\"section\":\"Unit\",\"value\":\"subgun presence service\"},{\"name\":\"BindsTo\",\"section\":\"Unit\",\"value\":\"subgun-http@%i.service\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker kill subgun-presence-%i\"},{\"name\":\"ExecStartPre\",\"section\":\"Service\",\"value\":\"-/usr/bin/docker rm subgun-presence-%i\"},{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/docker run --rm --name subgun-presence-%i -e AWS_ACCESS_KEY=AKIAIBC5MW3ONCW6J2XQ -e AWS_SECRET_KEY=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 -e AWS_REGION=us-east-1 -e ELB_NAME=bcwaldon-fleet-lb quay.io/coreos/elb-presence\"},{\"name\":\"ExecStop\",\"section\":\"Service\",\"value\":\"/usr/bin/docker stop subgun-presence-%i\"},{\"name\":\"MachineOf\",\"section\":\"X-Fleet\",\"value\":\"subgun-http@%i.service\"}]}]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_units_empty.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"units\":[]}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_units_null.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{\"units\":null}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/list_units_weird_response.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units" 5 | }, 6 | "response": { 7 | "status_code": 200, 8 | "headers": { 9 | "Content-Type": "application/json" 10 | }, 11 | "body": "{}" 12 | } 13 | } 14 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/set_unit_new.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/test.service", 5 | "headers": { 6 | "Content-Type": "application/json" 7 | }, 8 | "body": "{\"currentState\":null,\"desiredState\":\"launched\",\"machineID\":null,\"name\":\"test.service\",\"options\":[{\"name\":\"ExecStart\",\"section\":\"Service\",\"value\":\"/usr/bin/sleep 3000\"}]}" 9 | }, 10 | "response": { 11 | "status_code": 201, 12 | "headers": {}, 13 | "body": "" 14 | } 15 | } 16 | ] -------------------------------------------------------------------------------- /fixture/custom_cassettes/set_unit_update.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "request": { 4 | "url": "http://localhost:7002/fleet/v1/units/test.service", 5 | "headers": { 6 | "Content-Type": "application/json" 7 | }, 8 | "body": "{\"desiredState\":\"launched\"}" 9 | }, 10 | "response": { 11 | "status_code": 204, 12 | "headers": {}, 13 | "body": "" 14 | } 15 | } 16 | ] -------------------------------------------------------------------------------- /lib/fleet_api.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi do 2 | @moduledoc """ 3 | This module contains callback declarations for interacting with a Fleet API endpoint. 4 | """ 5 | use Behaviour 6 | 7 | @doc """ 8 | Retrieve the list of units that the Fleet cluster currently knows about. 9 | """ 10 | defcallback list_units(pid) :: {:ok, [FleetApi.Unit.t]} | {:error, any} 11 | 12 | @doc """ 13 | Retrieve the details for a specific unit in the Fleet cluster. 14 | """ 15 | defcallback get_unit(pid, unit_name :: String.t) :: {:ok, FleetApi.Unit.t} | {:error, any} 16 | 17 | @doc """ 18 | Remove a unit from the Fleet cluster. 19 | """ 20 | defcallback delete_unit(pid, unit_name :: String.t) :: :ok | {:error, any} 21 | 22 | @doc """ 23 | Adds or updates a unit in the Fleet cluster. If the cluster doesn't contain a 24 | unit with the given name, then a new unit is added to it. If a unit with the 25 | given name exists, it is updated with the new unit definition. 26 | """ 27 | defcallback set_unit(pid, unit_name :: String.t, FleetApi.Unit.t) :: :ok | {:error, any} 28 | 29 | @doc """ 30 | Get the detailed state information for all the units in the Fleet cluster. 31 | 32 | You may optionally provide options `machineID` and/or `unitName` to filter 33 | the response to a particular host or unit. 34 | """ 35 | defcallback list_unit_states(pid, opts :: [{atom, String.t}]) :: {:ok, [FleetApi.UnitState.t]} | {:error, any} 36 | 37 | @doc """ 38 | Retrieve the list of nodes currently in the Fleet cluster. 39 | """ 40 | defcallback list_machines(pid) :: {:ok, [FleetApi.Machine.t]} | {:error, any} 41 | 42 | @doc """ 43 | Retrieve the API Discovery document JSON for the Fleet API. 44 | """ 45 | defcallback get_api_discovery(pid) :: {:ok, Map.t} | {:error, any} 46 | 47 | defmacro __using__(_) do 48 | quote do 49 | use FleetApi.Api 50 | @behaviour FleetApi 51 | 52 | # To handle errors that may occur inside get_node_url, we'll wrap any 53 | # calls to API functions with call_with_url, which will either return 54 | # with the error info, or pass the resolved URL on down to the API func. 55 | defp call_with_url({:error, reason}, _fn), do: {:error, reason} 56 | 57 | defp call_with_url(node_url, fun) do 58 | fun.(node_url) 59 | end 60 | 61 | def list_units(pid) do 62 | pid 63 | |> get_node_url 64 | |> call_with_url(&api_list_units/1) 65 | end 66 | 67 | def get_unit(pid, unit_name) do 68 | pid 69 | |> get_node_url 70 | |> call_with_url(&(api_get_unit(&1, unit_name))) 71 | end 72 | 73 | def delete_unit(pid, unit_name) do 74 | pid 75 | |> get_node_url 76 | |> call_with_url(&(api_delete_unit(&1, unit_name))) 77 | end 78 | 79 | def set_unit(pid, unit_name, unit) do 80 | pid 81 | |> get_node_url 82 | |> call_with_url(&(api_set_unit(&1, unit_name, unit))) 83 | end 84 | 85 | def list_unit_states(pid, opts \\ []) do 86 | pid 87 | |> get_node_url 88 | |> call_with_url(&(api_list_unit_states(&1, opts))) 89 | end 90 | 91 | def list_machines(pid) do 92 | pid 93 | |> get_node_url 94 | |> call_with_url(&api_list_machines/1) 95 | end 96 | 97 | def get_api_discovery(pid) do 98 | pid 99 | |> get_node_url 100 | |> call_with_url(&api_discovery/1) 101 | end 102 | 103 | @doc """ 104 | Retrieves the Fleet API node URL, based on either etcd discovery or direct setting of the node url. 105 | """ 106 | defcallback get_node_url(pid) :: String.t 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /lib/fleet_api/api.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Api do 2 | @moduledoc false 3 | defmacro __using__(_) do 4 | quote do 5 | use FleetApi.Request 6 | alias FleetApi.Unit 7 | alias FleetApi.UnitOption 8 | alias FleetApi.UnitState 9 | alias FleetApi.Machine 10 | alias FleetApi.Error 11 | 12 | defp add_query_param(url, name, value) do 13 | separator = if String.contains?(url, "?"), do: "&", else: "?" 14 | 15 | url <> separator <> name <> "=" <> value 16 | end 17 | 18 | defp paginated_request(method, url, headers, body, expected_status \\ [200], resp_bodies \\ [], next_page_token \\ "") do 19 | request_url = if next_page_token == "" do 20 | url 21 | else 22 | add_query_param(url, "nextPageToken", next_page_token) 23 | end 24 | 25 | case request(method, request_url, headers, body, expected_status) do 26 | {:ok, %{"nextPageToken" => token} = resp_body} -> 27 | paginated_request(method, url, headers, body, expected_status, [resp_body | resp_bodies], token) 28 | {:ok, resp_body} -> 29 | result = [resp_body | resp_bodies] 30 | |> Enum.reverse 31 | 32 | {:ok, result} 33 | error -> error 34 | end 35 | end 36 | 37 | 38 | @spec api_list_units(String.t) :: {:ok, [FleetApi.Unit.t]} | {:error, any} 39 | defp api_list_units(node_url) do 40 | case paginated_request(:get, node_url <> "/fleet/v1/units", [], "") do 41 | {:ok, resp_bodies} -> 42 | units = resp_bodies 43 | |> Enum.flat_map(fn resp -> resp["units"] || [] end) 44 | |> Enum.filter(fn unit -> unit != nil end) 45 | |> Enum.map(&Unit.from_map/1) 46 | {:ok, units} 47 | other -> other 48 | end 49 | end 50 | 51 | 52 | @spec api_get_unit(String.t, String.t) :: {:ok, FleetApi.Unit.t} | {:error, any} 53 | defp api_get_unit(node_url, unit_name) do 54 | case request(:get, node_url <> "/fleet/v1/units/" <> unit_name, [], "") do 55 | {:ok, resp_body} -> 56 | unit = resp_body 57 | |> Unit.from_map 58 | {:ok, unit} 59 | other -> other 60 | end 61 | end 62 | 63 | 64 | @spec api_delete_unit(String.t, String.t) :: :ok | {:error, any} 65 | defp api_delete_unit(node_url, unit_name) do 66 | case request(:delete, node_url <> "/fleet/v1/units/" <> unit_name, [], "", [204]) do 67 | {:ok, _} -> :ok 68 | other -> other 69 | end 70 | end 71 | 72 | 73 | @spec api_set_unit(String.t, String.t, FleetApi.Unit.t) :: :ok | {:error, any} 74 | defp api_set_unit(node_url, unit_name, unit) do 75 | case request(:put, node_url <> "/fleet/v1/units/" <> unit_name, [{"Content-Type", "application/json"}], Poison.encode!(unit), [201, 204]) do 76 | {:ok, _} -> :ok 77 | other -> other 78 | end 79 | end 80 | 81 | 82 | @spec api_list_unit_states(String.t, [{atom, String.t}]) :: {:ok, [FleetApi.UnitState.t]} | {:error, any} 83 | defp api_list_unit_states(node_url, opts \\ []) do 84 | url = node_url <> "/fleet/v1/state" 85 | machine_id = Keyword.get(opts, :machine_id) 86 | unit_name = Keyword.get(opts, :unit_name) 87 | 88 | url = cond do 89 | machine_id && unit_name -> url <> "?machineID=" <> machine_id <> "&unitName=" <> unit_name 90 | machine_id -> url <> "?machineID=" <> machine_id 91 | unit_name -> url <> "?unitName=" <> unit_name 92 | true -> url 93 | end 94 | 95 | case paginated_request(:get, url, [], "") do 96 | {:ok, resp_bodies} -> 97 | states = resp_bodies 98 | |> Enum.flat_map(fn resp -> resp["states"] || [] end) 99 | |> Enum.map(&UnitState.from_map/1) 100 | {:ok, states} 101 | other -> other 102 | end 103 | end 104 | 105 | 106 | @spec api_list_machines(String.t) :: {:ok, [FleetApi.Machine.t]} | {:error, any} 107 | defp api_list_machines(node_url) do 108 | case paginated_request(:get, node_url <> "/fleet/v1/machines", [], "") do 109 | {:ok, resp_bodies} -> 110 | machines = resp_bodies 111 | |> Enum.flat_map(fn resp -> resp["machines"] || [] end) 112 | |> Enum.map(&Machine.from_map/1) 113 | 114 | {:ok, machines} 115 | other -> other 116 | end 117 | end 118 | 119 | @spec api_discovery(String.t) :: {:ok, Map.t} | {:error, any} 120 | defp api_discovery(node_url) do 121 | case request(:get, node_url <> "/fleet/v1/discovery") do 122 | {:ok, discovery} -> {:ok, discovery} 123 | other -> other 124 | end 125 | end 126 | end 127 | end 128 | end -------------------------------------------------------------------------------- /lib/fleet_api/direct.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Direct do 2 | @moduledoc """ 3 | Accesses the Fleet API via a directly-identified node URL. 4 | """ 5 | use FleetApi 6 | use GenServer 7 | 8 | ## GenServer initialization 9 | 10 | def start_link(node_url) do 11 | GenServer.start_link(__MODULE__, node_url) 12 | end 13 | 14 | def init(node_url) do 15 | {:ok, node_url} 16 | end 17 | 18 | @doc """ 19 | Retrieves the Fleet node URL based on the URL provided when the GenServer 20 | was started. 21 | """ 22 | @spec get_node_url(pid) :: String.t 23 | def get_node_url(pid) do 24 | GenServer.call(pid, :get_node_url) 25 | end 26 | 27 | def handle_call(:get_node_url, _from, node_url) do 28 | {:reply, node_url, node_url} 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/fleet_api/error.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Error do 2 | @moduledoc """ 3 | Defines a `FleetApi.Error` struct, representing erros that may be returned 4 | when making Fleet API calls. 5 | 6 | The following fields are public: 7 | 8 | * `code` - The HTTP status code of the response. 9 | * `message` - A human-readable error message explaining the failure. 10 | """ 11 | @type t :: %__MODULE__{} 12 | defstruct code: nil, message: nil 13 | 14 | @spec from_map(%{String.t => any}) :: FleetApi.Error.t 15 | def from_map(error_map) do 16 | %__MODULE__{ 17 | code: error_map["error"]["code"], 18 | message: error_map["error"]["message"] 19 | } 20 | end 21 | end -------------------------------------------------------------------------------- /lib/fleet_api/etcd.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Etcd do 2 | @moduledoc """ 3 | Accesses the Fleet API via a URL discovered through etcd. 4 | """ 5 | require Logger 6 | use FleetApi 7 | use GenServer 8 | 9 | @port_regex ~r/(:\d+)/ 10 | 11 | ## GenServer initialization 12 | def start_link(etcd_token) do 13 | GenServer.start_link(__MODULE__, etcd_token) 14 | end 15 | 16 | def init(etcd_token) do 17 | {:ok, %{etcd_token: etcd_token, last_updated: nil, nodes: []}} 18 | end 19 | 20 | @doc """ 21 | Retrieves a Fleet node URL based on the information stored in etcd, using the 22 | etcd token specified when the GenServer was started. 23 | """ 24 | @spec get_node_url(pid) :: String.t | {:error, any} 25 | def get_node_url(pid) do 26 | case GenServer.call(pid, :get_node_url, 60_000) do 27 | {:ok, node_url} -> 28 | node_url 29 | |> fix_etcd_node_url 30 | 31 | error -> error 32 | end 33 | end 34 | 35 | defp fix_etcd_node_url(node_url) do 36 | config = Application.get_env(:fleet_api, :etcd) 37 | if config[:fix_port_number] do 38 | Regex.replace(@port_regex, node_url, ":#{config[:api_port]}", global: false) 39 | else 40 | node_url 41 | end 42 | end 43 | 44 | def handle_call(:get_node_url, _from, state) do 45 | case get_state(state) do 46 | {:ok, state} -> 47 | case get_valid_node(state.nodes) do 48 | {nil, nodes} -> 49 | Logger.error "[FleetApi] Node list contained no valid nodes!" 50 | {:reply, {:error, :no_valid_nodes}, Map.put(state, :nodes, nodes)} 51 | {node_url, nodes} -> 52 | Logger.debug "[FleetApi] Confirmed node #{inspect node_url} was valid." 53 | {:reply, {:ok, node_url}, Map.put(state, :nodes, nodes)} 54 | end 55 | {:error, reason} -> 56 | {:reply, {:error, reason}, state} 57 | end 58 | end 59 | 60 | # validates the provided state is relevant, and if not, attempts to retrieve 61 | # new state data from etcd.io up to 5 times before failing. 62 | @spec get_state(Map.t, integer) :: {:ok, Map.t} | {:error, any} 63 | defp get_state(state, attempts \\ 0) do 64 | if valid_state?(state) do 65 | {:ok, state} 66 | else 67 | Logger.debug "[FleetApi] Refreshing list of etcd nodes, attempt: #{attempts}." 68 | case refresh_nodes(state.etcd_token) do 69 | {:ok, nodes} -> 70 | node_maps = for node_url <- nodes, do: %{ url: node_url, is_valid: nil } 71 | Logger.debug "[FleetApi] Successfully retrieved list of #{length nodes} etcd nodes." 72 | {:ok, %{state | nodes: node_maps, last_updated: :os.timestamp}} 73 | {:error, reason} -> 74 | if attempts < 5 do 75 | get_state(state, attempts + 1) 76 | else 77 | Logger.error "[FleetApi] Couldn't refresh list of etcd nodes after 5 attempts. Failing." 78 | {:error, reason} 79 | end 80 | end 81 | end 82 | end 83 | 84 | @spec refresh_nodes(String.t) :: [String.t] 85 | defp refresh_nodes(etcd_token) do 86 | Logger.debug "[FleetApi] Refreshing nodes for etcd token: #{etcd_token}" 87 | try do 88 | case request(:get, "https://discovery.etcd.io/#{etcd_token}", [{"Accept", "application/json"}]) do 89 | {:ok, %{"node" => node}} -> 90 | nodes = for n <- node["nodes"] || [], do: n["value"] 91 | {:ok, nodes} 92 | {:error, error} -> 93 | Logger.error "[FleetApi] An error occurred refreshing the list of etcd nodes: #{inspect error}." 94 | {:error, error.reason} 95 | end 96 | rescue e -> 97 | Logger.error "[FleetApi] An exception was raised during the request to etcd: #{inspect e}" 98 | Logger.debug "[FleetApi] Issuing a request to google just to check httpoison..." 99 | test_httpoison_request() 100 | 101 | {:error, e} 102 | catch e -> 103 | Logger.error "[FleetApi] refresh_node request threw: #{inspect e}." 104 | {:error, e} 105 | end 106 | end 107 | 108 | defp test_httpoison_request() do 109 | try do 110 | case request(:get, "https://google.com", [], "", 200..399, false) do 111 | {:ok, result} -> 112 | Logger.debug "[FleetApi] google.com request succeeded: #{inspect result}" 113 | {:error, error} -> 114 | Logger.warn "[FleetApi] google.com request failed: #{inspect error}" 115 | end 116 | rescue e -> 117 | Logger.warn "[FleetApi] google.com request raised an exception: #{inspect e}" 118 | catch e -> 119 | Logger.error "[FleetApi] google.com request threw: #{inspect e}." 120 | end 121 | end 122 | 123 | # finds the first node for which the discovery endpoint returns data. 124 | @spec get_valid_node([Map.t]) :: {String.t | nil, [Map.t]} 125 | defp get_valid_node(nodes) do 126 | Logger.debug "[FleetApi] Finding a valid node..." 127 | filtered = Enum.filter(nodes, fn node -> node.is_valid != false end) 128 | get_valid_node(nodes, filtered) 129 | end 130 | 131 | @spec get_valid_node([Map.t], [Map.t]) :: {String.t | nil, [Map.t]} 132 | defp get_valid_node(nodes, candidates) when length(candidates) > 0 do 133 | # Seed the RNG 134 | :random.seed(:os.timestamp) 135 | node = candidates 136 | |> Enum.shuffle 137 | |> List.first 138 | 139 | if node.is_valid == true do 140 | {node.url, nodes} 141 | else 142 | is_valid = validate_node(node.url) 143 | 144 | # Update our nodes list now that we know if this node is valid or not 145 | index = Enum.find_index(nodes, fn n -> n.url == node.url end) 146 | node = Map.put(node, :is_valid, is_valid) 147 | nodes = List.replace_at(nodes, index, node) 148 | 149 | if is_valid do 150 | {node.url, nodes} 151 | else 152 | # Since this node isn't valid, recurse back into get_valid_nodes to try 153 | # to find a valid node. 154 | get_valid_node(nodes) 155 | end 156 | end 157 | end 158 | 159 | defp get_valid_node(nodes, candidates) when length(candidates) == 0 do 160 | Logger.warn "[FleetApi] All nodes have been checked, and no valid nodes were found." 161 | {nil, nodes} 162 | end 163 | 164 | defp validate_node(node_url) do 165 | node_url 166 | |> fix_etcd_node_url 167 | |> api_discovery 168 | |> case do 169 | {:ok, _discovery} -> true 170 | _ -> false 171 | end 172 | end 173 | 174 | @spec valid_state?(Map.t) :: boolean 175 | defp valid_state?(state) do 176 | valid_token = state.etcd_token != nil && String.length(String.strip(state.etcd_token)) > 0 177 | 178 | # Check that we've refreshed in the last 10 minutes 179 | valid_time = state.last_updated != nil && :timer.now_diff(:os.timestamp, state.last_updated) < 600_000_000 180 | 181 | valid_nodes = state.nodes != nil && length(state.nodes) > 0 182 | 183 | valid_token && valid_time && valid_nodes 184 | end 185 | end -------------------------------------------------------------------------------- /lib/fleet_api/machine.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Machine do 2 | @moduledoc """ 3 | Defines a `FleetApi.Machine` struct, representing a host in the Fleet 4 | cluster. It uses the host's [machine-id](http://www.freedesktop.org/software/systemd/man/machine-id.html) 5 | as a unique identifier. 6 | 7 | The following fields are public: 8 | 9 | * `id` - unique identifier of Machine entity. 10 | * `primaryIP` - IP address that should be used to communicate with this host. 11 | * `metadata` - dictionary of key-value data published by the machine. 12 | """ 13 | @type t :: %__MODULE__{} 14 | defstruct id: nil, primaryIP: nil, metadata: nil 15 | 16 | @spec from_map(%{String.t => any}) :: FleetApi.Machine.t 17 | def from_map(machine_map) do 18 | %__MODULE__{ 19 | id: machine_map["id"], 20 | primaryIP: machine_map["primaryIP"], 21 | metadata: machine_map["metadata"] 22 | } 23 | end 24 | 25 | @doc """ 26 | Checks if this machine is responding to requests by attempting to access the 27 | API discovery endpoint of the Fleet API. 28 | """ 29 | @spec reachable?(FleetApi.Machine.t, integer) :: boolean 30 | def reachable?(machine, port \\ 7002) do 31 | {:ok, pid} = FleetApi.Direct.start_link("http://#{machine.primaryIP}:#{port}") 32 | 33 | case FleetApi.Direct.get_api_discovery(pid) do 34 | {:ok, _} -> true 35 | _ -> false 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /lib/fleet_api/request.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Request do 2 | @moduledoc false 3 | defmacro __using__(_) do 4 | quote do 5 | require Logger 6 | # Issue a request to the specified url, optionally passing a list of header 7 | # tuples and a body. The method argument specifies the HTTP method in the 8 | # form of an atom, e.g. :get, :post, :delete, etc. 9 | @spec request(atom, String.t, [tuple], String.t, [integer], boolean) :: {:ok, any} | {:error, any} 10 | defp request(method, url, headers \\ [], body \\ "", expected_status \\ [200], parse_response \\ true) do 11 | default_options = [recv_timeout: 30_000] 12 | options = case Application.get_env(:fleet_api, :proxy) do 13 | nil -> default_options 14 | proxy_opts -> [hackney: [proxy: proxy_opts]] ++ default_options 15 | end 16 | 17 | {_, _, request_id} = :os.timestamp() 18 | 19 | Logger.debug "[FleetApi] issuing request #{request_id} to #{url}" 20 | 21 | case HTTPoison.request(method, url, body, headers, options) do 22 | {:ok, %HTTPoison.Response{:status_code => status} = response} when status in 400..599 -> 23 | Logger.error "[FleetApi] request #{request_id} to #{url} returned status code #{inspect status}" 24 | if String.length(response.body) != 0 do 25 | error = response.body 26 | |> Poison.decode! 27 | |> FleetApi.Error.from_map 28 | {:error, %{reason: error}} 29 | else 30 | {:error, %{reason: "Received #{status} response."}} 31 | end 32 | {:ok, %HTTPoison.Response{status_code: status} = response} -> 33 | if status in expected_status do 34 | Logger.debug "[FleetApi] request #{request_id} to #{url} succeeded with status code #{inspect status}" 35 | if String.length(response.body) != 0 && parse_response do 36 | {:ok, Poison.decode!(response.body)} 37 | else 38 | {:ok, response} 39 | end 40 | else 41 | Logger.error "[FleetApi] request #{request_id} to #{url} returned status code #{inspect status}" 42 | {:error, %{reason: "Expected response status in #{inspect expected_status} but got #{status}."}} 43 | end 44 | error -> 45 | Logger.error "[FleetApi] request #{request_id} to #{url} did not complete. Error: #{inspect error}." 46 | error 47 | end 48 | end 49 | end 50 | end 51 | end -------------------------------------------------------------------------------- /lib/fleet_api/unit.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Unit do 2 | @moduledoc """ 3 | Defines a `FleetApi.Unit` struct, representing a service in a fleet cluster. 4 | 5 | The following fields are public: 6 | 7 | * `name` - unique identifier of entity. 8 | * `options` - list of UnitOption entities. 9 | * `desiredState` - state the user wishes the unit to be in ("inactive", "loaded", or "launched"). 10 | * `currentState` - state the unit is currently in (same possible values as desiredState). 11 | * `machineID` - ID of machine to which the unit is scheduled. 12 | """ 13 | @type t :: %__MODULE__{} 14 | alias FleetApi.UnitOption 15 | 16 | defstruct name: nil, options: [], desiredState: nil, currentState: nil, machineID: nil 17 | 18 | @spec from_map(%{String.t => any}) :: FleetApi.Unit.t 19 | def from_map(unit_map) do 20 | %__MODULE__{ 21 | name: unit_map["name"], 22 | options: (unit_map["options"] || []) |> Enum.map(&UnitOption.from_map/1), 23 | desiredState: unit_map["desiredState"], 24 | currentState: unit_map["currentState"], 25 | machineID: unit_map["machineID"] 26 | } 27 | end 28 | end -------------------------------------------------------------------------------- /lib/fleet_api/unit_option.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.UnitOption do 2 | @moduledoc """ 3 | Defines a `FleetApi.UnitOption` struct, representing one segment of the information used to describe a unit. 4 | 5 | The following fields are public: 6 | 7 | * `name` - name of option (e.g. "BindsTo", "After", "ExecStart"). 8 | * `section` - name of section that contains the option (e.g. "Unit", "Service", "Socket"). 9 | * `value` - value of option (e.g. "/usr/bin/docker run busybox /bin/sleep 1000"). 10 | """ 11 | @type t :: %__MODULE__{} 12 | 13 | defstruct section: nil, name: nil, value: nil 14 | 15 | @spec from_map(%{String.t => any}) :: FleetApi.UnitOption.t 16 | def from_map(option_map) do 17 | %__MODULE__{ 18 | section: option_map["section"], 19 | name: option_map["name"], 20 | value: option_map["value"] 21 | } 22 | end 23 | end -------------------------------------------------------------------------------- /lib/fleet_api/unit_state.ex: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.UnitState do 2 | @moduledoc """ 3 | Defines a `FleetApi.UnitState` struct, representing the current state of a particular unit. 4 | 5 | The following fields are public: 6 | 7 | * `name` - unique identifier of entity. 8 | * `hash` - SHA1 hash of underlying unit file. 9 | * `machineID` - ID of machine from which this state originated. 10 | * `systemdLoadState` - load state as reported by systemd. 11 | * `systemdActiveState` - active state as reported by systemd. 12 | * `systemdSubState` - sub state as reported by systemd. 13 | A unit state represents the current state of a given unit. 14 | """ 15 | @type t :: %__MODULE__{} 16 | 17 | defstruct name: nil, hash: nil, machineID: nil, systemdLoadState: nil, systemdActiveState: nil, systemdSubState: nil 18 | 19 | @spec from_map(%{String.t => any}) :: FleetApi.UnitState.t 20 | def from_map(state_map) do 21 | %__MODULE__{ 22 | name: state_map["name"], 23 | hash: state_map["hash"], 24 | machineID: state_map["machineID"], 25 | systemdLoadState: state_map["systemdLoadState"], 26 | systemdActiveState: state_map["systemdActiveState"], 27 | systemdSubState: state_map["systemdSubState"] 28 | } 29 | 30 | end 31 | end -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [app: :fleet_api, 6 | version: "0.0.15", 7 | elixir: "~> 1.0", 8 | deps: deps, 9 | description: description, 10 | package: package, 11 | docs: [readme: "README.md", 12 | main: "README", 13 | source_url: "https://github.com/jordan0day/fleet-api"]] 14 | end 15 | 16 | # Configuration for the OTP application 17 | # 18 | # Type `mix help compile.app` for more information 19 | def application do 20 | [ applications: [:logger, :httpoison], 21 | env: [etcd: [fix_port_number: true, api_port: 7002]]] 22 | end 23 | 24 | # Dependencies can be Hex packages: 25 | # 26 | # {:mydep, "~> 0.3.0"} 27 | # 28 | # Or git/path repositories: 29 | # 30 | # {:mydep, git: "https://github.com/elixir-lang/mydep.git", tag: "0.1.0"} 31 | # 32 | # Type `mix help deps` for more examples and options 33 | defp deps do 34 | [ 35 | {:httpoison, "0.7.1"}, 36 | {:poison, "1.3.1"}, 37 | {:exvcr, "0.4.0", only: :test}, 38 | {:earmark, "~> 0.1", only: :dev}, 39 | {:ex_doc, "~> 0.7", only: :dev}] 40 | end 41 | 42 | defp description do 43 | "A simple wrapper for the Fleet API. Can be used with etcd tokens or via direct node URLs." 44 | end 45 | 46 | defp package do 47 | [contributors: ["Jordan Day"], 48 | licenses: ["MIT"], 49 | links: %{"GitHub" => "https://github.com/jordan0day/fleet-api.git"}] 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{"earmark": {:hex, :earmark, "0.1.17"}, 2 | "ex_doc": {:hex, :ex_doc, "0.7.3"}, 3 | "exactor": {:hex, :exactor, "2.1.2"}, 4 | "exjsx": {:hex, :exjsx, "3.1.0"}, 5 | "exvcr": {:hex, :exvcr, "0.4.0"}, 6 | "hackney": {:hex, :hackney, "1.3.0"}, 7 | "httpoison": {:hex, :httpoison, "0.7.1"}, 8 | "idna": {:hex, :idna, "1.0.2"}, 9 | "jsx": {:hex, :jsx, "2.4.0"}, 10 | "meck": {:hex, :meck, "0.8.3"}, 11 | "poison": {:hex, :poison, "1.3.1"}, 12 | "ssl_verify_hostname": {:hex, :ssl_verify_hostname, "1.0.5"}} 13 | -------------------------------------------------------------------------------- /test/direct_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Direct.Test do 2 | use ExUnit.Case 3 | use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney 4 | 5 | import FleetApi.Direct 6 | 7 | setup_all do 8 | ExVCR.Config.cassette_library_dir("fixture/vcr_cassettes", "fixture/custom_cassettes") 9 | :ok 10 | end 11 | 12 | test "list_units" do 13 | use_cassette "list_units", custom: true do 14 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 15 | {:ok, units} = list_units(pid) 16 | 17 | assert 4 == length(units) 18 | 19 | assert %FleetApi.Unit{ 20 | currentState: "launched", 21 | desiredState: "launched", 22 | machineID: "820c30c0867844129d63f4409871ba39", 23 | name: "subgun-http.service", 24 | options: [ 25 | %FleetApi.UnitOption{ 26 | name: "Description", 27 | section: "Unit", 28 | value: "subgun"}, 29 | %FleetApi.UnitOption{ 30 | name: "ExecStartPre", 31 | section: "Service", 32 | value: "-/usr/bin/docker kill subgun-%i"}, 33 | %FleetApi.UnitOption{ 34 | name: "ExecStartPre", 35 | section: "Service", 36 | value: "-/usr/bin/docker rm subgun-%i"}, 37 | %FleetApi.UnitOption{ 38 | name: "ExecStart", 39 | section: "Service", 40 | value: "/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun"}, 41 | %FleetApi.UnitOption{ 42 | name: "ExecStop", 43 | section: "Service", 44 | value: "/usr/bin/docker stop subgun-%i"}, 45 | %FleetApi.UnitOption{ 46 | name: "Conflicts", 47 | section: "X-Fleet", 48 | value: "subgun-http@*.service"}]} in units 49 | 50 | assert %FleetApi.Unit{ 51 | currentState: "inactive", 52 | desiredState: "launched", 53 | machineID: "76ffb3a4588c46f3941c073df77be5e9", 54 | name: "subgun-http@.service", 55 | options: [ 56 | %FleetApi.UnitOption{ 57 | name: "Description", 58 | section: "Unit", 59 | value: "subgun"}, 60 | %FleetApi.UnitOption{ 61 | name: "ExecStartPre", 62 | section: "Service", 63 | value: "-/usr/bin/docker kill subgun-%i"}, 64 | %FleetApi.UnitOption{ 65 | name: "ExecStartPre", 66 | section: "Service", 67 | value: "-/usr/bin/docker rm subgun-%i"}, 68 | %FleetApi.UnitOption{ 69 | name: "ExecStart", 70 | section: "Service", 71 | value: "/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun"}, 72 | %FleetApi.UnitOption{ 73 | name: "ExecStop", 74 | section: "Service", 75 | value: "/usr/bin/docker stop subgun-%i"}, 76 | %FleetApi.UnitOption{ 77 | name: "Conflicts", 78 | section: "X-Fleet", 79 | value: "subgun-http@*.service"}]} in units 80 | end 81 | end 82 | 83 | test "list_units empty list" do 84 | use_cassette "list_units_empty", custom: true do 85 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 86 | {:ok, units} = list_units(pid) 87 | 88 | assert 0 == length(units) 89 | end 90 | end 91 | 92 | test "list_units nil list instead of empty list" do 93 | use_cassette "list_units_null", custom: true do 94 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 95 | {:ok, units} = list_units(pid) 96 | 97 | assert 0 == length(units) 98 | end 99 | end 100 | 101 | test "list_units no units field in response" do 102 | use_cassette "list_units_weird_response", custom: true do 103 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 104 | {:ok, units} = list_units(pid) 105 | 106 | assert 0 == length(units) 107 | end 108 | end 109 | 110 | test "get_unit" do 111 | use_cassette "get_unit", custom: true do 112 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 113 | {:ok, unit} = get_unit(pid, "subgun-http.service") 114 | 115 | assert "subgun-http.service" == unit.name 116 | assert "launched" == unit.currentState 117 | assert "launched" == unit.desiredState 118 | assert %FleetApi.UnitOption{name: "Description", section: "Unit", value: "subgun"} in unit.options 119 | end 120 | end 121 | 122 | test "get_unit empty options list" do 123 | use_cassette "get_unit_empty_options", custom: true do 124 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 125 | {:ok, unit} = get_unit(pid, "subgun-http.service") 126 | 127 | assert "subgun-http.service" == unit.name 128 | assert "launched" == unit.currentState 129 | assert "launched" == unit.desiredState 130 | assert length(unit.options) == 0 131 | end 132 | end 133 | 134 | test "get_unit null options list" do 135 | use_cassette "get_unit_null_options", custom: true do 136 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 137 | {:ok, unit} = get_unit(pid, "subgun-http.service") 138 | 139 | assert "subgun-http.service" == unit.name 140 | assert "launched" == unit.currentState 141 | assert "launched" == unit.desiredState 142 | assert length(unit.options) == 0 143 | end 144 | end 145 | 146 | test "get_unit missing options list" do 147 | use_cassette "get_unit_missing_options", custom: true do 148 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 149 | {:ok, unit} = get_unit(pid, "subgun-http.service") 150 | 151 | assert "subgun-http.service" == unit.name 152 | assert "launched" == unit.currentState 153 | assert "launched" == unit.desiredState 154 | assert length(unit.options) == 0 155 | end 156 | end 157 | 158 | test "delete_unit" do 159 | use_cassette "delete_unit", custom: true do 160 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 161 | assert :ok = delete_unit(pid, "subgun-http.service") 162 | end 163 | end 164 | 165 | test "list_machines" do 166 | use_cassette "list_machines", custom: true do 167 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 168 | {:ok, machines} = list_machines(pid) 169 | 170 | assert length(machines) == 3 171 | 172 | assert %FleetApi.Machine{ 173 | id: "76ffb3a4588c46f3941c073df77be5e9", 174 | metadata: nil, 175 | primaryIP: "127.0.0.1"} in machines 176 | 177 | assert %FleetApi.Machine{ 178 | id: "820c30c0867844129d63f4409871ba39", 179 | metadata: nil, 180 | primaryIP: "127.0.0.2"} in machines 181 | 182 | assert %FleetApi.Machine{ 183 | id: "f439a6a2dd8f43dbad60994cc1fb68f6", 184 | metadata: nil, 185 | primaryIP: "127.0.0.3"} in machines 186 | end 187 | end 188 | 189 | test "list_machines empty machine list" do 190 | use_cassette "list_machines_empty", custom: true do 191 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 192 | {:ok, machines} = list_machines(pid) 193 | 194 | assert length(machines) == 0 195 | end 196 | end 197 | 198 | test "list_machines null machine list" do 199 | use_cassette "list_machines_null", custom: true do 200 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 201 | {:ok, machines} = list_machines(pid) 202 | 203 | assert length(machines) == 0 204 | end 205 | end 206 | 207 | test "list_machines no machines field in response" do 208 | use_cassette "list_machines_weird_response", custom: true do 209 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 210 | {:ok, machines} = list_machines(pid) 211 | 212 | assert length(machines) == 0 213 | end 214 | end 215 | 216 | test "list_unit_states" do 217 | use_cassette "list_unit_states", custom: true do 218 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 219 | {:ok, states} = list_unit_states(pid) 220 | 221 | assert length(states) == 1 222 | 223 | assert %FleetApi.UnitState{ 224 | hash: "eef29cad431ad16c8e164400b2f3c85afd73b238", 225 | machineID: "820c30c0867844129d63f4409871ba39", 226 | name: "subgun-http.service", 227 | systemdActiveState: "active", 228 | systemdLoadState: "loaded", 229 | systemdSubState: "running"} in states 230 | end 231 | end 232 | 233 | test "list_unit_states empty states list" do 234 | use_cassette "list_unit_states_empty", custom: true do 235 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 236 | {:ok, states} = list_unit_states(pid) 237 | 238 | assert length(states) == 0 239 | end 240 | end 241 | 242 | test "list_unit_states null states list" do 243 | use_cassette "list_unit_states_null", custom: true do 244 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 245 | {:ok, states} = list_unit_states(pid) 246 | 247 | assert length(states) == 0 248 | end 249 | end 250 | 251 | test "list_unit_states missing states list" do 252 | use_cassette "list_unit_states_weird_response", custom: true do 253 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 254 | {:ok, states} = list_unit_states(pid) 255 | 256 | assert length(states) == 0 257 | end 258 | end 259 | 260 | test "create unit" do 261 | use_cassette "set_unit_new", custom: true do 262 | unit = %FleetApi.Unit{ 263 | name: "test.service", 264 | desiredState: "launched", 265 | options: [ 266 | %FleetApi.UnitOption{ 267 | name: "ExecStart", 268 | section: "Service", 269 | value: "/usr/bin/sleep 3000" 270 | } 271 | ] 272 | 273 | } 274 | 275 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 276 | assert :ok = set_unit(pid, "test.service", unit) 277 | end 278 | end 279 | 280 | test "update_unit_desired_state" do 281 | use_cassette "set_unit_update", custom: true do 282 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 283 | assert :ok = set_unit(pid, "test.service", "launched") 284 | end 285 | end 286 | 287 | test "get_api_discovery" do 288 | use_cassette "get_api_discovery", custom: true do 289 | {:ok, pid} = FleetApi.Direct.start_link("http://localhost:7002") 290 | {:ok, discovery} = get_api_discovery(pid) 291 | 292 | assert discovery["discoveryVersion"] == "v1" 293 | assert discovery["id"] == "fleet:v1" 294 | assert discovery["title"] == "fleet API" 295 | end 296 | end 297 | end -------------------------------------------------------------------------------- /test/etcd_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FleetApi.Etcd.Test do 2 | use ExUnit.Case 3 | use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney 4 | 5 | import FleetApi.Etcd 6 | 7 | setup_all do 8 | ExVCR.Config.cassette_library_dir("fixture/vcr_cassettes", "fixture/custom_cassettes") 9 | :ok 10 | end 11 | 12 | test "list_units" do 13 | use_cassette "etcd_list_units", custom: true do 14 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 15 | {:ok, units} = list_units(pid) 16 | 17 | assert 4 == length(units) 18 | 19 | assert %FleetApi.Unit{ 20 | currentState: "launched", 21 | desiredState: "launched", 22 | machineID: "820c30c0867844129d63f4409871ba39", 23 | name: "subgun-http.service", 24 | options: [ 25 | %FleetApi.UnitOption{ 26 | name: "Description", 27 | section: "Unit", 28 | value: "subgun"}, 29 | %FleetApi.UnitOption{ 30 | name: "ExecStartPre", 31 | section: "Service", 32 | value: "-/usr/bin/docker kill subgun-%i"}, 33 | %FleetApi.UnitOption{ 34 | name: "ExecStartPre", 35 | section: "Service", 36 | value: "-/usr/bin/docker rm subgun-%i"}, 37 | %FleetApi.UnitOption{ 38 | name: "ExecStart", 39 | section: "Service", 40 | value: "/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun"}, 41 | %FleetApi.UnitOption{ 42 | name: "ExecStop", 43 | section: "Service", 44 | value: "/usr/bin/docker stop subgun-%i"}, 45 | %FleetApi.UnitOption{ 46 | name: "Conflicts", 47 | section: "X-Fleet", 48 | value: "subgun-http@*.service"}]} in units 49 | 50 | assert %FleetApi.Unit{ 51 | currentState: "inactive", 52 | desiredState: "launched", 53 | machineID: "76ffb3a4588c46f3941c073df77be5e9", 54 | name: "subgun-http@.service", 55 | options: [ 56 | %FleetApi.UnitOption{ 57 | name: "Description", 58 | section: "Unit", 59 | value: "subgun"}, 60 | %FleetApi.UnitOption{ 61 | name: "ExecStartPre", 62 | section: "Service", 63 | value: "-/usr/bin/docker kill subgun-%i"}, 64 | %FleetApi.UnitOption{ 65 | name: "ExecStartPre", 66 | section: "Service", 67 | value: "-/usr/bin/docker rm subgun-%i"}, 68 | %FleetApi.UnitOption{ 69 | name: "ExecStart", 70 | section: "Service", 71 | value: "/usr/bin/docker run --rm --name subgun-%i -e SUBGUN_LISTEN=127.0.0.1:8080 -e SUBGUN_LISTS=recv@sandbox2398.mailgun.org -e SUBGUN_API_KEY=key-779ru4cibbnhfa1qp7a3apyvwkls7ny7 -p 8080:8080 coreos/subgun"}, 72 | %FleetApi.UnitOption{ 73 | name: "ExecStop", 74 | section: "Service", 75 | value: "/usr/bin/docker stop subgun-%i"}, 76 | %FleetApi.UnitOption{ 77 | name: "Conflicts", 78 | section: "X-Fleet", 79 | value: "subgun-http@*.service"}]} in units 80 | end 81 | end 82 | 83 | test "list_units empty list" do 84 | use_cassette "etcd_list_units_empty", custom: true do 85 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 86 | {:ok, units} = list_units(pid) 87 | 88 | assert 0 == length(units) 89 | end 90 | end 91 | 92 | test "list_units nil list instead of empty list" do 93 | use_cassette "etcd_list_units_null", custom: true do 94 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 95 | {:ok, units} = list_units(pid) 96 | 97 | assert 0 == length(units) 98 | end 99 | end 100 | 101 | test "list_units no units field in response" do 102 | use_cassette "etcd_list_units_weird_response", custom: true do 103 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 104 | {:ok, units} = list_units(pid) 105 | 106 | assert 0 == length(units) 107 | end 108 | end 109 | 110 | test "get_unit" do 111 | use_cassette "etcd_get_unit", custom: true do 112 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 113 | {:ok, unit} = get_unit(pid, "subgun-http.service") 114 | 115 | assert "subgun-http.service" == unit.name 116 | assert "launched" == unit.currentState 117 | assert "launched" == unit.desiredState 118 | assert %FleetApi.UnitOption{name: "Description", section: "Unit", value: "subgun"} in unit.options 119 | end 120 | end 121 | 122 | test "delete_unit" do 123 | use_cassette "etcd_delete_unit", custom: true do 124 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 125 | assert :ok = delete_unit(pid, "subgun-http.service") 126 | end 127 | end 128 | 129 | test "list_machines" do 130 | use_cassette "etcd_list_machines", custom: true do 131 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 132 | {:ok, machines} = list_machines(pid) 133 | 134 | assert length(machines) == 3 135 | 136 | assert %FleetApi.Machine{ 137 | id: "76ffb3a4588c46f3941c073df77be5e9", 138 | metadata: nil, 139 | primaryIP: "127.0.0.1"} in machines 140 | 141 | assert %FleetApi.Machine{ 142 | id: "820c30c0867844129d63f4409871ba39", 143 | metadata: nil, 144 | primaryIP: "127.0.0.2"} in machines 145 | 146 | assert %FleetApi.Machine{ 147 | id: "f439a6a2dd8f43dbad60994cc1fb68f6", 148 | metadata: nil, 149 | primaryIP: "127.0.0.3"} in machines 150 | end 151 | end 152 | 153 | test "list_unit_states" do 154 | use_cassette "etcd_list_unit_states", custom: true do 155 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 156 | {:ok, states} = list_unit_states(pid) 157 | 158 | assert length(states) == 1 159 | 160 | assert %FleetApi.UnitState{ 161 | hash: "eef29cad431ad16c8e164400b2f3c85afd73b238", 162 | machineID: "820c30c0867844129d63f4409871ba39", 163 | name: "subgun-http.service", 164 | systemdActiveState: "active", 165 | systemdLoadState: "loaded", 166 | systemdSubState: "running"} in states 167 | end 168 | end 169 | 170 | test "create unit" do 171 | use_cassette "etcd_set_unit_new", custom: true do 172 | unit = %FleetApi.Unit{ 173 | name: "test.service", 174 | desiredState: "launched", 175 | options: [ 176 | %FleetApi.UnitOption{ 177 | name: "ExecStart", 178 | section: "Service", 179 | value: "/usr/bin/sleep 3000" 180 | } 181 | ] 182 | 183 | } 184 | 185 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 186 | assert :ok = set_unit(pid, "test.service", unit) 187 | end 188 | end 189 | 190 | test "update_unit_desired_state" do 191 | use_cassette "etcd_set_unit_update", custom: true do 192 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 193 | assert :ok = set_unit(pid, "test.service", "launched") 194 | end 195 | end 196 | 197 | test "get_api_discovery" do 198 | use_cassette "etcd_get_api_discovery", custom: true do 199 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 200 | {:ok, discovery} = get_api_discovery(pid) 201 | 202 | assert discovery["discoveryVersion"] == "v1" 203 | assert discovery["id"] == "fleet:v1" 204 | assert discovery["title"] == "fleet API" 205 | end 206 | end 207 | 208 | test "list_units no etcd nodes available" do 209 | use_cassette "etcd_response_no_nodes", custom: true do 210 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 211 | 212 | assert {:error, :no_valid_nodes} == FleetApi.Etcd.list_units(pid) 213 | end 214 | end 215 | 216 | test "list_units refresh_nodes call gets 503" do 217 | use_cassette "etcd_response_503_response", custom: true do 218 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 219 | 220 | assert {:error, "Received 503 response."} == FleetApi.Etcd.list_units(pid) 221 | end 222 | end 223 | 224 | test "list_units multiple calls only calls etcd and discovery once" do 225 | use_cassette "etcd_list_units_multiple_calls", custom: true do 226 | {:ok, pid} = FleetApi.Etcd.start_link("abcd1234") 227 | 228 | {:ok, units} = list_units(pid) 229 | 230 | assert 4 == length(units) 231 | 232 | {:ok, units} = list_units(pid) 233 | {:ok, units} = list_units(pid) 234 | end 235 | end 236 | end -------------------------------------------------------------------------------- /test/fleet_api_test.exs: -------------------------------------------------------------------------------- 1 | defmodule FleetApiTest do 2 | 3 | end -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------