├── .editorconfig
├── .github
├── FUNDING.yml
└── workflows
│ └── release.yml
├── .gitignore
├── .goreleaser.yaml
├── LICENSE
├── README.md
├── go.mod
├── go.sum
├── nori
├── adapter
│ └── adapter.go
├── amazon
│ ├── aws.go
│ ├── ci.go
│ ├── instances.go
│ └── logs.go
├── fleek
│ ├── ci.go
│ ├── fleek.go
│ ├── instances.go
│ └── logs.go
├── heroku
│ ├── apps.go
│ ├── ci.go
│ ├── heroku.go
│ ├── instances.go
│ └── logs.go
├── models
│ ├── apps.go
│ ├── instances.go
│ ├── log.go
│ └── pipeline.go
├── nor.go
├── render
│ ├── ci.go
│ ├── instances.go
│ ├── logs.go
│ └── render.go
└── vultr
│ ├── ci.go
│ ├── instances.go
│ ├── logs.go
│ └── vultr.go
├── planor.go
├── screen-ci.png
├── screen-logs.png
└── ui
├── navigation
└── navigation.go
├── ui.go
├── uictx
└── uictx.go
└── views
├── ci
└── ci.go
├── instances
└── instances.go
├── logs
└── logs.go
└── views.go
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 | indent_style = space
9 | indent_size = 2
10 |
11 | [Makefile]
12 | indent_style = tab
13 |
14 | [*.go]
15 | indent_style = tab
16 | indent_size = 2
17 |
18 | [*.{diff,md}]
19 | trim_trailing_whitespace = false
20 |
21 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | custom: ["https://github.com/mrusme#support"]
2 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | tags:
6 | - '*'
7 |
8 | jobs:
9 |
10 | release:
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/checkout@v4
14 |
15 | - name: Set up Go
16 | uses: actions/setup-go@v5
17 | with:
18 | go-version: 1.18
19 |
20 | - name: Run GoReleaser
21 | uses: goreleaser/goreleaser-action@v6
22 | with:
23 | distribution: goreleaser
24 | version: latest
25 | args: release --rm-dist --timeout 80m
26 | env:
27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
28 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /planor
2 |
3 |
--------------------------------------------------------------------------------
/.goreleaser.yaml:
--------------------------------------------------------------------------------
1 | # .goreleaser.yaml
2 | builds:
3 | -
4 | env:
5 | - CGO_ENABLED=0
6 | goos:
7 | - darwin
8 | - linux
9 | - netbsd
10 | - openbsd
11 | - freebsd
12 | # - plan9
13 | # - android
14 | - windows
15 | goarch:
16 | - 386
17 | - amd64
18 | - arm
19 | - arm64
20 | goarm:
21 | - 6
22 | - 7
23 | ignore:
24 | - goos: darwin
25 | goarch: 386
26 | - goos: darwin
27 | goarch: arm
28 | - goos: netbsd
29 | goarch: arm64
30 | - goos: freebsd
31 | goarm: arm64
32 | - goos: plan9
33 | goarm: arm64
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Planor
2 | ------
3 | [](https://raw.githubusercontent.com/mrusme/go-fleek/master/LICENSE)
4 |
5 | ```
6 |
7 | === T H E C L O U D A V I A T O R ===
8 |
9 | ⠀⠀⠀⠀⠀⠀⠀⠀⢶⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
10 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
11 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢻⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
12 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣶⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
13 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
14 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠛⢿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
15 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
16 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⣿⣿⣿⣿⣶⣄⠀⠀⠀⠀⠄⠄⠒⠒⠒⠒⠒⠒⠄⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
17 | ⠀⠀⠀⣀⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⢿⣿⣿⣿⣿⣦⣤⣶⡀⠀⠀⠀⠀⠀⠀⢀⣠⡴⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
18 | ⠀⠀⠀⠈⠻⢿⣷⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣽⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣶⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
19 | ⠀⠀⠀⠀⠰⣾⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣴⣾⣿⣿⣿⣿⣿⣿⡿⢿⣿⣿⣿⣿⣿⣯⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
20 | ⠀⠀⠀⠀⠀⣿⣿⣿⣿⣟⠛⠀⠀⠀⠀⢀⣀⣤⣶⣾⣿⣿⠿⠿⠛⠛⠉⠉⠉⠀⠀⠀⠀⠈⠉⠛⠿⣿⣿⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
21 | ⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣆⣠⣤⣶⣿⠿⠟⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⣿⣿⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
22 | ⠀⠀⠀⠀⠀⢹⣿⠿⠿⣿⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⢿⣿⣿⣿⣷⣶⣄⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀
23 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠿⢿⣿⣿⣿⣶⣤⣀⠀⠀⠀⠀⠀
24 | ⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠿⣿⣿⣿⣶⣤⣀⠀
25 | /\\\\\\\ /\\ /\\\ /\\\\ /\\ /\\\\ /\\\\\\\ ⠈⠙⠛⠿⣿⣿⣶⣤
26 | \/\\////\\\\/\\ /\\\\\\ \/\\\\\ \/\\ /\\//\\ \/\\///\\\ ⠈⠉⠛⠿⠿⠶
27 | \/\\ \/\\\\/\\ /\\///\\\ \/\\/\\\ \/\\ /\\/ \///\\ \/\\ \/\\
28 | \/\\\\\\\/ \/\\ \/\\ \/\\ \/\\//\\ \/\\ /\\ \//\\\/\\\\\\\/
29 | \/\\//// \/\\ \/\\\\\\\\ \/\\\/ \\\/\\\/\\ \/\\\/\\///\\\
30 | \/\\ \/\\ \/\\////\\ \/\\ \/\\\/\\\//\\ /\\ \/\\ \// \\
31 | \/\\ \/\\ \/\\ \/\\ \/\\ \//\\\\\ \///\\ /\\ \/\\ \ /\\
32 | \/\\ \/\\\\\\\\/\\ \/\\ \/\\ \//\\\\ \///\\\ \/\\ \ /\\
33 | \// \/////// \// \// \// \//// \/// \// \///
34 |
35 | ```
36 |
37 | [](https://matrix.to/#/%21PHlbgZTdrhjkCJrfVY%3Amatrix.org)
39 |
40 | Planor is a text user interface for cloud services. It currently supports the
41 | following cloud service providers and cloud services:
42 |
43 | - Amazon Web Services (AWS)
44 | - [x] Elastic Cloud Compute
45 | - [x] CodePipeline
46 | - [x] CloudWatch Logs
47 | - Vultr
48 | - [x] Cloud Instances
49 | - Heroku
50 | - [x] Dynos
51 | - [x] Builds
52 | - [ ] Logs
53 | - Render.com
54 | - [x] Services (+ deploys states)
55 | - [ ] Logs
56 | - Fleek
57 | - [x] Sites
58 | - [ ] Logs
59 |
60 | ---
61 |
62 | 
63 | 
64 |
65 |
66 | ## Installation
67 |
68 | Either download a build from the releases page or clone this repository and run:
69 |
70 | ```sh
71 | go build
72 | ```
73 |
74 | or
75 |
76 | ```sh
77 | go install
78 | ```
79 |
80 | ### OpenBSD
81 |
82 | Planor is available through the `-current` and `-stable` ports on OpenBSD >=
83 | 7.2, as well as via `pkg_add planor` on `-current` and `-stable` > 7.2.
84 | The package is being maintained by [gonzalo-](https://github.com/gonzalo-)
85 | (see [this issue](https://github.com/mrusme/planor/issues/2)).
86 |
87 | For more info on using the anoncvs ports checkout, see
88 | [here](https://www.openbsd.org/anoncvs.html#updating) and
89 | [here](https://www.openbsd.org/faq/ports/guide.html).
90 |
91 |
92 | ## Configuration
93 |
94 | Nothing to configure, enjoy!
95 |
96 |
97 | ## Usage
98 |
99 | Make sure to have the cloud provider profile configured and upon launching
100 | planor press r to refresh the data.
101 |
102 |
103 | ### Amazon Web Services
104 |
105 | Create ~/.aws/config and add your profile, e.g.:
106 |
107 | ```ini
108 | [profile captain-baloo]
109 | region = us-east-1
110 | ```
111 |
112 | Create ~/.aws/credentials and add your profile credentials, e.g.:
113 |
114 | ```ini
115 | [captain-baloo]
116 | aws_access_key_id = AKXXXXXHDXXXGXXPXXHX
117 | aws_secret_access_key = qWX0Xx0XxxDxxx+0XsqXXLX/XXdXsxxMXxXlxKXv
118 | ```
119 |
120 | Then run planor and specify the cloud service and profile name:
121 |
122 | ```sh
123 | planor -c aws -p captain-baloo
124 | ```
125 |
126 | Library: https://github.com/aws/aws-sdk-go-v2
127 |
128 |
129 | ### Vultr
130 |
131 | Run planor and specify the environment variable that holds the Vultr API key as
132 | profile:
133 |
134 | ```sh
135 | export VULTR_API_KEY='...'
136 | planor -c vultr -p VULTR_API_KEY
137 | ```
138 |
139 | Library: https://github.com/vultr/govultr
140 |
141 |
142 | ### Heroku
143 |
144 | Run planor and specify the environment variable that holds the Heroku API key as
145 | profile:
146 |
147 | ```sh
148 | export HEROKU_API_KEY='...'
149 | planor -c heroku -p HEROKU_API_KEY
150 | ```
151 |
152 | Library: https://github.com/heroku/heroku-go
153 |
154 |
155 | ### Render
156 |
157 | Run planor and specify the environment variable that holds the Render API key as
158 | profile:
159 |
160 | ```sh
161 | export RENDER_API_KEY='...'
162 | planor -c render -p RENDER_API_KEY
163 | ```
164 |
165 | Library: https://github.com/mrusme/go-render
166 |
167 |
168 | ### Fleek
169 |
170 | Run planor and specify the environment variable that holds the Fleek API key as
171 | profile, and export the Fleek Team ID as environment variable as well:
172 |
173 | ```sh
174 | export FLEEK_TEAM_ID='my-team'
175 | export RENDER_API_KEY='...'
176 | planor -c render -p RENDER_API_KEY
177 | ```
178 |
179 | Library: https://github.com/mrusme/go-fleek
180 |
181 |
182 |
183 | ## Navigation
184 |
185 | The keyboard navigation:
186 |
187 | ```
188 | r: Refresh
189 | C-p: Previous tab/service
190 | C-n: Next tab/service
191 | F1-F12: Switch to tab
192 | tab: Switch focus
193 | k: Move up in list
194 | j: Move down in list
195 | g: Move to the beginning of list/text
196 | G: Move to the end of list/text
197 | q: Quit
198 | ```
199 |
200 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/mrusme/planor
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/aws/aws-sdk-go-v2 v1.26.1
7 | github.com/aws/aws-sdk-go-v2/config v1.27.10
8 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1
9 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.26.4
10 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1
11 | github.com/charmbracelet/bubbles v0.18.0
12 | github.com/charmbracelet/bubbletea v0.25.0
13 | github.com/charmbracelet/lipgloss v0.10.0
14 | github.com/heroku/heroku-go/v5 v5.5.0
15 | github.com/mrusme/go-fleek v0.1.1
16 | github.com/mrusme/go-render v0.1.0
17 | github.com/vultr/govultr/v2 v2.17.2
18 | golang.org/x/oauth2 v0.18.0
19 | )
20 |
21 | require (
22 | github.com/atotto/clipboard v0.1.4 // indirect
23 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 // indirect
24 | github.com/aws/aws-sdk-go-v2/credentials v1.17.10 // indirect
25 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 // indirect
26 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 // indirect
27 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 // indirect
28 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect
29 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect
30 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 // indirect
31 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 // indirect
32 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 // indirect
33 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 // indirect
34 | github.com/aws/smithy-go v1.20.2 // indirect
35 | github.com/aymanbagabas/go-osc52 v1.2.2 // indirect
36 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
37 | github.com/cenkalti/backoff v2.2.1+incompatible // indirect
38 | github.com/containerd/console v1.0.4 // indirect
39 | github.com/golang/protobuf v1.5.4 // indirect
40 | github.com/google/go-querystring v1.1.0 // indirect
41 | github.com/google/uuid v1.6.0 // indirect
42 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
43 | github.com/hashicorp/go-retryablehttp v0.7.5 // indirect
44 | github.com/hasura/go-graphql-client v0.12.1 // indirect
45 | github.com/jmespath/go-jmespath v0.4.0 // indirect
46 | github.com/klauspost/compress v1.17.7 // indirect
47 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
48 | github.com/mattn/go-isatty v0.0.20 // indirect
49 | github.com/mattn/go-localereader v0.0.1 // indirect
50 | github.com/mattn/go-runewidth v0.0.15 // indirect
51 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
52 | github.com/muesli/cancelreader v0.2.2 // indirect
53 | github.com/muesli/reflow v0.3.0 // indirect
54 | github.com/muesli/termenv v0.15.2 // indirect
55 | github.com/pborman/uuid v1.2.1 // indirect
56 | github.com/rivo/uniseg v0.4.7 // indirect
57 | github.com/sahilm/fuzzy v0.1.1 // indirect
58 | golang.org/x/net v0.22.0 // indirect
59 | golang.org/x/sync v0.6.0 // indirect
60 | golang.org/x/sys v0.18.0 // indirect
61 | golang.org/x/term v0.18.0 // indirect
62 | golang.org/x/text v0.14.0 // indirect
63 | google.golang.org/appengine v1.6.8 // indirect
64 | google.golang.org/protobuf v1.33.0 // indirect
65 | nhooyr.io/websocket v1.8.10 // indirect
66 | )
67 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
2 | github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
3 | github.com/aws/aws-sdk-go-v2 v1.17.3 h1:shN7NlnVzvDUgPQ+1rLMSxY8OWRNDRYtiqe0p/PgrhY=
4 | github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
5 | github.com/aws/aws-sdk-go-v2 v1.18.1 h1:+tefE750oAb7ZQGzla6bLkOwfcQCEtC5y2RqoqCeqKo=
6 | github.com/aws/aws-sdk-go-v2 v1.18.1/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
7 | github.com/aws/aws-sdk-go-v2 v1.19.0 h1:klAT+y3pGFBU/qVf1uzwttpBbiuozJYWzNLHioyDJ+k=
8 | github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
9 | github.com/aws/aws-sdk-go-v2 v1.21.2 h1:+LXZ0sgo8quN9UOKXXzAWRT3FWd4NxeXWOZom9pE7GA=
10 | github.com/aws/aws-sdk-go-v2 v1.21.2/go.mod h1:ErQhvNuEMhJjweavOYhxVkn2RUx7kQXVATHrjKtxIpM=
11 | github.com/aws/aws-sdk-go-v2 v1.26.1 h1:5554eUqIYVWpU0YmeeYZ0wU64H2VLBs8TlhRB2L+EkA=
12 | github.com/aws/aws-sdk-go-v2 v1.26.1/go.mod h1:ffIFB97e2yNsv4aTSGkqtHnppsIJzw7G7BReUZ3jCXM=
13 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2 h1:x6xsQXGSmW6frevwDA+vi/wqhp1ct18mVXYN08/93to=
14 | github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.2/go.mod h1:lPprDr1e6cJdyYeGXnRaJoP4Md+cDBvi2eOj00BlGmg=
15 | github.com/aws/aws-sdk-go-v2/config v1.18.7 h1:V94lTcix6jouwmAsgQMAEBozVAGJMFhVj+6/++xfe3E=
16 | github.com/aws/aws-sdk-go-v2/config v1.18.7/go.mod h1:OZYsyHFL5PB9UpyS78NElgKs11qI/B5KJau2XOJDXHA=
17 | github.com/aws/aws-sdk-go-v2/config v1.18.27 h1:Az9uLwmssTE6OGTpsFqOnaGpLnKDqNYOJzWuC6UAYzA=
18 | github.com/aws/aws-sdk-go-v2/config v1.18.27/go.mod h1:0My+YgmkGxeqjXZb5BYme5pc4drjTnM+x1GJ3zv42Nw=
19 | github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw=
20 | github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A=
21 | github.com/aws/aws-sdk-go-v2/config v1.18.45 h1:Aka9bI7n8ysuwPeFdm77nfbyHCAKQ3z9ghB3S/38zes=
22 | github.com/aws/aws-sdk-go-v2/config v1.18.45/go.mod h1:ZwDUgFnQgsazQTnWfeLWk5GjeqTQTL8lMkoE1UXzxdE=
23 | github.com/aws/aws-sdk-go-v2/config v1.27.10 h1:PS+65jThT0T/snC5WjyfHHyUgG+eBoupSDV+f838cro=
24 | github.com/aws/aws-sdk-go-v2/config v1.27.10/go.mod h1:BePM7Vo4OBpHreKRUMuDXX+/+JWP38FLkzl5m27/Jjs=
25 | github.com/aws/aws-sdk-go-v2/credentials v1.13.7 h1:qUUcNS5Z1092XBFT66IJM7mYkMwgZ8fcC8YDIbEwXck=
26 | github.com/aws/aws-sdk-go-v2/credentials v1.13.7/go.mod h1:AdCcbZXHQCjJh6NaH3pFaw8LUeBFn5+88BZGMVGuBT8=
27 | github.com/aws/aws-sdk-go-v2/credentials v1.13.26 h1:qmU+yhKmOCyujmuPY7tf5MxR/RKyZrOPO3V4DobiTUk=
28 | github.com/aws/aws-sdk-go-v2/credentials v1.13.26/go.mod h1:GoXt2YC8jHUBbA4jr+W3JiemnIbkXOfxSXcisUsZ3os=
29 | github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA=
30 | github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE=
31 | github.com/aws/aws-sdk-go-v2/credentials v1.13.43 h1:LU8vo40zBlo3R7bAvBVy/ku4nxGEyZe9N8MqAeFTzF8=
32 | github.com/aws/aws-sdk-go-v2/credentials v1.13.43/go.mod h1:zWJBz1Yf1ZtX5NGax9ZdNjhhI4rgjfgsyk6vTY1yfVg=
33 | github.com/aws/aws-sdk-go-v2/credentials v1.17.10 h1:qDZ3EA2lv1KangvQB6y258OssCHD0xvaGiEDkG4X/10=
34 | github.com/aws/aws-sdk-go-v2/credentials v1.17.10/go.mod h1:6t3sucOaYDwDssHQa0ojH1RpmVmF5/jArkye1b2FKMI=
35 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU=
36 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
37 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4 h1:LxK/bitrAr4lnh9LnIS6i7zWbCOdMsfzKFBI6LUCS0I=
38 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.4/go.mod h1:E1hLXN/BL2e6YizK1zFlYd8vsfi2GTjbjBazinMmeaM=
39 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU=
40 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc=
41 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13 h1:PIktER+hwIG286DqXyvVENjgLTAwGgoeriLDD5C+YlQ=
42 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.13/go.mod h1:f/Ib/qYjhV2/qdsf79H3QP/eRE4AkVyEf6sk7XfZ1tg=
43 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1 h1:FVJ0r5XTHSmIHJV6KuDmdYhEpvlHpiSd38RQWhut5J4=
44 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.1/go.mod h1:zusuAeqezXzAB24LGuzuekqMAEgWkVYukBec3kr3jUg=
45 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27 h1:I3cakv2Uy1vNmmhRQmFptYDxOvBnwCdNwyw63N0RaRU=
46 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
47 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34 h1:A5UqQEmPaCFpedKouS4v+dHCTUo2sKqhoKO9U5kxyWo=
48 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.34/go.mod h1:wZpTEecJe0Btj3IYnDx/VlUzor9wm3fJHyvLpQF0VwY=
49 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35 h1:hMUCiE3Zi5AHrRNGf5j985u0WyqI6r2NULhUfo0N/No=
50 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M=
51 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43 h1:nFBQlGtkbPzp/NjZLuFxRqmT91rLJkgvsEQs68h962Y=
52 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.43/go.mod h1:auo+PiyLl0n1l8A0e8RIeR8tOzYPfZZH/JNlrJ8igTQ=
53 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5 h1:aw39xVGeRWlWx9EzGVnhOR4yOjQDHPQ6o6NmBlscyQg=
54 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.5/go.mod h1:FSaRudD0dXiMPK2UjknVwwTYyZMRsHv3TtkabsZih5I=
55 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21 h1:5NbbMrIzmUn/TXFqAle6mgrH5m9cOvMLRGL7pnG8tRE=
56 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
57 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28 h1:srIVS45eQuewqz6fKKu6ZGXaq6FuFg5NzgQBAM6g8Y4=
58 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.28/go.mod h1:7VRpKQQedkfIEXb4k52I7swUnZP0wohVajJMRn3vsUw=
59 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29 h1:yOpYx+FTBdpk/g+sBU6Cb1H0U/TLEcYYp66mYqsPpcc=
60 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY=
61 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37 h1:JRVhO25+r3ar2mKGP7E0LDl8K9/G36gjlqca5iQbaqc=
62 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.37/go.mod h1:Qe+2KtKml+FEsQF/DHmDV+xjtche/hwoF75EG4UlHW8=
63 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5 h1:PG1F3OD1szkuQPzDw3CIQsRIrtTlUC3lP84taWzHlq0=
64 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.5/go.mod h1:jU1li6RFryMz+so64PpKtudI+QzbKoIEivqdf6LNpOc=
65 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ=
66 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
67 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35 h1:LWA+3kDM8ly001vJ1X1waCuLJdtTl48gwkPKWy9sosI=
68 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.35/go.mod h1:0Eg1YjxE0Bhn56lx+SHJwCzhW+2JGtizsrx+lCqrfm0=
69 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo=
70 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY=
71 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45 h1:hze8YsjSh8Wl1rYa1CJpRmXP21BvOBuc76YhW0HsuQ4=
72 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.45/go.mod h1:lD5M20o09/LCuQ2mE62Mb/iSdSlCNuj6H5ci7tW7OsE=
73 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU=
74 | github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY=
75 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.17.3 h1:GKDlULxx6rUH67l/CRnG0xZzeMLZVk5gVCkVqNK6bgg=
76 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.17.3/go.mod h1:xHK1ta0bQEa5jL6rahKRJvsibjzDO7NTIs5itzsF4w8=
77 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.21.2 h1:zMnh9plMceN5DVuG55IjzEwAS3kbeG0GTNzmbnqI/C8=
78 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.21.2/go.mod h1:zuZWQM3kYD+ibkP3GBrAMbsfUvHK7p7yOwWh9MKsnYQ=
79 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.22.1 h1:qm8LnOQM9yHwfGI7kY2W3gpd3hKttGuKkWplI7fHGH4=
80 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.22.1/go.mod h1:4tbPbziIVYtGAoIqr939uQmg6G/RAbZtU9j4384r1LI=
81 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.24.2 h1:g2t+hNCOYWICWs0cQLXk86DnXQMXgx1omrAGEpF/d68=
82 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.24.2/go.mod h1:5ngOUsc/7/voqXQ5Mn5T5l9/rWopTMgu7hk+4Fl2AS4=
83 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1 h1:suWu59CRsDNhw2YXPpa6drYEetIUUIMUhkzHmucbCf8=
84 | github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs v1.35.1/go.mod h1:tZiRxrv5yBRgZ9Z4OOOxwscAZRFk5DgYhEcjX1QpvgI=
85 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.13.21 h1:yqd0C0Zrgu8y9CeDW3YUyELa5BH8e8Q5FBERzxRCWGQ=
86 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.13.21/go.mod h1:CZzLW6TnEwqFDs9BcgSVB7lbUe4KGuspzmMHGAwBTow=
87 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.15.2 h1:S1e1mfi5FbfgVn1Yv79YqDSmJX4VFejkGw9oUM+Vwz4=
88 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.15.2/go.mod h1:Is5bXlJ8kPq1ff273cBpSynkCu4e2wmJcuLvurKO1DY=
89 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.15.4 h1:+59ejEQ+Kcm/BZ1365Lu+7W2+YJznPessD6QccEiCxE=
90 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.15.4/go.mod h1:Yg4V+0bw6/1S7oJkFqlvfRMz2Jczz/ASAFBISlBs2xA=
91 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.16.7 h1:WamPYh/bEELPrmvBvcIyq7blbVLrVTn6pHvfe8VsPxE=
92 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.16.7/go.mod h1:AAf6GmNYgPeUYgCdI+lD6Hm7ZYDAaMTiAIg0WClVla4=
93 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.26.4 h1:Fgue0HyqujVncfNA1A03akMLbJp2dCqnTu908OIymkg=
94 | github.com/aws/aws-sdk-go-v2/service/codepipeline v1.26.4/go.mod h1:VLzksRU3/kbT7LOZyfQmyYr2kevwJS1NDNBstTCcKRA=
95 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.77.0 h1:m6HYlpZlTWb9vHuuRHpWRieqPHWlS0mvQ90OJNrG/Nk=
96 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.77.0/go.mod h1:mV0E7631M1eXdB+tlGFIw6JxfsC7Pz7+7Aw15oLVhZw=
97 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.102.0 h1:P4dyjm49F2kKws0FpouBC6fjVImACXKt752+CWa01lM=
98 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.102.0/go.mod h1:tIctCeX9IbzsUTKHt53SVEcgyfxV2ElxJeEB+QUbc4M=
99 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.105.1 h1:wqbUi9viWc1M5ycr75LnFUIOvWgE3EDFvExtGPF6DHI=
100 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.105.1/go.mod h1:/0btVmMZJ0sn9JQ2N96XszlQNeRCJhhXOS/sPZgDeew=
101 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.125.0 h1:XnUTMYuPGOrQjlTcf9XoRYBLrN7xjzakkK0+QyP8fO0=
102 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.125.0/go.mod h1:raUdIDoNuDPn9dMG3cCmIm8RoWOmZUqQPzuw8xpmB8Y=
103 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1 h1:JBwnHlQvL39eeT03+vmBZuziutTKljmOKboKxQuIBck=
104 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.155.1/go.mod h1:xejKuuRDjz6z5OqyeLsz01MlOqqW7CqpAB4PabNvpu8=
105 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 h1:Ji0DY1xUsUr3I8cHps0G+XM3WWU16lP6yG8qu1GAZAs=
106 | github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2/go.mod h1:5CsjAbs3NlGQyZNFACh+zztPDI7fU6eW9QsxjfnuBKg=
107 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM=
108 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k=
109 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28 h1:bkRyG4a929RCnpVSTvLM2j/T4ls015ZhhYApbmYs15s=
110 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.28/go.mod h1:jj7znCIg05jXlaGBlFMGP8+7UN3VtCkRBG2spnmRQkU=
111 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29 h1:IiDolu/eLmuB18DRZibj77n1hHQT7z12jnGO7Ze3pLc=
112 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4=
113 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37 h1:WWZA/I2K4ptBS1kg0kV1JbBtG/umed0vwHRrmcr9z7k=
114 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.37/go.mod h1:vBmDnwWXWxNPFRMmG2m/3MKOe+xEcMDo1tanpaWCcck=
115 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7 h1:ogRAwT1/gxJBcSWDMZlgyFUM962F51A5CRhDLbxLdmo=
116 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.7/go.mod h1:YCsIZhXfRPLFFCl5xxY+1T9RKzOKjCut+28JSX2DnAk=
117 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.28 h1:gItLq3zBYyRDPmqAClgzTH8PBjDQGeyptYGHIwtYYNA=
118 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.28/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
119 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.12 h1:nneMBM2p79PGWBQovYO/6Xnc2ryRMw3InnDJq1FHkSY=
120 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.12/go.mod h1:HuCOxYsF21eKrerARYO6HapNeh9GBNq7fius2AcwodY=
121 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k=
122 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY=
123 | github.com/aws/aws-sdk-go-v2/service/sso v1.15.2 h1:JuPGc7IkOP4AaqcZSIcyqLpFSqBWK32rM9+a1g6u73k=
124 | github.com/aws/aws-sdk-go-v2/service/sso v1.15.2/go.mod h1:gsL4keucRCgW+xA85ALBpRFfdSLH4kHOVSnLMSuBECo=
125 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.4 h1:WzFol5Cd+yDxPAdnzTA5LmpHYSWinhmSj4rQChV0ee8=
126 | github.com/aws/aws-sdk-go-v2/service/sso v1.20.4/go.mod h1:qGzynb/msuZIE8I75DVRCUXw3o3ZyBmUvMwQ2t/BrGM=
127 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11 h1:KCacyVSs/wlcPGx37hcbT3IGYO8P8Jx+TgSDhAXtQMY=
128 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.11/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
129 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12 h1:2qTR7IFk7/0IN/adSFhYu9Xthr0zVFTgBrmPldILn80=
130 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.12/go.mod h1:E4VrHCPzmVB/KFXtqBGKb3c8zpbNBgKe3fisDNLAW5w=
131 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4=
132 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog=
133 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3 h1:HFiiRkf1SdaAmV3/BHOFZ9DjFynPHj8G/UIO1lQS+fk=
134 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.17.3/go.mod h1:a7bHA82fyUXOm+ZSWKU6PIoBxrjSprdLoM8xPYvzYVg=
135 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4 h1:Jux+gDDyi1Lruk+KHF91tK2KCuY61kzoCpvtvJJBtOE=
136 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.4/go.mod h1:mUYPBhaF2lGiukDEjJX2BLRRKTmoUSitGDUgM4tRxak=
137 | github.com/aws/aws-sdk-go-v2/service/sts v1.17.7 h1:9Mtq1KM6nD8/+HStvWcvYnixJ5N85DX+P+OY3kI3W2k=
138 | github.com/aws/aws-sdk-go-v2/service/sts v1.17.7/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
139 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.2 h1:XFJ2Z6sNUUcAz9poj+245DMkrHE4h2j5I9/xD50RHfE=
140 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.2/go.mod h1:dp0yLPsLBOi++WTxzCjA/oZqi6NPIhoR+uF7GeMU9eg=
141 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U=
142 | github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
143 | github.com/aws/aws-sdk-go-v2/service/sts v1.23.2 h1:0BkLfgeDjfZnZ+MhB3ONb01u9pwFYTCZVhlsSSBvlbU=
144 | github.com/aws/aws-sdk-go-v2/service/sts v1.23.2/go.mod h1:Eows6e1uQEsc4ZaHANmsPRzAKcVDrcmjjWiih2+HUUQ=
145 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.6 h1:cwIxeBttqPN3qkaAjcEcsh8NYr8n2HZPkcKgPAi1phU=
146 | github.com/aws/aws-sdk-go-v2/service/sts v1.28.6/go.mod h1:FZf1/nKNEkHdGGJP/cI2MoIMquumuRK6ol3QQJNDxmw=
147 | github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
148 | github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
149 | github.com/aws/smithy-go v1.15.0 h1:PS/durmlzvAFpQHDs4wi4sNNP9ExsqZh6IlfdHXgKK8=
150 | github.com/aws/smithy-go v1.15.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
151 | github.com/aws/smithy-go v1.20.2 h1:tbp628ireGtzcHDDmLT/6ADHidqnwgF57XOXZe6tp4Q=
152 | github.com/aws/smithy-go v1.20.2/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E=
153 | github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
154 | github.com/aymanbagabas/go-osc52 v1.2.1 h1:q2sWUyDcozPLcLabEMd+a+7Ea2DitxZVN9hTxab9L4E=
155 | github.com/aymanbagabas/go-osc52 v1.2.1/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
156 | github.com/aymanbagabas/go-osc52 v1.2.2 h1:NT7wkhEhPTcKnBCdPi9djmyy9L3JOL4+3SsfJyqptCo=
157 | github.com/aymanbagabas/go-osc52 v1.2.2/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
158 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
159 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
160 | github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
161 | github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
162 | github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
163 | github.com/charmbracelet/bubbles v0.14.0 h1:DJfCwnARfWjZLvMglhSQzo76UZ2gucuHPy9jLWX45Og=
164 | github.com/charmbracelet/bubbles v0.14.0/go.mod h1:bbeTiXwPww4M031aGi8UK2HT9RDWoiNibae+1yCMtcc=
165 | github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
166 | github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
167 | github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0=
168 | github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw=
169 | github.com/charmbracelet/bubbletea v0.21.0/go.mod h1:GgmJMec61d08zXsOhqRC/AiOx4K4pmz+VIcRIm1FKr4=
170 | github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck=
171 | github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU=
172 | github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
173 | github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
174 | github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM=
175 | github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg=
176 | github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
177 | github.com/charmbracelet/lipgloss v0.5.0/go.mod h1:EZLha/HbzEt7cYqdFPovlqy5FZPj0xFhg5SaqxScmgs=
178 | github.com/charmbracelet/lipgloss v0.6.0 h1:1StyZB9vBSOyuZxQUcUwGr17JmojPNm87inij9N3wJY=
179 | github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk=
180 | github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
181 | github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
182 | github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg=
183 | github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I=
184 | github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
185 | github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
186 | github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
187 | github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
188 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
189 | github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
190 | github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro=
191 | github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
192 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
193 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
194 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
195 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
196 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
197 | github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
198 | github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
199 | github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
200 | github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
201 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
202 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
203 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
204 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
205 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
206 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
207 | github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
208 | github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
209 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0=
210 | github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
211 | github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8=
212 | github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
213 | github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo=
214 | github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
215 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
216 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
217 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
218 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
219 | github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
220 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
221 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
222 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
223 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
224 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
225 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
226 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
227 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
228 | github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
229 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
230 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
231 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
232 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
233 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
234 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
235 | github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
236 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
237 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
238 | github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
239 | github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
240 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
241 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
242 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
243 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
244 | github.com/graph-gophers/graphql-go v1.4.0 h1:JE9wveRTSXwJyjdRd6bOQ7Ob5bewTUQ58Jv4OiVdpdE=
245 | github.com/graph-gophers/graphql-go v1.4.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
246 | github.com/graph-gophers/graphql-go v1.5.0 h1:fDqblo50TEpD0LY7RXk/LFVYEVqo3+tXMNMPSVXA1yc=
247 | github.com/graph-gophers/graphql-go v1.5.0/go.mod h1:YtmJZDLbF1YYNrlNAuiO5zAStUWc3XZT07iGsVqe1Os=
248 | github.com/graph-gophers/graphql-transport-ws v0.0.2 h1:DbmSkbIGzj8SvHei6n8Mh9eLQin8PtA8xY9eCzjRpvo=
249 | github.com/graph-gophers/graphql-transport-ws v0.0.2/go.mod h1:5BVKvFzOd2BalVIBFfnfmHjpJi/MZ5rOj8G55mXvZ8g=
250 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
251 | github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
252 | github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
253 | github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI=
254 | github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
255 | github.com/hashicorp/go-retryablehttp v0.7.1 h1:sUiuQAnLlbvmExtFQs72iFW/HXeUn8Z1aJLQ4LJJbTQ=
256 | github.com/hashicorp/go-retryablehttp v0.7.1/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
257 | github.com/hashicorp/go-retryablehttp v0.7.4 h1:ZQgVdpTdAL7WpMIwLzCfbalOcSUdkDZnpUv3/+BxzFA=
258 | github.com/hashicorp/go-retryablehttp v0.7.4/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
259 | github.com/hashicorp/go-retryablehttp v0.7.5 h1:bJj+Pj19UZMIweq/iie+1u5YCdGrnxCT9yvm0e+Nd5M=
260 | github.com/hashicorp/go-retryablehttp v0.7.5/go.mod h1:Jy/gPYAdjqffZ/yFGCFV2doI5wjtH1ewM9u8iYVjtX8=
261 | github.com/hasura/go-graphql-client v0.8.1 h1:yU4888urgkW4L47cs+QQDXl3YfVaNraUqym5qsJ41Ms=
262 | github.com/hasura/go-graphql-client v0.8.1/go.mod h1:NVifIwv+YFIUYGLQ7SM2/vBbzS/9rFP4vmIf/vf/zXM=
263 | github.com/hasura/go-graphql-client v0.9.3 h1:Xi3fqa2t9q4nJ2jM2AU8nB6qeAoMpbcYDiOSBnNAN1E=
264 | github.com/hasura/go-graphql-client v0.9.3/go.mod h1:AarJlxO1I59MPqU/TC7gQP0BMFgPEqUTt5LYPvykasw=
265 | github.com/hasura/go-graphql-client v0.10.0 h1:eQm/ap/rqxMG6yAGe6J+FkXu1VqJ9p21E63vz0A7zLQ=
266 | github.com/hasura/go-graphql-client v0.10.0/go.mod h1:z9UPkMmCBMuJjvBEtdE6F+oTR2r15AcjirVNq/8P+Ig=
267 | github.com/hasura/go-graphql-client v0.12.1 h1:tL+BCoyubkYYyaQ+tJz+oPe/pSxYwOJHwe5SSqqi6WI=
268 | github.com/hasura/go-graphql-client v0.12.1/go.mod h1:F4N4kR6vY8amio3gEu3tjSZr8GPOXJr3zj72DKixfLE=
269 | github.com/heroku/heroku-go/v5 v5.5.0 h1:+pKHpiPskqkkarrPHF7RpeUveXl+mAsKLAEI/ZIY9uA=
270 | github.com/heroku/heroku-go/v5 v5.5.0/go.mod h1:Uo3XhGPwaTpniR4X1e50BDjg4SzdFk2Bd2mgYZVkfHo=
271 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
272 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
273 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
274 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
275 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
276 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
277 | github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
278 | github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0=
279 | github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
280 | github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
281 | github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
282 | github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
283 | github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
284 | github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
285 | github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
286 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
287 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
288 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
289 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
290 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
291 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
292 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
293 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
294 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
295 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
296 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
297 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
298 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
299 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
300 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
301 | github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
302 | github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
303 | github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
304 | github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
305 | github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
306 | github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
307 | github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
308 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
309 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
310 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
311 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
312 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
313 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
314 | github.com/mrusme/go-fleek v0.1.1 h1:zX7kAERt/Z05DlyoT0OwMs77JXgXFTs5fXDkfJR0ttw=
315 | github.com/mrusme/go-fleek v0.1.1/go.mod h1:JH8/b5fM1X7WY8dkCcl2sSMGIEua4uHccsLAqtSCJG4=
316 | github.com/mrusme/go-render v0.1.0 h1:6J13rCJdOewqeFKOzy9pp2gbanKHhkvijUa4oUZBgbc=
317 | github.com/mrusme/go-render v0.1.0/go.mod h1:v8zUoMwWVC0pLhjsb17G8SlHVl/ZsJvP6kG1trj6DE0=
318 | github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
319 | github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a h1:jlDOeO5TU0pYlbc/y6PFguab5IjANI0Knrpg3u/ton4=
320 | github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
321 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
322 | github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
323 | github.com/muesli/cancelreader v0.2.0/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
324 | github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
325 | github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
326 | github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ=
327 | github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
328 | github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
329 | github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
330 | github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
331 | github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
332 | github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
333 | github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
334 | github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
335 | github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
336 | github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
337 | github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw=
338 | github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
339 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
340 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
341 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
342 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
343 | github.com/rivo/uniseg v0.4.3 h1:utMvzDsuh3suAEnhH0RdHmoPbU648o6CvXxTx4SBMOw=
344 | github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
345 | github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
346 | github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
347 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
348 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
349 | github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
350 | github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
351 | github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
352 | github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
353 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
354 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
355 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
356 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
357 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
358 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
359 | github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
360 | github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
361 | github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
362 | github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
363 | github.com/vultr/govultr/v2 v2.17.2 h1:gej/rwr91Puc/tgh+j33p/BLR16UrIPnSr+AIwYWZQs=
364 | github.com/vultr/govultr/v2 v2.17.2/go.mod h1:ZFOKGWmgjytfyjeyAdhQlSWwTjh2ig+X49cAp50dzXI=
365 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
366 | go.opentelemetry.io/otel v1.6.3/go.mod h1:7BgNga5fNlF/iZjG06hM3yofffp0ofKCDwSXx1GC4dI=
367 | go.opentelemetry.io/otel/trace v1.6.3/go.mod h1:GNJQusJlUgZl9/TQBPKU/Y/ty+0iVB5fjhKeJGZPGFs=
368 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
369 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
370 | golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
371 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
372 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
373 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
374 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
375 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
376 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
377 | golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
378 | golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
379 | golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU=
380 | golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ=
381 | golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
382 | golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
383 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
384 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
385 | golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
386 | golang.org/x/oauth2 v0.3.0 h1:6l90koy8/LaBLmLu8jpHeHexzMwEita0zFfYlggy2F8=
387 | golang.org/x/oauth2 v0.3.0/go.mod h1:rQrIauxkUhJ6CuwEXwymO2/eh4xz2ZWF1nBkcxS+tGk=
388 | golang.org/x/oauth2 v0.9.0 h1:BPpt2kU7oMRq3kCHAA1tbSEshXRw1LpG2ztgDwrzuAs=
389 | golang.org/x/oauth2 v0.9.0/go.mod h1:qYgFZaFiu6Wg24azG8bdV52QJXJGbZzIIsRCdVKzbLw=
390 | golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
391 | golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
392 | golang.org/x/oauth2 v0.13.0 h1:jDDenyj+WgFtmV3zYVoi8aE2BwtXFLWOA67ZfNWftiY=
393 | golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn0=
394 | golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
395 | golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
396 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
397 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
398 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
399 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
400 | golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
401 | golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
402 | golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
403 | golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
404 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
405 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
406 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
407 | golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
408 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
409 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
410 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
411 | golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
412 | golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
413 | golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
414 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
415 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
416 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
417 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
418 | golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
419 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
420 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
421 | golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
422 | golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
423 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
424 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
425 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
426 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
427 | golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
428 | golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
429 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
430 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
431 | golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
432 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
433 | golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
434 | golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
435 | golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
436 | golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
437 | golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
438 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
439 | golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
440 | golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
441 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
442 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
443 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
444 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
445 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
446 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
447 | golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
448 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
449 | golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
450 | golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
451 | golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
452 | golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
453 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
454 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
455 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
456 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
457 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
458 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
459 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
460 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
461 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
462 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
463 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
464 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
465 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
466 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
467 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
468 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
469 | google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
470 | google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
471 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
472 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
473 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
474 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
475 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
476 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
477 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
478 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
479 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
480 | nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g=
481 | nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
482 | nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q=
483 | nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
484 |
--------------------------------------------------------------------------------
/nori/adapter/adapter.go:
--------------------------------------------------------------------------------
1 | package adapter
2 |
3 | type Capability struct {
4 | ID string
5 | Name string
6 | }
7 |
--------------------------------------------------------------------------------
/nori/amazon/aws.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/mrusme/planor/nori/adapter"
7 |
8 | "github.com/aws/aws-sdk-go-v2/aws"
9 | "github.com/aws/aws-sdk-go-v2/config"
10 | "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
11 | "github.com/aws/aws-sdk-go-v2/service/codepipeline"
12 | "github.com/aws/aws-sdk-go-v2/service/ec2"
13 | )
14 |
15 | type Amazon struct {
16 | cfg aws.Config
17 | ec2 *ec2.Client
18 | cpc *codepipeline.Client
19 | cwl *cloudwatchlogs.Client
20 | }
21 |
22 | func (cloud *Amazon) LoadProfile(profile *string) error {
23 | var err error
24 |
25 | cloud.cfg, err = config.LoadDefaultConfig(context.Background(), config.WithSharedConfigProfile(*profile))
26 | if err != nil {
27 | return err
28 | }
29 |
30 | return nil
31 | }
32 |
33 | func (cloud *Amazon) LoadClients() error {
34 | cloud.ec2 = ec2.NewFromConfig(cloud.cfg)
35 | cloud.cpc = codepipeline.NewFromConfig(cloud.cfg)
36 | cloud.cwl = cloudwatchlogs.NewFromConfig(cloud.cfg)
37 | return nil
38 | }
39 |
40 | func (cloud *Amazon) GetCapabilities() []adapter.Capability {
41 | var caps []adapter.Capability
42 |
43 | caps = append(caps, adapter.Capability{
44 | ID: "instances",
45 | Name: "Elastic Cloud Compute",
46 | })
47 | caps = append(caps, adapter.Capability{
48 | ID: "ci",
49 | Name: "CodePipeline",
50 | })
51 | caps = append(caps, adapter.Capability{
52 | ID: "logs",
53 | Name: "CloudWatch Logs",
54 | })
55 |
56 | return caps
57 | }
58 |
--------------------------------------------------------------------------------
/nori/amazon/ci.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 |
8 | "github.com/aws/aws-sdk-go-v2/aws"
9 | "github.com/aws/aws-sdk-go-v2/service/codepipeline"
10 | )
11 |
12 | func (cloud *Amazon) ListPipelines() ([]models.Pipeline, error) {
13 | input := codepipeline.ListPipelinesInput{
14 | MaxResults: aws.Int32(1000),
15 | NextToken: nil,
16 | }
17 |
18 | var pipelines []models.Pipeline
19 |
20 | for true {
21 | ret, err := cloud.cpc.ListPipelines(context.Background(), &input)
22 | if err != nil {
23 | return nil, err
24 | }
25 |
26 | for _, pipeline := range ret.Pipelines {
27 | newPipeline := models.Pipeline{
28 | ID: *pipeline.Name,
29 | Name: *pipeline.Name,
30 | Version: string(*pipeline.Version),
31 | CreatedAt: *pipeline.Created,
32 | UpdatedAt: *pipeline.Updated,
33 | }
34 |
35 | cloud.UpdatePipelineStatus(&newPipeline)
36 |
37 | pipelines = append(pipelines, newPipeline)
38 | }
39 |
40 | input.NextToken = ret.NextToken
41 | if input.NextToken == nil {
42 | break
43 | }
44 | }
45 |
46 | return pipelines, nil
47 | }
48 |
49 | func (cloud *Amazon) UpdatePipelineStatus(pipeline *models.Pipeline) error {
50 | stateRet, err := cloud.cpc.GetPipelineState(
51 | context.Background(),
52 | &codepipeline.GetPipelineStateInput{
53 | Name: &pipeline.Name,
54 | },
55 | )
56 |
57 | var state = stateRet
58 | if err != nil {
59 | pipeline.Error = err
60 | return err
61 | }
62 |
63 | var stages []models.PipelineStage
64 | for _, stage := range state.StageStates {
65 | var actions []models.PipelineStageAction
66 | for _, action := range stage.ActionStates {
67 | newAction := models.PipelineStageAction{
68 | Name: *action.ActionName,
69 | Status: string((*action.LatestExecution).Status),
70 | UpdatedAt: *action.LatestExecution.LastStatusChange,
71 | }
72 |
73 | if action.LatestExecution.Summary != nil {
74 | newAction.Summary = *action.LatestExecution.Summary
75 | }
76 |
77 | if action.LatestExecution.PercentComplete != nil {
78 | newAction.PercentComplete = *action.LatestExecution.PercentComplete
79 | }
80 |
81 | actions = append(actions, newAction)
82 | }
83 |
84 | newStage := models.PipelineStage{
85 | ID: *stage.StageName,
86 | Name: *stage.StageName,
87 | Status: string((*stage.LatestExecution).Status),
88 | Actions: actions,
89 | }
90 |
91 | stages = append(stages, newStage)
92 | }
93 |
94 | pipeline.Version = string(*state.PipelineVersion)
95 | pipeline.Stages = stages
96 |
97 | return nil
98 | }
99 |
--------------------------------------------------------------------------------
/nori/amazon/instances.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 |
8 | "github.com/aws/aws-sdk-go-v2/aws"
9 | "github.com/aws/aws-sdk-go-v2/service/ec2"
10 | // "github.com/aws/aws-sdk-go-v2/service/ec2/types"
11 | )
12 |
13 | func (cloud *Amazon) ListInstances() ([]models.Instance, error) {
14 | input := ec2.DescribeInstancesInput{
15 | MaxResults: aws.Int32(1000),
16 | NextToken: nil,
17 | }
18 |
19 | var instances []models.Instance
20 |
21 | for true {
22 | ret, err := cloud.ec2.DescribeInstances(context.Background(), &input)
23 | if err != nil {
24 | return nil, err
25 | }
26 |
27 | for _, reservation := range ret.Reservations {
28 | for _, instance := range reservation.Instances {
29 | newInstance := models.Instance{
30 | ID: *instance.InstanceId,
31 | Name: *instance.InstanceId,
32 |
33 | Type: string(instance.InstanceType),
34 | Architecture: string(instance.Architecture),
35 | CPUCores: int(*instance.CpuOptions.CoreCount),
36 | CPUThreads: int(*instance.CpuOptions.ThreadsPerCore),
37 |
38 | Image: *instance.ImageId,
39 | Status: string(instance.State.Name),
40 | }
41 |
42 | if instance.PublicIpAddress != nil {
43 | newInstance.IPv4 = *instance.PublicIpAddress
44 | }
45 |
46 | if instance.Ipv6Address != nil {
47 | newInstance.IPv6 = *instance.Ipv6Address
48 | }
49 |
50 | instances = append(instances, newInstance)
51 | }
52 | }
53 |
54 | input.NextToken = ret.NextToken
55 | if input.NextToken == nil {
56 | break
57 | }
58 | }
59 |
60 | return instances, nil
61 | }
62 |
--------------------------------------------------------------------------------
/nori/amazon/logs.go:
--------------------------------------------------------------------------------
1 | package amazon
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/mrusme/planor/nori/models"
8 |
9 | "github.com/aws/aws-sdk-go-v2/aws"
10 | "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs"
11 | "github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs/types"
12 | )
13 |
14 | func (cloud *Amazon) ListLogGroups(updateStreams bool, updateEvents bool) ([]models.LogGroup, error) {
15 | input := cloudwatchlogs.DescribeLogGroupsInput{
16 | Limit: aws.Int32(50),
17 | NextToken: nil,
18 | }
19 |
20 | var logGroups []models.LogGroup
21 |
22 | for true {
23 | ret, err := cloud.cwl.DescribeLogGroups(context.Background(), &input)
24 | if err != nil {
25 | return nil, err
26 | }
27 |
28 | for _, logGroup := range ret.LogGroups {
29 | newLogGroup := models.LogGroup{
30 | ID: *logGroup.Arn,
31 | Name: *logGroup.LogGroupName,
32 | SizeBytes: *logGroup.StoredBytes,
33 | }
34 |
35 | if updateStreams == true {
36 | err := cloud.UpdateLogStreams(&newLogGroup, updateEvents)
37 | if err != nil {
38 | return nil, err
39 | }
40 | }
41 |
42 | logGroups = append(logGroups, newLogGroup)
43 | }
44 |
45 | input.NextToken = ret.NextToken
46 | if input.NextToken == nil {
47 | break
48 | }
49 | }
50 |
51 | return logGroups, nil
52 | }
53 |
54 | func (cloud *Amazon) UpdateLogStreams(logGroup *models.LogGroup, updateEvents bool) error {
55 | input := cloudwatchlogs.DescribeLogStreamsInput{
56 | LogGroupName: aws.String(logGroup.Name),
57 | Limit: aws.Int32(2),
58 | NextToken: nil,
59 | Descending: aws.Bool(true),
60 | // OrderBy: https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/cloudwatchlogs@v1.15.1/types#OrderBy
61 | OrderBy: types.OrderByLastEventTime,
62 | }
63 |
64 | for true {
65 | ret, err := cloud.cwl.DescribeLogStreams(context.Background(), &input)
66 | if err != nil {
67 | return err
68 | }
69 |
70 | for _, logStream := range ret.LogStreams {
71 | newLogStream := models.LogStream{
72 | ID: *logStream.Arn,
73 | Name: *logStream.LogStreamName,
74 | GroupName: logGroup.Name,
75 | CreatedAt: time.UnixMilli(*logStream.CreationTime),
76 | FirstEventAt: time.UnixMilli(*logStream.FirstEventTimestamp),
77 | LastEventAt: time.UnixMilli(*logStream.LastEventTimestamp),
78 | LastIngestedAt: time.UnixMilli(*logStream.LastIngestionTime),
79 | }
80 |
81 | if updateEvents == true {
82 | err := cloud.UpdateLogEvents(&newLogStream)
83 | if err != nil {
84 | return err
85 | }
86 | }
87 |
88 | logGroup.Streams = append(logGroup.Streams, newLogStream)
89 | }
90 |
91 | // input.NextToken = ret.NextToken
92 | // if input.NextToken == nil {
93 | // break
94 | // }
95 | break
96 | }
97 |
98 | return nil
99 | }
100 |
101 | func (cloud *Amazon) UpdateLogEvents(logStream *models.LogStream) error {
102 | input := cloudwatchlogs.GetLogEventsInput{
103 | LogGroupName: aws.String(logStream.GroupName),
104 | LogStreamName: aws.String(logStream.Name),
105 | Limit: aws.Int32(100),
106 | NextToken: nil,
107 | StartFromHead: aws.Bool(false),
108 | }
109 |
110 | for true {
111 | ret, err := cloud.cwl.GetLogEvents(context.Background(), &input)
112 | if err != nil {
113 | return err
114 | }
115 |
116 | for _, logEvent := range ret.Events {
117 | newLogEvent := models.LogEvent{
118 | Message: *logEvent.Message,
119 | Timestamp: time.UnixMilli(*logEvent.Timestamp),
120 | IngestedAt: time.UnixMilli(*logEvent.IngestionTime),
121 | }
122 |
123 | logStream.LogEvents = append(logStream.LogEvents, newLogEvent)
124 | }
125 |
126 | // input.NextToken = ret.NextForwardToken
127 | // if input.NextToken == nil {
128 | // break
129 | // }
130 | break
131 | }
132 |
133 | return nil
134 | }
135 |
--------------------------------------------------------------------------------
/nori/fleek/ci.go:
--------------------------------------------------------------------------------
1 | package fleek
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Fleek) ListPipelines() ([]models.Pipeline, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
--------------------------------------------------------------------------------
/nori/fleek/fleek.go:
--------------------------------------------------------------------------------
1 | package fleek
2 |
3 | import (
4 | // "context"
5 | "os"
6 |
7 | "github.com/mrusme/planor/nori/adapter"
8 |
9 | gofleek "github.com/mrusme/go-fleek"
10 | )
11 |
12 | type Fleek struct {
13 | apiKey string
14 | teamId string
15 | fleek *gofleek.FleekClient
16 | }
17 |
18 | func (cloud *Fleek) LoadProfile(profile *string) error {
19 | cloud.apiKey = os.Getenv(*profile)
20 | cloud.teamId = os.Getenv("FLEEK_TEAM_ID")
21 |
22 | return nil
23 | }
24 |
25 | func (cloud *Fleek) LoadClients() error {
26 | var err error
27 | cloud.fleek, err = gofleek.New(cloud.apiKey)
28 |
29 | return err
30 | }
31 |
32 | func (cloud *Fleek) GetCapabilities() []adapter.Capability {
33 | var caps []adapter.Capability
34 |
35 | caps = append(caps, adapter.Capability{
36 | ID: "instances",
37 | Name: "Sites",
38 | })
39 |
40 | return caps
41 | }
42 |
--------------------------------------------------------------------------------
/nori/fleek/instances.go:
--------------------------------------------------------------------------------
1 | package fleek
2 |
3 | import (
4 | "github.com/mrusme/planor/nori/models"
5 | // gofleek "github.com/mrusme/go-fleek"
6 | )
7 |
8 | func (cloud *Fleek) ListInstances() ([]models.Instance, error) {
9 | var instances []models.Instance
10 | ret, err := cloud.fleek.GetSitesByTeamId(cloud.teamId)
11 | if err != nil {
12 | return nil, err
13 | }
14 |
15 | for _, instance := range ret {
16 | newInstance := models.Instance{
17 | ID: instance.Slug,
18 | Name: instance.Name,
19 |
20 | Type: instance.Platform,
21 |
22 | Status: instance.PublishedDeploy.Status,
23 | }
24 |
25 | instances = append(instances, newInstance)
26 | }
27 |
28 | return instances, nil
29 | }
30 |
--------------------------------------------------------------------------------
/nori/fleek/logs.go:
--------------------------------------------------------------------------------
1 | package fleek
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Fleek) ListLogGroups(updateStreams bool, updateEvents bool) ([]models.LogGroup, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
13 | func (cloud *Fleek) UpdateLogStreams(logGroup *models.LogGroup, updateEvents bool) error {
14 | return errors.New("Unsupported")
15 | }
16 |
17 | func (cloud *Fleek) UpdateLogEvents(logStream *models.LogStream) error {
18 | return errors.New("Unsupported")
19 | }
20 |
--------------------------------------------------------------------------------
/nori/heroku/apps.go:
--------------------------------------------------------------------------------
1 | package heroku
2 |
3 | import (
4 | "context"
5 | // "errors"
6 |
7 | "github.com/mrusme/planor/nori/models"
8 |
9 | herokugo "github.com/heroku/heroku-go/v5"
10 | )
11 |
12 | func (cloud *Heroku) ListApps() ([]models.App, error) {
13 | input := herokugo.ListRange{
14 | Field: "id",
15 | Max: 100,
16 | Descending: false,
17 | }
18 |
19 | ret, err := cloud.heroku.AppList(context.Background(), &input)
20 | if err != nil {
21 | return nil, err
22 | }
23 |
24 | var apps []models.App
25 | for _, app := range ret {
26 | newApp := models.App{
27 | ID: app.ID,
28 | Name: app.Name,
29 | }
30 |
31 | apps = append(apps, newApp)
32 | }
33 |
34 | return apps, nil
35 | }
36 |
--------------------------------------------------------------------------------
/nori/heroku/ci.go:
--------------------------------------------------------------------------------
1 | package heroku
2 |
3 | import (
4 | "context"
5 | // "errors"
6 |
7 | "github.com/mrusme/planor/nori/models"
8 |
9 | herokugo "github.com/heroku/heroku-go/v5"
10 | )
11 |
12 | func (cloud *Heroku) ListPipelines() ([]models.Pipeline, error) {
13 | apps, err := cloud.ListApps()
14 | if err != nil {
15 | return nil, err
16 | }
17 |
18 | var pipelines []models.Pipeline
19 | input := herokugo.ListRange{
20 | Field: "id",
21 | Max: 1,
22 | Descending: true,
23 | }
24 | for _, app := range apps {
25 | ret, err := cloud.heroku.BuildList(context.Background(), app.ID, &input)
26 | if err != nil {
27 | return pipelines, err
28 | }
29 |
30 | for _, build := range ret {
31 | newPipelineStage := models.PipelineStage{
32 | ID: build.ID,
33 | Name: build.Release.ID,
34 | Status: build.Status,
35 | }
36 |
37 | var stages []models.PipelineStage
38 | stages = append(stages, newPipelineStage)
39 | newPipeline := models.Pipeline{
40 | ID: app.ID,
41 | Name: app.Name,
42 | Stages: stages,
43 | }
44 |
45 | pipelines = append(pipelines, newPipeline)
46 | }
47 | }
48 |
49 | return pipelines, nil
50 | }
51 |
--------------------------------------------------------------------------------
/nori/heroku/heroku.go:
--------------------------------------------------------------------------------
1 | package heroku
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/mrusme/planor/nori/adapter"
7 |
8 | herokugo "github.com/heroku/heroku-go/v5"
9 | )
10 |
11 | type Heroku struct {
12 | apiKey string
13 | heroku *herokugo.Service
14 | }
15 |
16 | func (cloud *Heroku) LoadProfile(profile *string) error {
17 | cloud.apiKey = os.Getenv(*profile)
18 |
19 | return nil
20 | }
21 |
22 | func (cloud *Heroku) LoadClients() error {
23 | herokugo.DefaultTransport.BearerToken = cloud.apiKey
24 |
25 | cloud.heroku = herokugo.NewService(herokugo.DefaultClient)
26 |
27 | return nil
28 | }
29 |
30 | func (cloud *Heroku) GetCapabilities() []adapter.Capability {
31 | var caps []adapter.Capability
32 |
33 | caps = append(caps, adapter.Capability{
34 | ID: "instances",
35 | Name: "Dynos",
36 | })
37 | caps = append(caps, adapter.Capability{
38 | ID: "ci",
39 | Name: "Builds",
40 | })
41 |
42 | return caps
43 | }
44 |
--------------------------------------------------------------------------------
/nori/heroku/instances.go:
--------------------------------------------------------------------------------
1 | package heroku
2 |
3 | import (
4 | "context"
5 | // "errors"
6 |
7 | "github.com/mrusme/planor/nori/models"
8 |
9 | herokugo "github.com/heroku/heroku-go/v5"
10 | )
11 |
12 | func (cloud *Heroku) ListInstances() ([]models.Instance, error) {
13 | apps, err := cloud.ListApps()
14 | if err != nil {
15 | return nil, err
16 | }
17 |
18 | var dynos []models.Instance
19 | input := herokugo.ListRange{
20 | Field: "id",
21 | Max: 100,
22 | Descending: false,
23 | }
24 | for _, app := range apps {
25 | ret, err := cloud.heroku.DynoList(context.Background(), app.ID, &input)
26 | if err != nil {
27 | return dynos, err
28 | }
29 |
30 | for _, dyno := range ret {
31 | newDyno := models.Instance{
32 | ID: dyno.ID,
33 | Name: dyno.Name,
34 |
35 | Type: dyno.Size,
36 | Image: dyno.Command,
37 |
38 | Status: dyno.State,
39 | }
40 |
41 | dynos = append(dynos, newDyno)
42 | }
43 | }
44 |
45 | return dynos, nil
46 | }
47 |
--------------------------------------------------------------------------------
/nori/heroku/logs.go:
--------------------------------------------------------------------------------
1 | package heroku
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Heroku) ListLogGroups(updateStreams bool, updateEvents bool) ([]models.LogGroup, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
13 | func (cloud *Heroku) UpdateLogStreams(logGroup *models.LogGroup, updateEvents bool) error {
14 | return errors.New("Unsupported")
15 | }
16 |
17 | func (cloud *Heroku) UpdateLogEvents(logStream *models.LogStream) error {
18 | return errors.New("Unsupported")
19 | }
20 |
--------------------------------------------------------------------------------
/nori/models/apps.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // "fmt"
4 |
5 | type App struct {
6 | ID string
7 | Name string
8 | }
9 |
10 | func (app App) FilterValue() string {
11 | return app.Name
12 | }
13 |
14 | func (app App) Title() string {
15 | return app.Name
16 | }
17 |
18 | func (app App) Description() string {
19 | return app.ID
20 | }
21 |
--------------------------------------------------------------------------------
/nori/models/instances.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | // "fmt"
4 |
5 | type Instance struct {
6 | ID string
7 | Name string
8 |
9 | Region string
10 |
11 | Type string
12 | Architecture string
13 | CPUCores int
14 | CPUThreads int
15 | Hypervisor string
16 |
17 | RAMSize int // MB
18 | DiskSize int // GB
19 |
20 | OS string
21 | Image string
22 |
23 | InternalIPv4 string
24 |
25 | IPv4 string
26 | NetmaskV4 string
27 | GatewayV4 string
28 |
29 | IPv6 string
30 | NetworkV6 string
31 | NetsizeV6 int
32 |
33 | TransferLimit int // GB
34 |
35 | Info string
36 | Status string
37 | VNC string
38 | }
39 |
40 | func (instance Instance) FilterValue() string {
41 | return instance.Name
42 | }
43 |
44 | func (instance Instance) Title() string {
45 | return instance.Name
46 | }
47 |
48 | func (instance Instance) Description() string {
49 | return instance.ID
50 | }
51 |
--------------------------------------------------------------------------------
/nori/models/log.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "fmt"
5 | "time"
6 | )
7 |
8 | type LogEvent struct {
9 | Message string
10 | Timestamp time.Time
11 | IngestedAt time.Time
12 | }
13 |
14 | type LogStream struct {
15 | ID string
16 | Name string
17 | GroupName string
18 | CreatedAt time.Time
19 | FirstEventAt time.Time
20 | LastEventAt time.Time
21 | LastIngestedAt time.Time
22 | LogEvents []LogEvent
23 | }
24 |
25 | func (logStream LogStream) FilterValue() string {
26 | return logStream.Name
27 | }
28 |
29 | func (logStream LogStream) Title() string {
30 | return logStream.Name
31 | }
32 |
33 | func (logStream LogStream) Description() string {
34 | return logStream.LastEventAt.String()
35 | }
36 |
37 | type LogGroup struct {
38 | ID string
39 | Name string
40 | Streams []LogStream
41 | SizeBytes int64
42 | }
43 |
44 | func (logGroup LogGroup) FilterValue() string {
45 | return logGroup.Name
46 | }
47 |
48 | func (logGroup LogGroup) Title() string {
49 | return logGroup.Name
50 | }
51 |
52 | func (logGroup LogGroup) Description() string {
53 | return fmt.Sprintf("%d KB", logGroup.SizeBytes/1024)
54 | }
55 |
--------------------------------------------------------------------------------
/nori/models/pipeline.go:
--------------------------------------------------------------------------------
1 | package models
2 |
3 | import (
4 | "time"
5 | )
6 |
7 | type PipelineStageAction struct {
8 | Name string
9 | Status string
10 | Summary string
11 | PercentComplete int32
12 | UpdatedAt time.Time
13 | }
14 |
15 | type PipelineStage struct {
16 | ID string
17 | Name string
18 | Status string
19 | Actions []PipelineStageAction
20 | }
21 |
22 | type Pipeline struct {
23 | ID string
24 | Name string
25 | Version string
26 | Stages []PipelineStage
27 | Error error
28 | CreatedAt time.Time
29 | UpdatedAt time.Time
30 | }
31 |
32 | func (pipeline Pipeline) FilterValue() string {
33 | return pipeline.Name
34 | }
35 |
36 | func (pipeline Pipeline) Title() string {
37 | return pipeline.Name
38 | }
39 |
40 | func (pipeline Pipeline) Description() string {
41 | if len(pipeline.Stages) > 0 {
42 | return pipeline.Stages[0].Status
43 | }
44 |
45 | return "N/A"
46 | }
47 |
--------------------------------------------------------------------------------
/nori/nor.go:
--------------------------------------------------------------------------------
1 | package nori
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/adapter"
7 | "github.com/mrusme/planor/nori/models"
8 |
9 | "github.com/mrusme/planor/nori/amazon"
10 | "github.com/mrusme/planor/nori/fleek"
11 | "github.com/mrusme/planor/nori/heroku"
12 | "github.com/mrusme/planor/nori/render"
13 | "github.com/mrusme/planor/nori/vultr"
14 | )
15 |
16 | type Nor interface {
17 | GetCapabilities() []adapter.Capability
18 |
19 | LoadProfile(profile *string) error
20 | LoadClients() error
21 |
22 | ListInstances() ([]models.Instance, error)
23 |
24 | ListPipelines() ([]models.Pipeline, error)
25 |
26 | ListLogGroups(updateStreams bool, updateEvents bool) ([]models.LogGroup, error)
27 | UpdateLogStreams(logGroup *models.LogGroup, updateEvents bool) error
28 | UpdateLogEvents(logStream *models.LogStream) error
29 | }
30 |
31 | func New(cloudType *string, profile *string) (Nor, error) {
32 | var cloud Nor
33 |
34 | switch *cloudType {
35 | case "aws":
36 | cloud = new(amazon.Amazon)
37 | case "vultr":
38 | cloud = new(vultr.Vultr)
39 | case "heroku":
40 | cloud = new(heroku.Heroku)
41 | case "render":
42 | cloud = new(render.Render)
43 | case "fleek":
44 | cloud = new(fleek.Fleek)
45 | default:
46 | return nil, errors.New("No such cloud")
47 | }
48 |
49 | err := cloud.LoadProfile(profile)
50 | if err != nil {
51 | return nil, err
52 | }
53 |
54 | err = cloud.LoadClients()
55 | if err != nil {
56 | return nil, err
57 | }
58 |
59 | return cloud, nil
60 | }
61 |
--------------------------------------------------------------------------------
/nori/render/ci.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Render) ListPipelines() ([]models.Pipeline, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
--------------------------------------------------------------------------------
/nori/render/instances.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "github.com/mrusme/planor/nori/models"
5 | // gorender "github.com/mrusme/go-render"
6 | )
7 |
8 | func (cloud *Render) ListInstances() ([]models.Instance, error) {
9 | var instances []models.Instance
10 | ret, err := cloud.render.ListServices()
11 | if err != nil {
12 | return nil, err
13 | }
14 |
15 | for _, instance := range ret {
16 | newInstance := models.Instance{
17 | ID: instance.ID,
18 | Name: instance.Name,
19 |
20 | Type: instance.Type,
21 |
22 | Status: instance.Deploys[0].Status,
23 | }
24 |
25 | instances = append(instances, newInstance)
26 | }
27 |
28 | return instances, nil
29 | }
30 |
--------------------------------------------------------------------------------
/nori/render/logs.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Render) ListLogGroups(updateStreams bool, updateEvents bool) ([]models.LogGroup, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
13 | func (cloud *Render) UpdateLogStreams(logGroup *models.LogGroup, updateEvents bool) error {
14 | return errors.New("Unsupported")
15 | }
16 |
17 | func (cloud *Render) UpdateLogEvents(logStream *models.LogStream) error {
18 | return errors.New("Unsupported")
19 | }
20 |
--------------------------------------------------------------------------------
/nori/render/render.go:
--------------------------------------------------------------------------------
1 | package render
2 |
3 | import (
4 | // "context"
5 | "os"
6 |
7 | "github.com/mrusme/planor/nori/adapter"
8 |
9 | gorender "github.com/mrusme/go-render"
10 | )
11 |
12 | type Render struct {
13 | apiKey string
14 | render *gorender.RenderClient
15 | }
16 |
17 | func (cloud *Render) LoadProfile(profile *string) error {
18 | cloud.apiKey = os.Getenv(*profile)
19 |
20 | return nil
21 | }
22 |
23 | func (cloud *Render) LoadClients() error {
24 | var err error
25 | cloud.render, err = gorender.New(cloud.apiKey)
26 |
27 | return err
28 | }
29 |
30 | func (cloud *Render) GetCapabilities() []adapter.Capability {
31 | var caps []adapter.Capability
32 |
33 | caps = append(caps, adapter.Capability{
34 | ID: "instances",
35 | Name: "Services",
36 | })
37 |
38 | return caps
39 | }
40 |
--------------------------------------------------------------------------------
/nori/vultr/ci.go:
--------------------------------------------------------------------------------
1 | package vultr
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Vultr) ListPipelines() ([]models.Pipeline, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
--------------------------------------------------------------------------------
/nori/vultr/instances.go:
--------------------------------------------------------------------------------
1 | package vultr
2 |
3 | import (
4 | "context"
5 | // "errors"
6 | "fmt"
7 | "strings"
8 |
9 | "github.com/mrusme/planor/nori/models"
10 |
11 | "github.com/vultr/govultr/v2"
12 | )
13 |
14 | func (cloud *Vultr) ListInstances() ([]models.Instance, error) {
15 | input := govultr.ListOptions{}
16 |
17 | var instances []models.Instance
18 | ret, _, err := cloud.vultr.Instance.List(context.Background(), &input)
19 | if err != nil {
20 | return nil, err
21 | }
22 |
23 | for _, instance := range ret {
24 | newInstance := models.Instance{
25 | ID: instance.ID,
26 | Name: instance.Label,
27 |
28 | Region: instance.Region,
29 |
30 | Type: instance.Plan,
31 | Architecture: instance.Plan,
32 | CPUCores: instance.VCPUCount,
33 | CPUThreads: 1,
34 |
35 | RAMSize: instance.RAM,
36 | DiskSize: instance.Disk,
37 |
38 | OS: instance.Os,
39 | Image: instance.ImageID,
40 |
41 | InternalIPv4: instance.InternalIP,
42 |
43 | IPv4: instance.MainIP,
44 | NetmaskV4: instance.NetmaskV4,
45 | GatewayV4: instance.GatewayV4,
46 |
47 | IPv6: instance.V6MainIP,
48 | NetworkV6: instance.V6Network,
49 | NetsizeV6: instance.V6NetworkSize,
50 |
51 | TransferLimit: instance.AllowedBandwidth,
52 | Info: strings.Join(instance.Features, ", "),
53 | Status: fmt.Sprintf(
54 | "%s\nPower: %s\nServer: %s",
55 | instance.Status,
56 | instance.PowerStatus,
57 | instance.ServerStatus,
58 | ),
59 | VNC: instance.KVM,
60 | }
61 |
62 | instances = append(instances, newInstance)
63 | }
64 |
65 | return instances, nil
66 | }
67 |
--------------------------------------------------------------------------------
/nori/vultr/logs.go:
--------------------------------------------------------------------------------
1 | package vultr
2 |
3 | import (
4 | "errors"
5 |
6 | "github.com/mrusme/planor/nori/models"
7 | )
8 |
9 | func (cloud *Vultr) ListLogGroups(updateStreams bool, updateEvents bool) ([]models.LogGroup, error) {
10 | return nil, errors.New("Unsupported")
11 | }
12 |
13 | func (cloud *Vultr) UpdateLogStreams(logGroup *models.LogGroup, updateEvents bool) error {
14 | return errors.New("Unsupported")
15 | }
16 |
17 | func (cloud *Vultr) UpdateLogEvents(logStream *models.LogStream) error {
18 | return errors.New("Unsupported")
19 | }
20 |
--------------------------------------------------------------------------------
/nori/vultr/vultr.go:
--------------------------------------------------------------------------------
1 | package vultr
2 |
3 | import (
4 | "context"
5 | "os"
6 |
7 | "github.com/mrusme/planor/nori/adapter"
8 |
9 | "github.com/vultr/govultr/v2"
10 | "golang.org/x/oauth2"
11 | )
12 |
13 | type Vultr struct {
14 | cfg oauth2.Config
15 | apiKey string
16 | vultr *govultr.Client
17 | }
18 |
19 | func (cloud *Vultr) LoadProfile(profile *string) error {
20 | cloud.apiKey = os.Getenv(*profile)
21 | cloud.cfg = oauth2.Config{}
22 |
23 | return nil
24 | }
25 |
26 | func (cloud *Vultr) LoadClients() error {
27 | ctx := context.Background()
28 | ts := cloud.cfg.TokenSource(ctx, &oauth2.Token{AccessToken: cloud.apiKey})
29 | cloud.vultr = govultr.NewClient(oauth2.NewClient(ctx, ts))
30 | cloud.vultr.SetUserAgent("github.com/mrusme/planor")
31 |
32 | return nil
33 | }
34 |
35 | func (cloud *Vultr) GetCapabilities() []adapter.Capability {
36 | var caps []adapter.Capability
37 |
38 | caps = append(caps, adapter.Capability{
39 | ID: "instances",
40 | Name: "Cloud Instances",
41 | })
42 |
43 | return caps
44 | }
45 |
--------------------------------------------------------------------------------
/planor.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "flag"
5 | // "io/ioutil"
6 | // "log"
7 | "os"
8 |
9 | tea "github.com/charmbracelet/bubbletea"
10 | "github.com/mrusme/planor/nori"
11 | "github.com/mrusme/planor/ui"
12 | "github.com/mrusme/planor/ui/uictx"
13 | )
14 |
15 | func main() {
16 | var cloudProvider = "aws"
17 | var cloudProfile = ""
18 |
19 | flag.StringVar(&cloudProvider, "c", "aws",
20 | "cloud provider: aws, vultr, heroku")
21 | flag.StringVar(&cloudProfile, "p", "",
22 | "aws profile name, vultr/heroku/render/fleek api key env variable name")
23 | flag.Parse()
24 |
25 | if cloudProvider == "" || cloudProfile == "" {
26 | flag.Usage()
27 | os.Exit(1)
28 | }
29 |
30 | cloud, err := nori.New(&cloudProvider, &cloudProfile)
31 | if err != nil {
32 | panic(err)
33 | }
34 |
35 | ctx := uictx.New(&cloud)
36 |
37 | // log.SetOutput(ioutil.Discard)
38 |
39 | tui := tea.NewProgram(ui.NewModel(&ctx), tea.WithAltScreen())
40 | err = tui.Start()
41 | if err != nil {
42 | panic(err)
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/screen-ci.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrusme/planor/53440195c5fb22755f6004a4cee9bfb21ab359f5/screen-ci.png
--------------------------------------------------------------------------------
/screen-logs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mrusme/planor/53440195c5fb22755f6004a4cee9bfb21ab359f5/screen-logs.png
--------------------------------------------------------------------------------
/ui/navigation/navigation.go:
--------------------------------------------------------------------------------
1 | package navigation
2 |
3 | import (
4 | "strings"
5 |
6 | "github.com/mrusme/planor/ui/uictx"
7 |
8 | "github.com/charmbracelet/bubbles/spinner"
9 | tea "github.com/charmbracelet/bubbletea"
10 | "github.com/charmbracelet/lipgloss"
11 | )
12 |
13 | var (
14 | highlight = lipgloss.AdaptiveColor{Light: "#874BFD", Dark: "#7D56F4"}
15 |
16 | activeTabBorder = lipgloss.Border{
17 | Top: "─",
18 | Bottom: " ",
19 | Left: "│",
20 | Right: "│",
21 | TopLeft: "╭",
22 | TopRight: "╮",
23 | BottomLeft: "┘",
24 | BottomRight: "└",
25 | }
26 |
27 | tabBorder = lipgloss.Border{
28 | Top: "─",
29 | Bottom: "─",
30 | Left: "│",
31 | Right: "│",
32 | TopLeft: "╭",
33 | TopRight: "╮",
34 | BottomLeft: "┴",
35 | BottomRight: "┴",
36 | }
37 |
38 | tab = lipgloss.NewStyle().
39 | Border(tabBorder, true).
40 | BorderForeground(highlight).
41 | Padding(0, 1)
42 |
43 | activeTab = tab.Copy().Border(activeTabBorder, true)
44 |
45 | tabGap = tab.Copy().
46 | BorderTop(false).
47 | BorderLeft(false).
48 | BorderRight(false)
49 | )
50 |
51 | var Navigation = []string{}
52 |
53 | type Model struct {
54 | CurrentId int
55 | ctx *uictx.Ctx
56 | spinner spinner.Model
57 | }
58 |
59 | func NewModel(ctx *uictx.Ctx) Model {
60 | m := Model{
61 | CurrentId: 0,
62 | ctx: ctx,
63 | }
64 |
65 | for _, capability := range (*ctx.Cloud).GetCapabilities() {
66 | Navigation = append(Navigation, capability.Name)
67 | }
68 |
69 | m.spinner = spinner.New()
70 | m.spinner.Spinner = spinner.Dot
71 | m.spinner.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
72 |
73 | return m
74 | }
75 |
76 | func (m Model) Init() tea.Cmd {
77 | return m.spinner.Tick
78 | }
79 |
80 | func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
81 | var cmds []tea.Cmd
82 |
83 | if m.ctx.Loading == true {
84 | cmds = append(cmds, m.spinner.Tick)
85 | } else {
86 | return m, nil
87 | }
88 |
89 | switch msg := msg.(type) {
90 | case spinner.TickMsg:
91 | var cmd tea.Cmd
92 | m.spinner, cmd = m.spinner.Update(msg)
93 | cmds = append(cmds, cmd)
94 | }
95 |
96 | return m, tea.Batch(cmds...)
97 | }
98 |
99 | func (m Model) View() string {
100 | var items []string
101 |
102 | for i, nav := range Navigation {
103 | if m.CurrentId == i {
104 | items = append(items, activeTab.Render(nav))
105 | } else {
106 | items = append(items, tab.Render(nav))
107 | }
108 | }
109 |
110 | row := lipgloss.JoinHorizontal(
111 | lipgloss.Top,
112 | items...,
113 | )
114 |
115 | if m.ctx.Loading == false {
116 | gap := tabGap.Render(strings.Repeat(" ", max(0, m.ctx.Screen[0]-lipgloss.Width(row)-2)))
117 | row = lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap)
118 | } else {
119 | gap := tabGap.Render(strings.Repeat(" ", max(0, m.ctx.Screen[0]-lipgloss.Width(row)-4)))
120 | row = lipgloss.JoinHorizontal(lipgloss.Bottom, row, gap, " ", m.spinner.View())
121 | }
122 |
123 | return lipgloss.JoinHorizontal(lipgloss.Top, row, "\n\n")
124 | }
125 |
126 | func max(a, b int) int {
127 | if a > b {
128 | return a
129 | }
130 | return b
131 | }
132 |
133 | func (m *Model) NthTab(nth int) {
134 | if nth > len(Navigation) {
135 | nth = len(Navigation)
136 | } else if nth < 1 {
137 | nth = 1
138 | }
139 |
140 | m.CurrentId = nth - 1
141 | }
142 |
143 | func (m *Model) PrevTab() {
144 | m.CurrentId--
145 |
146 | if m.CurrentId < 0 {
147 | m.CurrentId = len(Navigation) - 1
148 | }
149 | }
150 |
151 | func (m *Model) NextTab() {
152 | m.CurrentId++
153 |
154 | if m.CurrentId >= len(Navigation) {
155 | m.CurrentId = 0
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/ui/ui.go:
--------------------------------------------------------------------------------
1 | package ui
2 |
3 | import (
4 | // "fmt"
5 | "strings"
6 |
7 | "github.com/mrusme/planor/ui/navigation"
8 | "github.com/mrusme/planor/ui/uictx"
9 |
10 | "github.com/mrusme/planor/ui/views"
11 | "github.com/mrusme/planor/ui/views/ci"
12 | "github.com/mrusme/planor/ui/views/instances"
13 | "github.com/mrusme/planor/ui/views/logs"
14 |
15 | "github.com/charmbracelet/bubbles/key"
16 | tea "github.com/charmbracelet/bubbletea"
17 | )
18 |
19 | type KeyMap struct {
20 | FirstTab key.Binding
21 | SecondTab key.Binding
22 | ThirdTab key.Binding
23 | FourthTab key.Binding
24 | FifthTab key.Binding
25 | SixthTab key.Binding
26 | SeventhTab key.Binding
27 | EightTab key.Binding
28 | NinthTab key.Binding
29 | TenthTab key.Binding
30 | EleventhTab key.Binding
31 | TwelfthTab key.Binding
32 | ThirteenthTab key.Binding
33 | PrevTab key.Binding
34 | NextTab key.Binding
35 | Up key.Binding
36 | Down key.Binding
37 | Quit key.Binding
38 | }
39 |
40 | var DefaultKeyMap = KeyMap{
41 | FirstTab: key.NewBinding(
42 | key.WithKeys("f1"),
43 | key.WithHelp("f1", "first tab"),
44 | ),
45 | SecondTab: key.NewBinding(
46 | key.WithKeys("f2"),
47 | key.WithHelp("f2", "second tab"),
48 | ),
49 | ThirdTab: key.NewBinding(
50 | key.WithKeys("f3"),
51 | key.WithHelp("f3", "third tab"),
52 | ),
53 | FourthTab: key.NewBinding(
54 | key.WithKeys("f4"),
55 | key.WithHelp("f4", "fourth tab"),
56 | ),
57 | FifthTab: key.NewBinding(
58 | key.WithKeys("f5"),
59 | key.WithHelp("f5", "fifth tab"),
60 | ),
61 | SixthTab: key.NewBinding(
62 | key.WithKeys("f6"),
63 | key.WithHelp("f6", "sixth tab"),
64 | ),
65 | SeventhTab: key.NewBinding(
66 | key.WithKeys("f7"),
67 | key.WithHelp("f7", "seventh tab"),
68 | ),
69 | EightTab: key.NewBinding(
70 | key.WithKeys("f8"),
71 | key.WithHelp("f8", "eight tab"),
72 | ),
73 | NinthTab: key.NewBinding(
74 | key.WithKeys("f9"),
75 | key.WithHelp("f9", "ninth tab"),
76 | ),
77 | TenthTab: key.NewBinding(
78 | key.WithKeys("f10"),
79 | key.WithHelp("f10", "tenth tab"),
80 | ),
81 | EleventhTab: key.NewBinding(
82 | key.WithKeys("f11"),
83 | key.WithHelp("f11", "eleventh tab"),
84 | ),
85 | TwelfthTab: key.NewBinding(
86 | key.WithKeys("f12"),
87 | key.WithHelp("f12", "twelfth tab"),
88 | ),
89 | ThirteenthTab: key.NewBinding(
90 | key.WithKeys("f13"),
91 | key.WithHelp("f13", "thirteenth tab"),
92 | ),
93 | PrevTab: key.NewBinding(
94 | key.WithKeys("ctrl+p"),
95 | key.WithHelp("ctrl+p", "previous tab"),
96 | ),
97 | NextTab: key.NewBinding(
98 | key.WithKeys("ctrl+n"),
99 | key.WithHelp("ctrl+n", "next tab"),
100 | ),
101 | Up: key.NewBinding(
102 | key.WithKeys("k", "up"),
103 | key.WithHelp("↑/k", "move up"),
104 | ),
105 | Down: key.NewBinding(
106 | key.WithKeys("j", "down"),
107 | key.WithHelp("↓/j", "move down"),
108 | ),
109 | Quit: key.NewBinding(
110 | key.WithKeys("q", "ctrl+q"),
111 | key.WithHelp("q/Q", "quit"),
112 | ),
113 | }
114 |
115 | type Model struct {
116 | keymap KeyMap
117 | nav navigation.Model
118 | views []views.View
119 | ctx *uictx.Ctx
120 | }
121 |
122 | func NewModel(ctx *uictx.Ctx) Model {
123 | m := Model{
124 | keymap: DefaultKeyMap,
125 | ctx: ctx,
126 | }
127 |
128 | m.nav = navigation.NewModel(m.ctx)
129 | for _, capability := range (*m.ctx.Cloud).GetCapabilities() {
130 | switch capability.ID {
131 | case "instances":
132 | m.views = append(m.views, instances.NewModel(m.ctx))
133 | case "ci":
134 | m.views = append(m.views, ci.NewModel(m.ctx))
135 | case "logs":
136 | m.views = append(m.views, logs.NewModel(m.ctx))
137 | }
138 | }
139 |
140 | return m
141 | }
142 |
143 | func (m Model) Init() tea.Cmd {
144 | return tea.Batch(tea.EnterAltScreen)
145 | }
146 |
147 | func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
148 | cmds := make([]tea.Cmd, 0)
149 |
150 | switch msg := msg.(type) {
151 | case tea.KeyMsg:
152 | switch {
153 | case key.Matches(msg, m.keymap.Quit):
154 | return m, tea.Quit
155 |
156 | case key.Matches(msg, m.keymap.FirstTab):
157 | m.nav.NthTab(1)
158 | return m, nil
159 |
160 | case key.Matches(msg, m.keymap.SecondTab):
161 | m.nav.NthTab(2)
162 | return m, nil
163 |
164 | case key.Matches(msg, m.keymap.ThirdTab):
165 | m.nav.NthTab(3)
166 | return m, nil
167 |
168 | case key.Matches(msg, m.keymap.FourthTab):
169 | m.nav.NthTab(4)
170 | return m, nil
171 |
172 | case key.Matches(msg, m.keymap.FifthTab):
173 | m.nav.NthTab(5)
174 | return m, nil
175 |
176 | case key.Matches(msg, m.keymap.SixthTab):
177 | m.nav.NthTab(6)
178 | return m, nil
179 |
180 | case key.Matches(msg, m.keymap.SeventhTab):
181 | m.nav.NthTab(7)
182 | return m, nil
183 |
184 | case key.Matches(msg, m.keymap.EightTab):
185 | m.nav.NthTab(8)
186 | return m, nil
187 |
188 | case key.Matches(msg, m.keymap.NinthTab):
189 | m.nav.NthTab(9)
190 | return m, nil
191 |
192 | case key.Matches(msg, m.keymap.TenthTab):
193 | m.nav.NthTab(10)
194 | return m, nil
195 |
196 | case key.Matches(msg, m.keymap.EleventhTab):
197 | m.nav.NthTab(11)
198 | return m, nil
199 |
200 | case key.Matches(msg, m.keymap.TwelfthTab):
201 | m.nav.NthTab(12)
202 | return m, nil
203 |
204 | case key.Matches(msg, m.keymap.ThirteenthTab):
205 | m.nav.NthTab(13)
206 | return m, nil
207 |
208 | case key.Matches(msg, m.keymap.PrevTab):
209 | m.nav.PrevTab()
210 | return m, nil
211 |
212 | case key.Matches(msg, m.keymap.NextTab):
213 | m.nav.NextTab()
214 | return m, nil
215 | }
216 |
217 | case tea.WindowSizeMsg:
218 | m.setSizes(msg.Width, msg.Height)
219 | for i := range m.views {
220 | v, cmd := m.views[i].Update(msg)
221 | m.views[i] = v
222 | cmds = append(cmds, cmd)
223 | }
224 | }
225 |
226 | v, cmd := m.views[m.nav.CurrentId].Update(msg)
227 | m.views[m.nav.CurrentId] = v
228 | cmds = append(cmds, cmd)
229 |
230 | nav, cmd := m.nav.Update(msg)
231 | m.nav = nav
232 | cmds = append(cmds, cmd)
233 |
234 | return m, tea.Batch(cmds...)
235 | }
236 |
237 | func (m Model) View() string {
238 | s := strings.Builder{}
239 | s.WriteString(m.nav.View() + "\n\n")
240 | s.WriteString(m.views[m.nav.CurrentId].View())
241 | return s.String()
242 | }
243 |
244 | func (m Model) setSizes(winWidth int, winHeight int) {
245 | (*m.ctx).Screen[0] = winWidth
246 | (*m.ctx).Screen[1] = winHeight
247 | m.ctx.Content[0] = m.ctx.Screen[0]
248 | m.ctx.Content[1] = m.ctx.Screen[1] - 5
249 | }
250 |
--------------------------------------------------------------------------------
/ui/uictx/uictx.go:
--------------------------------------------------------------------------------
1 | package uictx
2 |
3 | import (
4 | "github.com/mrusme/planor/nori"
5 | )
6 |
7 | type Ctx struct {
8 | Screen [2]int
9 | Content [2]int
10 | Cloud *nori.Nor
11 | Loading bool
12 | }
13 |
14 | func New(cloud *nori.Nor) Ctx {
15 | return Ctx{
16 | Screen: [2]int{0, 0},
17 | Content: [2]int{0, 0},
18 | Cloud: cloud,
19 | Loading: false,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ui/views/ci/ci.go:
--------------------------------------------------------------------------------
1 | package ci
2 |
3 | import (
4 | "fmt"
5 | "math"
6 |
7 | "github.com/charmbracelet/bubbles/key"
8 | "github.com/charmbracelet/bubbles/list"
9 | "github.com/charmbracelet/bubbles/viewport"
10 | tea "github.com/charmbracelet/bubbletea"
11 | "github.com/charmbracelet/lipgloss"
12 | "github.com/mrusme/planor/nori/models"
13 | "github.com/mrusme/planor/ui/uictx"
14 | )
15 |
16 | var (
17 | listStyle = lipgloss.NewStyle().
18 | Margin(0, 0, 0, 0).
19 | Padding(1, 1).
20 | Border(lipgloss.RoundedBorder()).
21 | BorderForeground(lipgloss.Color("#874BFD")).
22 | BorderTop(true).
23 | BorderLeft(true).
24 | BorderRight(true).
25 | BorderBottom(true)
26 |
27 | viewportStyle = lipgloss.NewStyle().
28 | Margin(0, 0, 0, 0).
29 | Padding(1, 1).
30 | Border(lipgloss.RoundedBorder()).
31 | BorderForeground(lipgloss.Color("#874BFD")).
32 | BorderTop(true).
33 | BorderLeft(true).
34 | BorderRight(true).
35 | BorderBottom(true)
36 | )
37 |
38 | type KeyMap struct {
39 | Refresh key.Binding
40 | Select key.Binding
41 | SwitchFocus key.Binding
42 | }
43 |
44 | var DefaultKeyMap = KeyMap{
45 | Refresh: key.NewBinding(
46 | key.WithKeys("r", "R"),
47 | key.WithHelp("r/R", "refresh"),
48 | ),
49 | Select: key.NewBinding(
50 | key.WithKeys("enter"),
51 | key.WithHelp("enter", "select"),
52 | ),
53 | SwitchFocus: key.NewBinding(
54 | key.WithKeys("tab"),
55 | key.WithHelp("tab", "switch focus"),
56 | ),
57 | }
58 |
59 | type Model struct {
60 | keymap KeyMap
61 | list list.Model
62 | items []list.Item
63 | viewport viewport.Model
64 | ctx *uictx.Ctx
65 |
66 | focused int
67 | focusables [2]tea.Model
68 | }
69 |
70 | func (m Model) Init() tea.Cmd {
71 | return nil
72 | }
73 |
74 | func NewModel(ctx *uictx.Ctx) Model {
75 | m := Model{
76 | keymap: DefaultKeyMap,
77 | focused: 0,
78 | }
79 |
80 | // m.focusables = append(m.focusables, m.list)
81 | // m.focusables = append(m.focusables, m.viewport)
82 |
83 | m.list = list.New(m.items, list.NewDefaultDelegate(), 0, 0)
84 | m.list.Title = "Pipelines"
85 | m.ctx = ctx
86 |
87 | return m
88 | }
89 |
90 | func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
91 | var cmds []tea.Cmd
92 |
93 | switch msg := msg.(type) {
94 | case tea.KeyMsg:
95 | switch {
96 | case key.Matches(msg, m.keymap.Refresh):
97 | m.ctx.Loading = true
98 | cmds = append(cmds, m.refresh())
99 |
100 | case key.Matches(msg, m.keymap.SwitchFocus):
101 | m.focused++
102 | if m.focused >= len(m.focusables) {
103 | m.focused = 0
104 | }
105 | // return m, nil
106 |
107 | case key.Matches(msg, m.keymap.Select):
108 | i, ok := m.list.SelectedItem().(models.Pipeline)
109 | if ok {
110 | m.viewport.SetContent(m.renderViewport(&i))
111 | return m, nil
112 | }
113 | }
114 |
115 | case tea.WindowSizeMsg:
116 | listWidth := int(math.Floor(float64(m.ctx.Content[0]) / 4.0))
117 | listHeight := m.ctx.Content[1] - 1
118 | viewportWidth := m.ctx.Content[0] - listWidth - 4
119 | viewportHeight := m.ctx.Content[1] - 1
120 |
121 | listStyle.Width(listWidth)
122 | listStyle.Height(listHeight)
123 | m.list.SetSize(
124 | listWidth-2,
125 | listHeight-2,
126 | )
127 |
128 | viewportStyle.Width(viewportWidth)
129 | viewportStyle.Height(viewportHeight)
130 | m.viewport = viewport.New(viewportWidth-4, viewportHeight-4)
131 | m.viewport.Width = viewportWidth - 4
132 | m.viewport.Height = viewportHeight - 4
133 | // cmds = append(cmds, viewport.Sync(m.viewport))
134 |
135 | case []list.Item:
136 | m.items = msg
137 | m.list.SetItems(m.items)
138 | m.ctx.Loading = false
139 | }
140 |
141 | var cmd tea.Cmd
142 |
143 | if m.focused == 0 {
144 | listStyle.BorderForeground(lipgloss.Color("#FFFFFF"))
145 | viewportStyle.BorderForeground(lipgloss.Color("#874BFD"))
146 | m.list, cmd = m.list.Update(msg)
147 | cmds = append(cmds, cmd)
148 | } else if m.focused == 1 {
149 | listStyle.BorderForeground(lipgloss.Color("#874BFD"))
150 | viewportStyle.BorderForeground(lipgloss.Color("#FFFFFF"))
151 | m.viewport, cmd = m.viewport.Update(msg)
152 | cmds = append(cmds, cmd)
153 | }
154 |
155 | return m, tea.Batch(cmds...)
156 | }
157 |
158 | func (m Model) View() string {
159 | var view string
160 |
161 | view = lipgloss.JoinHorizontal(
162 | lipgloss.Top,
163 | listStyle.Render(m.list.View()),
164 | viewportStyle.Render(m.viewport.View()),
165 | )
166 |
167 | return view
168 | }
169 |
170 | func (m *Model) refresh() tea.Cmd {
171 | return func() tea.Msg {
172 | var items []list.Item
173 |
174 | pipelines, err := (*m.ctx.Cloud).ListPipelines()
175 | if err != nil {
176 | fmt.Printf("%s", err) // TODO: Implement error message
177 | }
178 | for _, pipeline := range pipelines {
179 | items = append(items, pipeline)
180 | }
181 |
182 | return items
183 | }
184 | }
185 |
186 | func (m *Model) renderViewport(pipeline *models.Pipeline) string {
187 | var vp string = ""
188 |
189 | vp = fmt.Sprintf(
190 | "%s\n\nUpdated %s\n",
191 | pipeline.Name,
192 | pipeline.UpdatedAt.String(),
193 | )
194 |
195 | for _, stage := range pipeline.Stages {
196 | vp = fmt.Sprintf(
197 | "%s\n %s\n Status: %s\n",
198 | vp,
199 | stage.Name,
200 | stage.Status,
201 | )
202 |
203 | for _, action := range stage.Actions {
204 | prct := fmt.Sprintf("%d%%", action.PercentComplete)
205 | if prct == "0%" {
206 | prct = ""
207 | }
208 |
209 | vp = fmt.Sprintf(
210 | "%s\n %s\n Updated: %s\n Status: %s %s\n %s\n",
211 | vp,
212 | action.Name,
213 | action.UpdatedAt.String(),
214 | action.Status,
215 | prct,
216 | action.Summary,
217 | )
218 | }
219 | }
220 |
221 | return vp
222 | }
223 |
--------------------------------------------------------------------------------
/ui/views/instances/instances.go:
--------------------------------------------------------------------------------
1 | package instances
2 |
3 | import (
4 | "fmt"
5 | "math"
6 |
7 | "github.com/charmbracelet/bubbles/key"
8 | "github.com/charmbracelet/bubbles/list"
9 | "github.com/charmbracelet/bubbles/viewport"
10 | tea "github.com/charmbracelet/bubbletea"
11 | "github.com/charmbracelet/lipgloss"
12 | "github.com/mrusme/planor/nori/models"
13 | "github.com/mrusme/planor/ui/uictx"
14 | )
15 |
16 | var (
17 | listStyle = lipgloss.NewStyle().
18 | Margin(0, 0, 0, 0).
19 | Padding(1, 1).
20 | Border(lipgloss.RoundedBorder()).
21 | BorderForeground(lipgloss.Color("#874BFD")).
22 | BorderTop(true).
23 | BorderLeft(true).
24 | BorderRight(true).
25 | BorderBottom(true)
26 |
27 | viewportStyle = lipgloss.NewStyle().
28 | Margin(0, 0, 0, 0).
29 | Padding(1, 1).
30 | Border(lipgloss.RoundedBorder()).
31 | BorderForeground(lipgloss.Color("#874BFD")).
32 | BorderTop(true).
33 | BorderLeft(true).
34 | BorderRight(true).
35 | BorderBottom(true)
36 | )
37 |
38 | type KeyMap struct {
39 | Refresh key.Binding
40 | Select key.Binding
41 | SwitchFocus key.Binding
42 | }
43 |
44 | var DefaultKeyMap = KeyMap{
45 | Refresh: key.NewBinding(
46 | key.WithKeys("r", "R"),
47 | key.WithHelp("r/R", "refresh"),
48 | ),
49 | Select: key.NewBinding(
50 | key.WithKeys("enter"),
51 | key.WithHelp("enter", "select"),
52 | ),
53 | SwitchFocus: key.NewBinding(
54 | key.WithKeys("tab"),
55 | key.WithHelp("tab", "switch focus"),
56 | ),
57 | }
58 |
59 | type Model struct {
60 | keymap KeyMap
61 | list list.Model
62 | items []list.Item
63 | viewport viewport.Model
64 | ctx *uictx.Ctx
65 |
66 | focused int
67 | focusables [2]tea.Model
68 | }
69 |
70 | func (m Model) Init() tea.Cmd {
71 | return nil
72 | }
73 |
74 | func NewModel(ctx *uictx.Ctx) Model {
75 | m := Model{
76 | keymap: DefaultKeyMap,
77 | focused: 0,
78 | }
79 |
80 | // m.focusables = append(m.focusables, m.list)
81 | // m.focusables = append(m.focusables, m.viewport)
82 |
83 | m.list = list.New(m.items, list.NewDefaultDelegate(), 0, 0)
84 | m.list.Title = "Instances"
85 | m.ctx = ctx
86 |
87 | return m
88 | }
89 |
90 | func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
91 | var cmds []tea.Cmd
92 |
93 | switch msg := msg.(type) {
94 | case tea.KeyMsg:
95 | switch {
96 | case key.Matches(msg, m.keymap.Refresh):
97 | m.ctx.Loading = true
98 | cmds = append(cmds, m.refresh())
99 |
100 | case key.Matches(msg, m.keymap.SwitchFocus):
101 | m.focused++
102 | if m.focused >= len(m.focusables) {
103 | m.focused = 0
104 | }
105 | // return m, nil
106 |
107 | case key.Matches(msg, m.keymap.Select):
108 | i, ok := m.list.SelectedItem().(models.Instance)
109 | if ok {
110 | m.viewport.SetContent(m.renderViewport(&i))
111 | return m, nil
112 | }
113 | }
114 |
115 | case tea.WindowSizeMsg:
116 | listWidth := int(math.Floor(float64(m.ctx.Content[0]) / 4.0))
117 | listHeight := m.ctx.Content[1] - 1
118 | viewportWidth := m.ctx.Content[0] - listWidth - 4
119 | viewportHeight := m.ctx.Content[1] - 1
120 |
121 | listStyle.Width(listWidth)
122 | listStyle.Height(listHeight)
123 | m.list.SetSize(
124 | listWidth-2,
125 | listHeight-2,
126 | )
127 |
128 | viewportStyle.Width(viewportWidth)
129 | viewportStyle.Height(viewportHeight)
130 | m.viewport = viewport.New(viewportWidth-4, viewportHeight-4)
131 | m.viewport.Width = viewportWidth - 4
132 | m.viewport.Height = viewportHeight - 4
133 | // cmds = append(cmds, viewport.Sync(m.viewport))
134 |
135 | case []list.Item:
136 | m.items = msg
137 | m.list.SetItems(m.items)
138 | m.ctx.Loading = false
139 | }
140 |
141 | var cmd tea.Cmd
142 |
143 | if m.focused == 0 {
144 | listStyle.BorderForeground(lipgloss.Color("#FFFFFF"))
145 | viewportStyle.BorderForeground(lipgloss.Color("#874BFD"))
146 | m.list, cmd = m.list.Update(msg)
147 | cmds = append(cmds, cmd)
148 | } else if m.focused == 1 {
149 | listStyle.BorderForeground(lipgloss.Color("#874BFD"))
150 | viewportStyle.BorderForeground(lipgloss.Color("#FFFFFF"))
151 | m.viewport, cmd = m.viewport.Update(msg)
152 | cmds = append(cmds, cmd)
153 | }
154 |
155 | return m, tea.Batch(cmds...)
156 | }
157 |
158 | func (m Model) View() string {
159 | var view string
160 |
161 | view = lipgloss.JoinHorizontal(
162 | lipgloss.Top,
163 | listStyle.Render(m.list.View()),
164 | viewportStyle.Render(m.viewport.View()),
165 | )
166 |
167 | return view
168 | }
169 |
170 | func (m *Model) refresh() tea.Cmd {
171 | return func() tea.Msg {
172 | var items []list.Item
173 |
174 | instances, err := (*m.ctx.Cloud).ListInstances()
175 | if err != nil {
176 | fmt.Printf("%s", err) // TODO: Implement error message
177 | }
178 | for _, instance := range instances {
179 | items = append(items, instance)
180 | }
181 |
182 | return items
183 | }
184 | }
185 |
186 | func (m *Model) renderViewport(instance *models.Instance) string {
187 | var vp string = ""
188 |
189 | vp = fmt.Sprintf(
190 | "%s\n",
191 | instance.Name,
192 | )
193 |
194 | if len(instance.Region) > 0 {
195 | vp = fmt.Sprintf(
196 | "%sRegion: %s\n",
197 | vp,
198 | instance.Region,
199 | )
200 | }
201 |
202 | if len(instance.Info) > 0 {
203 | vp = fmt.Sprintf(
204 | "%sInfo: %s\n",
205 | vp,
206 | instance.Info,
207 | )
208 | }
209 |
210 | if len(instance.Status) > 0 {
211 | vp = fmt.Sprintf(
212 | "%sStatus: %s\n",
213 | vp,
214 | instance.Status,
215 | )
216 | }
217 |
218 | vp = fmt.Sprintf(
219 | "%s\nType: %s\nArchiecture: %s\n%d Cores, %d Threads per Core\n",
220 | vp,
221 | instance.Type,
222 | instance.Architecture,
223 | instance.CPUCores,
224 | instance.CPUThreads,
225 | )
226 |
227 | if len(instance.Hypervisor) > 0 {
228 | vp = fmt.Sprintf(
229 | "%sHypervisor: %s\n",
230 | vp,
231 | instance.Hypervisor,
232 | )
233 | }
234 |
235 | vp = fmt.Sprintf("%s\n", vp)
236 |
237 | if instance.RAMSize > 0 {
238 | vp = fmt.Sprintf(
239 | "%sRAM: %d MB\n",
240 | vp,
241 | instance.RAMSize,
242 | )
243 | }
244 |
245 | if instance.DiskSize > 0 {
246 | vp = fmt.Sprintf(
247 | "%sDisk: %d GB\n",
248 | vp,
249 | instance.DiskSize,
250 | )
251 | }
252 |
253 | if len(instance.OS) > 0 {
254 | vp = fmt.Sprintf(
255 | "%sOS: %s\n",
256 | vp,
257 | instance.OS,
258 | )
259 | }
260 |
261 | if len(instance.Image) > 0 {
262 | vp = fmt.Sprintf(
263 | "%sImage: %s\n",
264 | vp,
265 | instance.Image,
266 | )
267 | }
268 |
269 | vp = fmt.Sprintf("%s\n", vp)
270 |
271 | if len(instance.InternalIPv4) > 0 {
272 | vp = fmt.Sprintf(
273 | "%sInternal IPv4: %s\n",
274 | vp,
275 | instance.InternalIPv4,
276 | )
277 | }
278 |
279 | vp = fmt.Sprintf("%s\n", vp)
280 |
281 | if len(instance.IPv4) > 0 {
282 | vp = fmt.Sprintf(
283 | "%sIPv4: %s\nNetmask: %s\nGateway: %s\n",
284 | vp,
285 | instance.IPv4,
286 | instance.NetmaskV4,
287 | instance.GatewayV4,
288 | )
289 | }
290 |
291 | vp = fmt.Sprintf("%s\n", vp)
292 |
293 | if len(instance.IPv6) > 0 {
294 | vp = fmt.Sprintf(
295 | "%sIPv6: %s\nNetwork: %s\nSize: %d\n",
296 | vp,
297 | instance.IPv6,
298 | instance.NetworkV6,
299 | instance.NetsizeV6,
300 | )
301 | }
302 |
303 | vp = fmt.Sprintf("%s\n", vp)
304 |
305 | if instance.TransferLimit > 0 {
306 | vp = fmt.Sprintf(
307 | "%sTransfer limit: %d GB\n",
308 | vp,
309 | instance.TransferLimit,
310 | )
311 | }
312 |
313 | /* if len(instance.VNC) > 0 {
314 | vp = fmt.Sprintf(
315 | "%sVNC: %s\n",
316 | vp,
317 | instance.VNC,
318 | )
319 | } */
320 |
321 | return vp
322 | }
323 |
--------------------------------------------------------------------------------
/ui/views/logs/logs.go:
--------------------------------------------------------------------------------
1 | package logs
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "strings"
7 | "unicode"
8 |
9 | "github.com/charmbracelet/bubbles/key"
10 | "github.com/charmbracelet/bubbles/list"
11 | "github.com/charmbracelet/bubbles/viewport"
12 | tea "github.com/charmbracelet/bubbletea"
13 | "github.com/charmbracelet/lipgloss"
14 | "github.com/mrusme/planor/nori/models"
15 | "github.com/mrusme/planor/ui/uictx"
16 | )
17 |
18 | var (
19 | listStyle = lipgloss.NewStyle().
20 | Margin(0, 0, 0, 0).
21 | Padding(1, 1).
22 | Border(lipgloss.RoundedBorder()).
23 | BorderForeground(lipgloss.Color("#874BFD")).
24 | BorderTop(true).
25 | BorderLeft(true).
26 | BorderRight(true).
27 | BorderBottom(true)
28 |
29 | viewportStyle = lipgloss.NewStyle().
30 | Margin(0, 0, 0, 0).
31 | Padding(1, 1).
32 | Border(lipgloss.RoundedBorder()).
33 | BorderForeground(lipgloss.Color("#874BFD")).
34 | BorderTop(true).
35 | BorderLeft(true).
36 | BorderRight(true).
37 | BorderBottom(true)
38 | )
39 |
40 | type KeyMap struct {
41 | Refresh key.Binding
42 | Select key.Binding
43 | GoBack key.Binding
44 | SwitchFocus key.Binding
45 | }
46 |
47 | var DefaultKeyMap = KeyMap{
48 | Refresh: key.NewBinding(
49 | key.WithKeys("r", "R"),
50 | key.WithHelp("r/R", "refresh"),
51 | ),
52 | Select: key.NewBinding(
53 | key.WithKeys("enter"),
54 | key.WithHelp("enter", "select"),
55 | ),
56 | GoBack: key.NewBinding(
57 | key.WithKeys("backspace"),
58 | key.WithHelp("backspace", "go back"),
59 | ),
60 | SwitchFocus: key.NewBinding(
61 | key.WithKeys("tab"),
62 | key.WithHelp("tab", "switch focus"),
63 | ),
64 | }
65 |
66 | type Model struct {
67 | keymap KeyMap
68 | listGroups list.Model
69 | items []list.Item
70 | viewport viewport.Model
71 | ctx *uictx.Ctx
72 |
73 | focused int
74 | focusables [2]tea.Model
75 | }
76 |
77 | func (m Model) Init() tea.Cmd {
78 | return nil
79 | }
80 |
81 | func NewModel(ctx *uictx.Ctx) Model {
82 | m := Model{
83 | keymap: DefaultKeyMap,
84 | focused: 0,
85 | }
86 |
87 | m.listGroups = list.New(m.items, list.NewDefaultDelegate(), 0, 0)
88 | m.listGroups.Title = "Groups"
89 |
90 | m.ctx = ctx
91 |
92 | return m
93 | }
94 |
95 | func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
96 | var cmds []tea.Cmd
97 |
98 | switch msg := msg.(type) {
99 | case tea.KeyMsg:
100 | switch {
101 | case key.Matches(msg, m.keymap.Refresh):
102 | m.ctx.Loading = true
103 | m.listGroups.Title = "Groups"
104 | cmds = append(cmds, m.refresh())
105 |
106 | case key.Matches(msg, m.keymap.SwitchFocus):
107 | m.focused++
108 | if m.focused >= len(m.focusables) {
109 | m.focused = 0
110 | }
111 | // return m, nil
112 |
113 | case key.Matches(msg, m.keymap.GoBack):
114 | m.listGroups.SetItems(m.items)
115 | return m, nil
116 |
117 | case key.Matches(msg, m.keymap.Select):
118 | group, ok := m.listGroups.SelectedItem().(models.LogGroup)
119 | if ok {
120 | m.listGroups.Title = "Streams"
121 | (*m.ctx.Cloud).UpdateLogStreams(&group, false)
122 | var items []list.Item
123 | for _, stream := range group.Streams {
124 | items = append(items, stream)
125 | }
126 | m.listGroups.SetItems(items)
127 | m.listGroups.Select(0)
128 | return m, nil
129 | }
130 |
131 | stream, ok := m.listGroups.SelectedItem().(models.LogStream)
132 | if ok {
133 | (*m.ctx.Cloud).UpdateLogEvents(&stream)
134 | m.viewport.SetContent(m.renderViewport(&stream))
135 | return m, nil
136 | }
137 | }
138 |
139 | case tea.WindowSizeMsg:
140 | listWidth := int(math.Floor(float64(m.ctx.Content[0]) / 4.0))
141 | listHeight := m.ctx.Content[1] - 1
142 | viewportWidth := m.ctx.Content[0] - listWidth - 4
143 | viewportHeight := m.ctx.Content[1] - 1
144 |
145 | listStyle.Width(listWidth)
146 | listStyle.Height(listHeight)
147 | m.listGroups.SetSize(
148 | listWidth-2,
149 | listHeight-2,
150 | )
151 |
152 | viewportStyle.Width(viewportWidth)
153 | viewportStyle.Height(viewportHeight)
154 | m.viewport = viewport.New(viewportWidth-4, viewportHeight-4)
155 | m.viewport.Width = viewportWidth - 4
156 | m.viewport.Height = viewportHeight - 4
157 | // cmds = append(cmds, viewport.Sync(m.viewport))
158 |
159 | case []list.Item:
160 | m.items = msg
161 | m.listGroups.SetItems(m.items)
162 | m.ctx.Loading = false
163 | }
164 |
165 | var cmd tea.Cmd
166 |
167 | if m.focused == 0 {
168 | listStyle.BorderForeground(lipgloss.Color("#FFFFFF"))
169 | viewportStyle.BorderForeground(lipgloss.Color("#874BFD"))
170 | m.listGroups, cmd = m.listGroups.Update(msg)
171 | cmds = append(cmds, cmd)
172 | } else if m.focused == 1 {
173 | listStyle.BorderForeground(lipgloss.Color("#874BFD"))
174 | viewportStyle.BorderForeground(lipgloss.Color("#FFFFFF"))
175 | m.viewport, cmd = m.viewport.Update(msg)
176 | cmds = append(cmds, cmd)
177 | }
178 |
179 | return m, tea.Batch(cmds...)
180 | }
181 |
182 | func (m Model) View() string {
183 | var view string
184 |
185 | view = lipgloss.JoinHorizontal(
186 | lipgloss.Top,
187 | listStyle.Render(m.listGroups.View()),
188 | viewportStyle.Render(m.viewport.View()),
189 | )
190 |
191 | return view
192 | }
193 |
194 | func (m *Model) refresh() tea.Cmd {
195 | return func() tea.Msg {
196 | var items []list.Item
197 |
198 | logGroups, err := (*m.ctx.Cloud).ListLogGroups(false, false)
199 | if err != nil {
200 | fmt.Printf("%s", err) // TODO: Implement error message
201 | return items
202 | }
203 | for _, logGroup := range logGroups {
204 | items = append(items, logGroup)
205 | }
206 |
207 | return items
208 | }
209 | }
210 |
211 | func (m *Model) renderViewport(logStream *models.LogStream) string {
212 | var vp string = ""
213 |
214 | for _, event := range logStream.LogEvents {
215 | cleaned := strings.Map(func(r rune) rune {
216 | if unicode.IsPrint(r) && r != '\\' {
217 | return r
218 | }
219 | return -1
220 | }, event.Message)
221 |
222 | vp = fmt.Sprintf(
223 | "%s%s\n",
224 | vp,
225 | cleaned,
226 | )
227 | }
228 |
229 | return vp
230 | }
231 |
--------------------------------------------------------------------------------
/ui/views/views.go:
--------------------------------------------------------------------------------
1 | package views
2 |
3 | import (
4 | tea "github.com/charmbracelet/bubbletea"
5 | )
6 |
7 | type View interface {
8 | View() string
9 | Update(msg tea.Msg) (tea.Model, tea.Cmd)
10 | }
11 |
--------------------------------------------------------------------------------