├── .coveragerc
├── .envrc
├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── LICENSE.txt
├── MANIFEST.in
├── OWNERS.md
├── README.md
├── RELEASE.md
├── SUPPORT.md
├── packet
├── BGPConfig.py
├── BGPSession.py
├── Batch.py
├── Device.py
├── DeviceBatch.py
├── Email.py
├── Event.py
├── Facility.py
├── HardwareReservation.py
├── IPAddress.py
├── Manager.py
├── Metro.py
├── OperatingSystem.py
├── Organization.py
├── Plan.py
├── Project.py
├── Provider.py
├── SSHKey.py
├── Snapshot.py
├── Vlan.py
├── Volume.py
├── __init__.py
└── baseapi.py
├── setup.cfg
├── setup.py
├── shell.nix
├── test
├── fixtures
│ ├── get__capacity.json
│ ├── get_devices_9dec7266.json
│ ├── get_devices_e123s_ips.json
│ ├── get_facilities.json
│ ├── get_ips_e123s.json
│ ├── get_locations_metros.json
│ ├── get_operating-systems.json
│ ├── get_plans.json
│ ├── get_projects.json
│ ├── get_projects_1234_bgp-config.json
│ ├── get_projects_438659f0.json
│ ├── get_projects_438659f0_devices.json
│ ├── get_projects_438659f0_ips.json
│ ├── get_projects_438659f0_storage.json
│ ├── get_projects_438659f1_ips.json
│ ├── get_ssh-keys.json
│ ├── get_ssh-keys_084a5dec.json
│ ├── get_storage_f9a8a263.json
│ ├── get_storage_f9a8a263_snapshots.json
│ ├── get_user.json
│ ├── patch_devices_e781ae1b.json
│ ├── patch_projects_438659f0.json
│ ├── patch_ssh-keys_084a5dec.json
│ ├── patch_storage_f9a8a263.json
│ ├── post__capacity.json
│ ├── post__capacity_metros.json
│ ├── post_devices_e781ae1b_actions.json
│ ├── post_projects.json
│ ├── post_projects_438659f0_devices.json
│ ├── post_projects_438659f0_storage.json
│ ├── post_ssh-keys.json
│ ├── post_storage_f9a8a263_attachments.json
│ ├── post_storage_f9a8a263_clone.json
│ └── post_storage_f9a8a263_snapshots.json
├── test_baseapi.py
├── test_batch.py
├── test_device.py
├── test_email.py
├── test_event.py
├── test_ips.py
├── test_organization.py
├── test_packet.py
├── test_ports.py
├── test_vlan.py
├── test_volume.py
└── test_vpn.py
└── tox.ini
/.coveragerc:
--------------------------------------------------------------------------------
1 | [run]
2 | branch = True
3 | source = packet
4 |
5 | [report]
6 | exclude_lines =
7 | pragma: no cover
8 | def __repr__
9 | if .debug:
10 | raise NotImplementedError
11 | if __name__ == .__main__.:
12 | ignore_errors = True
13 | omit =
14 | .tox/*
15 | test/*
16 | setup.py
17 |
18 |
--------------------------------------------------------------------------------
/.envrc:
--------------------------------------------------------------------------------
1 | which nix &>/dev/null && use nix
2 | unset SOURCE_DATE_EPOCH
3 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: Python package
2 |
3 | on:
4 | - push
5 | - pull_request
6 |
7 | jobs:
8 | lint:
9 | runs-on: ubuntu-latest
10 | strategy:
11 | matrix:
12 | python-version:
13 | - '3.10'
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Set up Python ${{ matrix.python-version }}
18 | uses: actions/setup-python@v2
19 | with:
20 | python-version: ${{ matrix.python-version }}
21 | - id: fmt_and_lint
22 | run: |
23 | pip install black==22.6.0 pylama
24 | black --check --diff .
25 | pylama packet test setup.py
26 |
27 | test:
28 | runs-on: ubuntu-latest
29 | strategy:
30 | matrix:
31 | python-version:
32 | - 2.7
33 | - 3.8
34 | - 3.9
35 | - '3.10'
36 |
37 | steps:
38 | - uses: actions/checkout@v2
39 | - name: Set up Python ${{ matrix.python-version }}
40 | uses: actions/setup-python@v2
41 | with:
42 | python-version: ${{ matrix.python-version }}
43 | - name: Install Dependencies
44 | run: |
45 | python -m pip install --upgrade setuptools pip wheel
46 | pip install tox tox-gh-actions
47 | - name: Test with tox
48 | run: |
49 | tox
50 | find . # TODO: remove this
51 | # - name: Upload coverage.xml
52 | # if: ${{ matrix.python-version == '3.10' }}
53 | # uses: actions/upload-artifact@v2
54 | # with:
55 | # name: tox-gh-actions-coverage
56 | # path: coverage.xml
57 | # if-no-files-found: error
58 | - uses: codecov/codecov-action@v2
59 | if: ${{ matrix.python-version == '3.10' }}
60 | with:
61 | flags: unittests # optional
62 | name: codecov-umbrella # optional
63 | fail_ci_if_error: true # optional (default = false)
64 | verbose: true # optional (default = false)
65 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 | .idea/
9 | .vscode/
10 |
11 | # Distribution / packaging
12 | .Python
13 | env/
14 | build/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | enable/
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | unittest_*.xml
49 | *,cover
50 | .hypothesis/
51 |
52 | # Translations
53 | *.mo
54 | *.pot
55 |
56 | # Django stuff:
57 | *.log
58 | local_settings.py
59 |
60 | # Flask stuff:
61 | instance/
62 | .webassets-cache
63 |
64 | # Scrapy stuff:
65 | .scrapy
66 |
67 | # Sphinx documentation
68 | docs/_build/
69 |
70 | # PyBuilder
71 | target/
72 |
73 | # IPython Notebook
74 | .ipynb_checkpoints
75 |
76 | # pyenv
77 | .python-version
78 |
79 | # celery beat schedule file
80 | celerybeat-schedule
81 |
82 | # dotenv
83 | .env
84 |
85 | # virtualenv
86 | venv/
87 | ENV/
88 |
89 | # Spyder project settings
90 | .spyderproject
91 |
92 | # Rope project settings
93 | .ropeproject
94 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
5 | This project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6 |
7 | ## [1.45.0] - Unreleased
8 | ### Added
9 | ### Changed
10 | ### Fixed
11 |
12 | ## [1.44.3] - 2022-07-27
13 | ### Fixed
14 | - Fix handling non-dict error data in ResponseError #128 #129
15 |
16 | ## [1.44.2] - 2021-11-18
17 | ### Fixed
18 | - Fix logic behind `validate_metro_capacity` #125
19 |
20 | ## [1.44.1] - 2021-09-20
21 | ### Fixed
22 | - Fix metros URL used in Metro API lists #122
23 | - Catch TypeError when HardwareReservations do not have a device assignment #120
24 |
25 | ## [1.44.0] - 2021-05-19
26 | ### Added
27 | - User-Agent header added to client requests #113
28 | - `Metro` class added (https://feedback.equinixmetal.com/changelog/new-metros-feature-live) #110
29 | - Adds `metro` property to `DeviceBatch`, `Device`, `IPAddress`, `VLan` #110
30 | - Adds `metro` to `create_device`, `reserve_ip_address`, `create_vlan`, `create_connection` #110
31 | - Adds `list_metros`, `validate_metro_capacity` to `Manager` #110
32 | - Adds `CODE_OF_CONDUCT.md`, `SUPPORT.md`, `OWNERS.md` #102, #101, #100
33 | - Adds package metadata for `author`, `author_email`, `copyright` #114
34 | ### Changed
35 | - `facility` is now optional in `create_device`, `reserve_ip_address`, `create_vlan`, `create_connection` #110
36 | - CI is using GH Actions instead of Drone #115
37 | ### Fixed
38 | - Handles when IPAddress Facility is null in API responses #117
39 |
40 | ## [1.43.1] - 2020-09-04
41 | ### Fixed
42 | - ResponseError fixed for Python2.7 compatibility
43 |
44 | ## [1.43.0] - 2020-07-14
45 | ### Added
46 | - Support for reinstalling the operating system to a device, including changing the operating system.
47 | - `Manager.create_vlan` now includes a description argument
48 | ### Changed
49 | - `ResponseError` will now be raised when an API call results in an error
50 | ### Fixed
51 | - `Manager.validate_capacity` now considers availability
52 | - `Manager.create_project_ssh_key` will retry when it encounters 404 responses following a successful creation.
53 | - API responses with `{"error":""}` keys were not handled well, and will now be handled just like `{"errors":[""]}` keys.
54 |
55 | ## [1.42.0] - 2020-02-14
56 | ### Added
57 | - Capturing of `available_in` to Plan
58 | - Capturing of `hardware_reservation`, `spot_price_max`, `termination_time`, and `provisioning_percentage` to `Device`
59 | - Support for creating project ssh keys
60 | - Support for passing `custom_data` when creating a device
61 | ### Fixed
62 | - Black not building for CI and thus failing
63 |
64 | ## [1.41.0] - 2019-10-16
65 | ### Added
66 | - Support for retrieval of hardware reservations
67 | - CPR support at device creation
68 |
69 | ## [1.40.0] - 2019-10-14
70 | ### Added
71 | - Integration tests are only run if `PACKET_PYTHON_TEST_ACTUAL_API` envvar is set
72 | - Rescue action and along with test
73 | - Missing SPDX and source encoding meta comments
74 | ### Removed
75 | - Use of Travis CI
76 |
77 | ## [1.39.1] - 2019-09-17
78 | ### Added
79 | - Support for `hardware_reservation_id`
80 |
81 | ## [1.39.0] - 2019-08-26
82 | ### Added
83 | - Support for Organizations, Events, Emails, VLAN, Snapshot Policies, Batches, Ports, VPN and IPs.
84 | - Live tests
85 |
86 | ## [1.38.2] - 2019-05-30
87 | ### Added
88 | - Test fixtures to sdist
89 |
90 | ## [1.38.1] - 2019-05-30
91 | ### Fixed
92 | - Changelog
93 |
94 | ## [1.38.0] - 2019-05-30
95 | ### Added
96 | - Support for python3.7
97 | - `legacy` param to `get_capacity` function
98 | ### Removed
99 | - Support for python3.3
100 | ### Changed
101 | - setup.py no longer converts markdown to reST because pypi now supports markdown, woop.
102 |
103 | ## [1.37.1] - 2018-01-08
104 | ### Fixed
105 | - Version number in setup.py
106 |
107 | ## [1.37.0] - 2018-01-08
108 | ### Added
109 | - Spot Market Support
110 | - Ability to specify ssh keys on device creation
111 |
112 | ## [1.36.0] - 2017-10-16
113 | ### Added
114 | - Better tests using PacketMockManager
115 | - Test on 2.7 and 3.[3-6]
116 | - Changelog
117 |
118 | ### Changed
119 | - Use tox for testing
120 |
121 | ## [1.35] - 2017-08-04
122 | ### Fixed
123 | - Some tests were broken
124 |
125 | ## [1.35]
126 | ### Added
127 | - `public_ipv4_subnet_size`
128 |
129 | ## [1.34] - 2017-08-04
130 | ### Added
131 | - Custom iPXE and `always_pxe` setting
132 | - Volume coloning
133 | - Device Tags
134 |
135 | ### Fixed
136 | - Handling of error messages from api response
137 |
138 | ## [1.33] - 2017-03-15
139 | ### Fixed
140 | - Default payment method
141 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at support@equinixmetal.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU LESSER 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 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include CHANGELOG.md
2 | include README.md
3 | include test/fixtures/*.json
4 |
--------------------------------------------------------------------------------
/OWNERS.md:
--------------------------------------------------------------------------------
1 | # Owners
2 |
3 | This project is governed by [Equinix Metal](https://metal.equinix.com) and benefits from a community of users that collaborate and contribute to its use in Kubernetes on Equinix Metal.
4 |
5 | Members of the Equinix Metal Github organization will strive to triage issues in a timely manner, see [SUPPORT.md](SUPPORT.md) for details.
6 |
7 | See the [packethost/standards glossary](https://github.com/packethost/standards/blob/master/glossary.md#ownersmd) for more details about this file.
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Equinix Metal
2 |
3 | A Python client for the Equinix Metal API.
4 |
5 | [](https://github.com/packethost/packet-python/actions/workflows/test.yml)
6 | [](https://github.com/packethost/standards/blob/main/maintained-statement.md#maintained-statements)
7 |
8 | This repository is [Maintained](https://github.com/packethost/standards/blob/master/maintained-statement.md) meaning that this software is supported by Equinix Metal and its community - available to use in production environments.
9 |
10 | ## Table of Contents
11 |
12 | * [Installation](#installation)
13 | * [Documentation](#documentation)
14 | * [Authentication](#authentication)
15 | * [Examples](#examples)
16 | * [List Projects](#list-projects)
17 | * [List Plans](#list-plans)
18 | * [Creating a Device](#creating-a-device)
19 | * [Checking the Status and Rebooting a Device](#checking-the-status-and-rebooting-a-device)
20 | * [Listing all Devices Limiting to 50 per Page](#listing-all-devices-limiting-to-50-per-page)
21 | * [Updating a Device](#updating-a-device)
22 | * [Deleting a Device](#deleting-a-device)
23 | * [Creating a Device Batch](#creating-a-device-batch)
24 | * [Creating a Volume](#creating-a-volume)
25 | * [Attaching and Detaching a Volume](#attaching-and-detaching-a-volume)
26 | * [Creating and Restoring a Volume Snapshot](#creating-and-restoring-a-volume-snapshot)
27 | * [Listing Project IP Addresses](#listing-project-ip-addresses)
28 | * [Creating a Project for an Organization](#creating-a-project-for-an-organization)
29 | * [Creating a VLAN](#creating-a-vlan)
30 | * [Contributing](#contributing)
31 | * [Copyright](#copyright)
32 | * [Changes](#changes)
33 |
34 | ## Installation
35 |
36 | The Equinix Metal python api library can be installed using pip:
37 |
38 | pip install packet-python
39 |
40 | Package information available here:
41 |
42 | https://pypi.python.org/pypi/packet-python
43 |
44 | ## Documentation
45 |
46 | Full Equinix Metal API documenation is available here:
47 | [https://metal.equinix.com/developers/api/](https://metal.equinix.com/developers/api/)
48 |
49 | ## Authentication
50 |
51 | Provide your credentials when instantiating client:
52 |
53 | ```python
54 | import packet
55 | manager = packet.Manager(auth_token="yourapiauthtoken")
56 | ```
57 |
58 | ## Examples
59 |
60 | ### List Projects
61 |
62 | ```python
63 | import packet
64 | manager = packet.Manager(auth_token="yourapiauthtoken")
65 |
66 | projects = manager.list_projects()
67 | for project in projects:
68 | print(project)
69 | ```
70 |
71 | ### List Plans
72 |
73 | ```python
74 | import packet
75 | manager = packet.Manager(auth_token="yourapiauthtoken")
76 |
77 | plans = manager.list_plans()
78 | for plan in plans:
79 | print(plan)
80 | if 'cpus' in plan.specs:
81 | print(plan.specs['cpus'][0]['count'])
82 | ```
83 |
84 | ### Creating a Device
85 |
86 | ```python
87 | import packet
88 | manager = packet.Manager(auth_token="yourapiauthtoken")
89 |
90 | device = manager.create_device(project_id='project-id',
91 | hostname='node-name-of-your-choice',
92 | plan='baremetal_1', metro='sv',
93 | operating_system='ubuntu_18_04')
94 | print(device)
95 | ```
96 |
97 | ### Checking the Status and Rebooting a Device
98 |
99 | ```python
100 | import packet
101 | manager = packet.Manager(auth_token="yourapiauthtoken")
102 |
103 | device = manager.get_device('device-id')
104 | print(device.state)
105 | device.reboot()
106 | ```
107 |
108 | ### Listing all Devices Limiting to 50 per Page
109 |
110 | _Equinix Metal API defaults to a limit of 10 per page_
111 |
112 | ```python
113 | import packet
114 | manager = packet.Manager(auth_token="yourapiauthtoken")
115 | params = {
116 | 'per_page': 50
117 | }
118 | devices = manager.list_devices(project_id='project_id', params = params)
119 | print(devices)
120 | ```
121 |
122 | ### Updating a Device
123 |
124 | ```python
125 | import packet
126 | manager = packet.Manager(auth_token="yourapiauthtoken")
127 |
128 | device = manager.get_device('device-id')
129 | device.hostname = "test02"
130 | device.description = "new description"
131 |
132 | device.update()
133 | ```
134 |
135 | ### Deleting a Device
136 |
137 | ```python
138 | import packet
139 | manager = packet.Manager(auth_token="yourapiauthtoken")
140 |
141 | device = manager.get_device('device-id')
142 | device.delete()
143 | ```
144 |
145 | ### Creating a Device Batch
146 |
147 | ```python
148 | import packet
149 | manager = packet.Manager(auth_token="yourapiauthtoken")
150 |
151 | batch01 = packet.DeviceBatch({
152 | "hostname": "batch01",
153 | "quantity": 2,
154 | "facility": "ams1",
155 | "operating_system": "centos_7",
156 | "plan": "baremetal_0",
157 | })
158 |
159 | device_batch = manager.create_batch(project_id="project_id", params=[batch01])
160 | print(device_batch)
161 | ```
162 |
163 | ### Creating a Volume
164 |
165 | ```python
166 | import packet
167 | manager = packet.Manager(auth_token="yourapiauthtoken")
168 |
169 | volume = manager.create_volume(project_id="project-id",
170 | description="volume description",
171 | plan="storage_1",
172 | size="100",
173 | facility="ewr1",
174 | snapshot_count=7,
175 | snapshot_frequency="1day")
176 | print(volume)
177 | ```
178 |
179 | ### Attaching and Detaching a Volume
180 |
181 | ```python
182 | import packet
183 | import time
184 |
185 | manager = packet.Manager(auth_token="yourapiauthtoken")
186 | volume = manager.get_volume("volume_id")
187 |
188 | volume.attach("device_id")
189 |
190 | while True:
191 | if manager.get_device("device_id").state == "active":
192 | break
193 | time.sleep(2)
194 |
195 | volume.detach()
196 | ```
197 |
198 | ## Creating and Restoring a Volume Snapshot
199 |
200 | ```python
201 | import packet
202 | import time
203 |
204 | manager = packet.Manager(auth_token="yourapiauthtoken")
205 |
206 | volume = manager.get_volume("volume_id")
207 | volume.create_snapshot()
208 |
209 | while True:
210 | if manager.get_volume(volume.id).state == "active":
211 | break
212 | time.sleep(2)
213 |
214 | snapshots = manager.get_snapshots(volume.id)
215 | volume.restore(snapshots[0].timestamp)
216 | ```
217 |
218 | ### Listing Project IP Addresses
219 |
220 | ```python
221 | import packet
222 | manager = packet.Manager(auth_token="yourapiauthtoken")
223 |
224 | ips = manager.list_project_ips("project_id")
225 | for ip in ips:
226 | print(ip.address)
227 | ```
228 |
229 | ### Creating a Project for an Organization
230 |
231 | ```python
232 | import packet
233 | manager = packet.Manager(auth_token="yourapiauthtoken")
234 |
235 | project = manager.create_organization_project(
236 | org_id="organization_id",
237 | name="Integration Tests",
238 | customdata={"tag": "QA"}
239 | )
240 | print(project)
241 | ```
242 |
243 | ### Creating a VLAN
244 |
245 | ```python
246 | import packet
247 | manager = packet.Manager(auth_token="yourapiauthtoken")
248 |
249 | vlan = manager.create_vlan(project_id="project_id", facility="ewr1")
250 | print(vlan)
251 | ```
252 |
253 | ## Contributing
254 |
255 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
256 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
257 | * Fork the project.
258 | * Start a feature/bugfix branch.
259 | * Commit and push until you are happy with your contribution.
260 | * You can test your changes with the `tox`, which is what GitHub Actions use to check builds.
261 |
262 | ## Credits
263 |
264 | CargoCulted with much gratitude from:
265 | https://github.com/koalalorenzo/python-digitalocean
266 |
267 | ## Changes
268 |
269 | See the [Changelog](CHANGELOG.md) for further details.
270 |
--------------------------------------------------------------------------------
/RELEASE.md:
--------------------------------------------------------------------------------
1 | # Release Instructions
2 |
3 | These build and release instructions are intended for the maintainers and future maintainers of this project.
4 |
5 | ## Preparing a new version
6 |
7 | * a version update in `packet/__init__.py`
8 | * Update CHANGELOG.md with the new release notes
9 | * Add a stub for the next set of Unreleased changes
10 | * Create a PR with these changes
11 | * Merge
12 |
13 | ## Tagging and Building
14 |
15 | Pull down the merged changes:
16 |
17 | ```bash
18 | git fetch origin
19 | git checkout origin/master
20 | ```
21 |
22 | Tag the commit:
23 |
24 | ```bash
25 | git tag -a vAA.BB.CC origin/master -m vAA.BB.CC # use -s if gpg is available
26 | ```
27 |
28 | Build the package using `setuptools`:
29 |
30 | ```bash
31 | python setup.py sdist bdist_wheel
32 | ```
33 |
34 | ## Publishing
35 |
36 | Make sure you have `~/.pypirc` correctly populated, as of today should look something like:
37 |
38 | ```
39 | [distutils]
40 | index-servers =
41 | pypi
42 | testpypi
43 |
44 | [pypi]
45 | username: username-here
46 | password: password-here
47 |
48 | [testpypi]
49 | repository: https://test.pypi.org/legacy/
50 | username: username-here (not necessarily same as real pypi)
51 | password: password-here (not necessarily same as real pypi)
52 | ```
53 |
54 | If you are using a token, the username is `__token__`. Make sure you have been added to the
55 | [project](https://pypi.org/manage/project/packet-python/collaboration/) and the
56 | [test project](https://test.pypi.org/manage/project/packet-python/collaboration/).
57 |
58 | Then upload using twine to testpypi first:
59 |
60 | ```bash
61 | twine upload -r testpypi dist/*
62 | ```
63 |
64 | If everything looks good, push the tag to GH, and then push to the real
65 | pypi:
66 |
67 | ```bash
68 | git push origin --tags vAA.BB.CC
69 | twine upload dist/*
70 | ```
71 |
72 | Congratulations, you published `packet-python`, :raised_hands:!
73 |
74 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | If you require support, please email support@equinixmetal.com, visit the Equinix Metal IRC channel (#equinixmetal on freenode), subscribe to the [Equinix Metal Community Slack](https://slack.equinixmetal.com/) channel or post an issue within this repository.
2 |
--------------------------------------------------------------------------------
/packet/BGPConfig.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class BGPConfig:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.status = data.get("status")
9 | self.deployment_type = data.get("deployment_type")
10 | self.asn = data.get("asn")
11 | self.md5 = data.get("md5")
12 | self.route_object = data.get("route_object")
13 | self.max_prefix = data.get("max_prefix")
14 | self.created_at = data.get("created_at")
15 | self.requested_at = data.get("requested_at")
16 | self.project = data.get("project")
17 | self.sessions = data.get("sessions")
18 | self.ranges = data.get("ranges")
19 | self.href = data.get("href")
20 |
21 | def __str__(self):
22 | return "%s" % self.id
23 |
24 | def __repr__(self):
25 | return "{}: {}".format(self.__class__.__name__, self.id)
26 |
--------------------------------------------------------------------------------
/packet/BGPSession.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class BGPSession:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.status = data.get("status")
9 | self.learned_routes = data.get("learned_routes")
10 | self.switch_name = data.get("switch_name")
11 | self.default_route = data.get("default_route")
12 | self.created_at = data.get("created_at")
13 | self.updated_at = data.get("updated_at")
14 | self.device = data.get("device")
15 | self.address_family = data.get("address_family")
16 | self.href = data.get("href")
17 |
18 | def __str__(self):
19 | return "%s" % self.id
20 |
21 | def __repr__(self):
22 | return "{}: {}".format(self.__class__.__name__, self.id)
23 |
--------------------------------------------------------------------------------
/packet/Batch.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Batch:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.error_messages = data.get("error_messages")
9 | self.quantity = data.get("quantity")
10 | self.state = data.get("state")
11 | self.created_at = data.get("created_at")
12 | self.updated_at = data.get("updated_at")
13 | self.devices = data.get("devices")
14 | self.project = data.get("project")
15 | self.state = data.get("state")
16 | self.error_messages = data.get("error_messages")
17 |
18 | def __str__(self):
19 | return "%s" % self.id
20 |
21 | def __repr__(self):
22 | return "{}: {}".format(self.__class__.__name__, self.id)
23 |
--------------------------------------------------------------------------------
/packet/Device.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 | # from .Volume import Volume
4 | from .OperatingSystem import OperatingSystem
5 |
6 |
7 | class Device:
8 | def __init__(self, data, manager):
9 | self.manager = manager
10 |
11 | self.id = data.get("id")
12 | self.short_id = data.get("short_id")
13 | self.hostname = data.get("hostname")
14 | self.description = data.get("description")
15 | self.state = data.get("state")
16 | self.tags = data.get("tags")
17 | self.image_url = data.get("image_url")
18 | self.billing_cycle = data.get("billing_cycle")
19 | self.user = data.get("user")
20 | self.iqn = data.get("iqn")
21 | self.locked = data.get("locked")
22 | self.bonding_mode = data.get("bonding_mode")
23 | self.created_at = data.get("created_at")
24 | self.updated_at = data.get("updated_at")
25 | self.ipxe_script_url = data.get("ipxe_script_url")
26 | self.always_pxe = data.get("always_pxe", False)
27 | self.storage = data.get("storage")
28 | self.customdata = data.get("customdata")
29 | self.operating_system = OperatingSystem(data["operating_system"])
30 | self.facility = data.get("facility")
31 | self.metro = data.get("metro")
32 | self.project = data.get("project")
33 | self.ssh_keys = data.get("ssh_keys")
34 | self.project_lite = data.get("project_lite")
35 | self.volumes = data.get("volumes")
36 | self.ip_addresses = data.get("ip_addresses")
37 | self.plan = data.get("plan")
38 | self.userdata = data.get("userdata")
39 | self.switch_uuid = data.get("switch_uuid")
40 | self.network_ports = data.get("network_ports")
41 | self.href = data.get("href")
42 | self.spot_instance = data.get("spot_instance", False)
43 | self.hardware_reservation_id = data.get("hardware_reservation_id")
44 | self.spot_price_max = data.get("spot_price_max")
45 | self.termination_time = data.get("termination_time")
46 | self.root_password = data.get("root_password")
47 | self.provisioning_percentage = data.get("provisioning_percentage")
48 |
49 | def update(self):
50 | params = {
51 | "hostname": self.hostname,
52 | "locked": self.locked,
53 | "tags": self.tags,
54 | "description": self.description,
55 | "billing_cycle": self.billing_cycle,
56 | "userdata": self.userdata,
57 | "always_pxe": self.always_pxe,
58 | "ipxe_script_url": self.ipxe_script_url,
59 | "spot_instance": self.spot_instance,
60 | "customdata": self.customdata,
61 | }
62 |
63 | return self.manager.call_api(
64 | "devices/%s" % self.id, type="PATCH", params=params
65 | )
66 |
67 | def delete(self):
68 | return self.manager.call_api("devices/%s" % self.id, type="DELETE")
69 |
70 | def reinstall(self, operating_system=None, ipxe_script_url=None):
71 | params = {"type": "reinstall"}
72 | if operating_system is not None:
73 | params["operating_system"] = operating_system
74 | if ipxe_script_url is not None:
75 | params["operating_system"] = "custom_ipxe"
76 | params["ipxe_script_url"] = ipxe_script_url
77 |
78 | return self.manager.call_api(
79 | "devices/%s/actions" % self.id, type="POST", params=params
80 | )
81 |
82 | def power_off(self):
83 | params = {"type": "power_off"}
84 | return self.manager.call_api(
85 | "devices/%s/actions" % self.id, type="POST", params=params
86 | )
87 |
88 | def power_on(self):
89 | params = {"type": "power_on"}
90 | return self.manager.call_api(
91 | "devices/%s/actions" % self.id, type="POST", params=params
92 | )
93 |
94 | def reboot(self):
95 | params = {"type": "reboot"}
96 | return self.manager.call_api(
97 | "devices/%s/actions" % self.id, type="POST", params=params
98 | )
99 |
100 | def rescue(self):
101 | params = {"type": "rescue"}
102 | return self.manager.call_api(
103 | "devices/%s/actions" % self.id, type="POST", params=params
104 | )
105 |
106 | def ips(self):
107 | return self.manager.list_device_ips(self.id)
108 |
109 | def __str__(self):
110 | return "%s" % self.hostname
111 |
112 | def __repr__(self):
113 | return "{}: {}".format(self.__class__.__name__, self.id)
114 |
115 | def __getitem__(self, item):
116 | return getattr(self, item)
117 |
--------------------------------------------------------------------------------
/packet/DeviceBatch.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class DeviceBatch:
6 | def __init__(self, data):
7 | self.hostname = data.get("hostname")
8 | self.plan = data.get("plan")
9 | self.operating_system = data.get("operating_system")
10 | self.facility = data.get("facility")
11 | self.metro = data.get("metro")
12 | self.quantity = data.get("quantity")
13 |
14 | def __str__(self):
15 | return "%s" % self.hostname
16 |
--------------------------------------------------------------------------------
/packet/Email.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Email:
6 | def __init__(self, data, manager):
7 | self.manager = manager
8 |
9 | self.id = data.get("id")
10 | self.address = data.get("address")
11 | self.default = data.get("default")
12 | self.verified = data.get("verified")
13 |
14 | def update(self):
15 | params = {"address": self.address, "default": self.default}
16 |
17 | return self.manager.call_api("emails/%s" % self.id, type="PATCH", params=params)
18 |
19 | def delete(self):
20 | return self.manager.call_api("emails/%s" % self.id, type="DELETE")
21 |
22 | def __str__(self):
23 | return "%s" % self.address
24 |
25 | def __repr__(self):
26 | return "{}: {}".format(self.__class__.__name__, self.id)
27 |
--------------------------------------------------------------------------------
/packet/Event.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Event:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.type = data.get("type")
9 | self.body = data.get("body")
10 | self.state = data.get("state")
11 | self.created_at = data.get("created_at")
12 | self.modified_by = data.get("modified_by")
13 | self.ip = data.get("ip")
14 | self.interpolated = data.get("interpolated")
15 |
16 | def __str__(self):
17 | return "%s" % self.interpolated
18 |
19 | def __repr__(self):
20 | return "{}: {}".format(self.__class__.__name__, self.id)
21 |
--------------------------------------------------------------------------------
/packet/Facility.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Facility:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.code = data.get("code")
9 | self.name = data.get("name")
10 | self.features = data.get("features")
11 | self.address = data.get("address")
12 |
13 | def __str__(self):
14 | return "%s" % self.code
15 |
16 | def __repr__(self):
17 | return "{}: {}".format(self.__class__.__name__, self.id)
18 |
--------------------------------------------------------------------------------
/packet/HardwareReservation.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | from .Plan import Plan
5 | from .Project import Project
6 | from .Device import Device
7 |
8 |
9 | class HardwareReservation:
10 | def __init__(self, data, manager):
11 | self.manager = manager
12 |
13 | self.id = data.get("id")
14 | self.created_at = data.get("created_at")
15 | self.billing_cycle = data.get("billing_cycle")
16 | self.created_at = data.get("created_at")
17 | self.short_id = data.get("short_id")
18 | self.intervals = data.get("intervals")
19 | self.current_period = data.get("current_period")
20 | self.started_at = data.get("started_at")
21 | self.custom_rate = data.get("custom_rate")
22 | self.remove_at = data.get("remove_at")
23 | self.project = data.get("project")
24 | # self.facility = data.get("facility")
25 | self.device = data.get("device")
26 | self.provisionable = data.get("provisionable")
27 | self.spare = data.get("spare")
28 | self.need_of_service = data.get("need_of_service")
29 | self.plan = Plan(data.get("plan"))
30 | self.switch_uuid = data.get("switch_uuid")
31 |
32 | try:
33 | project_data = self.manager.call_api(data["project"]["href"], type="GET")
34 | self.project = Project(project_data, self.manager)
35 | except (KeyError, IndexError):
36 | self.attached_to = None
37 |
38 | # endpoint is not working yet
39 | # try:
40 | # facility_data = self.manager.call_api(
41 | # data["facility"]["href"], type="GET"
42 | # )
43 | # self.project = Facility(facility_data, self.manager)
44 | # except (KeyError, IndexError):
45 | # self.attached_to = None
46 |
47 | try:
48 | device_data = self.manager.call_api(data["device"]["href"], type="GET")
49 | self.device = Device(device_data, self.manager)
50 | except (KeyError, IndexError, TypeError):
51 | self.attached_to = None
52 |
53 | def __str__(self):
54 | return "%s" % self.id
55 |
56 | def __repr__(self):
57 | return "{}: {}".format(self.__class__.__name__, self.id)
58 |
59 | def __getitem__(self, item):
60 | return getattr(self, item)
61 |
--------------------------------------------------------------------------------
/packet/IPAddress.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | from .Facility import Facility
5 | from .Metro import Metro
6 |
7 |
8 | class IPAddress:
9 | def __init__(self, data, manager):
10 | self.manager = manager
11 |
12 | self.id = data.get("id")
13 | self.address_family = data.get("address_family")
14 | self.netmask = data.get("netmask")
15 | self.created_at = data.get("created_at")
16 | self.details = data.get("details")
17 | self.tags = data.get("tags")
18 | self.public = data.get("public")
19 | self.cidr = data.get("cidr")
20 | self.management = data.get("management")
21 | self.enabled = data.get("enabled")
22 | self.global_ip = data.get("global_ip")
23 | self.customdata = data.get("customdata")
24 | self.project = data.get("project")
25 | self.project_lite = data.get("project_lite")
26 | self.details = data.get("details")
27 | self.assigned_to = data.get("assigned_to")
28 | self.interface = data.get("interface")
29 | self.network = data.get("network")
30 | self.address = data.get("address")
31 | self.gateway = data.get("gateway")
32 |
33 | facility = data.get("facility")
34 | self.facility = Facility(facility) if facility else None
35 | metro = data.get("metro")
36 | self.metro = Metro(metro) if metro else None
37 |
38 | def delete(self):
39 | return self.manager.call_api("ips/%s" % self.id, type="DELETE")
40 |
41 | def __str__(self):
42 | return "%s" % self.code
43 |
44 | def __repr__(self):
45 | return "{}: {}".format(self.__class__.__name__, self.id)
46 |
--------------------------------------------------------------------------------
/packet/Manager.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 | from packet.Vlan import Vlan
4 | from .baseapi import BaseAPI
5 | from .baseapi import Error as PacketError
6 | from .baseapi import ResponseError
7 | from .Batch import Batch
8 | from .Plan import Plan
9 | from .Device import Device
10 | from .SSHKey import SSHKey
11 | from .Project import Project
12 | from .Facility import Facility
13 | from .Metro import Metro
14 | from .OperatingSystem import OperatingSystem
15 | from .Volume import Volume
16 | from .BGPConfig import BGPConfig
17 | from .BGPSession import BGPSession
18 | from .IPAddress import IPAddress
19 | from .HardwareReservation import HardwareReservation
20 | from .Snapshot import Snapshot
21 | from .Organization import Organization
22 | from .Email import Email
23 | from .Event import Event
24 | from .Provider import Provider
25 |
26 |
27 | class Manager(BaseAPI):
28 | def __init__(self, auth_token, consumer_token=None):
29 | super(Manager, self).__init__(auth_token, consumer_token)
30 |
31 | def call_api(self, method, type="GET", params=None):
32 | return super(Manager, self).call_api(method, type, params)
33 |
34 | def get_user(self):
35 | return self.call_api("user")
36 |
37 | def list_facilities(self, params={}):
38 | data = self.call_api("facilities", params=params)
39 | facilities = list()
40 | for jsoned in data["facilities"]:
41 | facility = Facility(jsoned)
42 | facilities.append(facility)
43 | return facilities
44 |
45 | def list_metros(self, params={}):
46 | data = self.call_api("locations/metros", params=params)
47 | metros = list()
48 | for jsoned in data["metros"]:
49 | metro = Metro(jsoned)
50 | metros.append(metro)
51 | return metros
52 |
53 | def list_plans(self, params={}):
54 | data = self.call_api("plans", params=params)
55 | plans = list()
56 | for jsoned in data["plans"]:
57 | plan = Plan(jsoned)
58 | plans.append(plan)
59 | return plans
60 |
61 | def list_operating_systems(self, params={}):
62 | data = self.call_api("operating-systems", params=params)
63 | oss = list()
64 | for jsoned in data["operating_systems"]:
65 | os = OperatingSystem(jsoned)
66 | oss.append(os)
67 | return oss
68 |
69 | def list_projects(self, params={}):
70 | data = self.call_api("projects", params=params)
71 | self.total = data["meta"]["total"]
72 | projects = list()
73 | for jsoned in data["projects"]:
74 | project = Project(jsoned, self)
75 | projects.append(project)
76 | return projects
77 |
78 | def get_project(self, project_id):
79 | data = self.call_api("projects/%s" % project_id)
80 | return Project(data, self)
81 |
82 | def create_project(self, name):
83 | params = {"name": name}
84 | data = self.call_api("projects", type="POST", params=params)
85 | return Project(data, self)
86 |
87 | def list_hardware_reservations(self, project_id, params={}):
88 | data = self.call_api(
89 | "projects/%s/hardware-reservations" % project_id, params=params
90 | )
91 | hardware_reservations = list()
92 | for jsoned in data["hardware_reservations"]:
93 | hardware_reservation = HardwareReservation(jsoned, self)
94 | hardware_reservations.append(hardware_reservation)
95 | return hardware_reservations
96 |
97 | def get_hardware_reservation(self, hardware_reservation_id):
98 | data = self.call_api("hardware-reservations/%s" % hardware_reservation_id)
99 | return HardwareReservation(data, self)
100 |
101 | def list_devices(self, project_id, params={}):
102 | data = self.call_api("projects/%s/devices" % project_id, params=params)
103 | devices = list()
104 | for jsoned in data["devices"]:
105 | device = Device(jsoned, self)
106 | devices.append(device)
107 | return devices
108 |
109 | def list_all_devices(self, project_id):
110 | raw_devices = list()
111 | page = 1
112 | while True:
113 | paginate = {"page": page}
114 | data = self.call_api("projects/%s/devices" % project_id, params=paginate)
115 | next = self.meta["next"]
116 | raw_devices.extend(data["devices"])
117 | if next is None:
118 | break
119 | else:
120 | page += 1
121 |
122 | all_devices = list()
123 | for raw_device in raw_devices:
124 | device = Device(raw_device, self)
125 | all_devices.append(device)
126 | return all_devices
127 |
128 | def create_device(
129 | self,
130 | project_id,
131 | hostname,
132 | plan,
133 | facility="",
134 | operating_system="",
135 | always_pxe=False,
136 | billing_cycle="hourly",
137 | features={},
138 | ipxe_script_url="",
139 | locked=False,
140 | project_ssh_keys=[],
141 | public_ipv4_subnet_size=31,
142 | spot_instance=False,
143 | spot_price_max=-1,
144 | tags={},
145 | termination_time=None,
146 | user_ssh_keys=[],
147 | userdata="",
148 | hardware_reservation_id="",
149 | storage={},
150 | customdata={},
151 | metro="",
152 | ):
153 |
154 | params = {
155 | "billing_cycle": billing_cycle,
156 | "features": features,
157 | "hostname": hostname,
158 | "locked": locked,
159 | "operating_system": operating_system,
160 | "plan": plan,
161 | "project_id": project_id,
162 | "public_ipv4_subnet_size": public_ipv4_subnet_size,
163 | "project_ssh_keys": project_ssh_keys,
164 | "tags": tags,
165 | "user_ssh_keys": user_ssh_keys,
166 | "userdata": userdata,
167 | }
168 |
169 | if metro != "":
170 | params["metro"] = metro
171 | if facility != "":
172 | params["facility"] = facility
173 | if hardware_reservation_id != "":
174 | params["hardware_reservation_id"] = hardware_reservation_id
175 | if storage:
176 | params["storage"] = storage
177 | if customdata:
178 | params["customdata"] = customdata
179 | if ipxe_script_url != "":
180 | params["always_pxe"] = always_pxe
181 | params["ipxe_script_url"] = ipxe_script_url
182 | params["operating_system"] = "custom_ipxe"
183 | if spot_instance:
184 | params["spot_instance"] = spot_instance
185 | params["spot_price_max"] = spot_price_max
186 | params["termination_time"] = termination_time
187 | data = self.call_api(
188 | "projects/%s/devices" % project_id, type="POST", params=params
189 | )
190 | return Device(data, self)
191 |
192 | def get_device(self, device_id):
193 | data = self.call_api("devices/%s" % device_id)
194 | return Device(data, self)
195 |
196 | def list_ssh_keys(self, params={}):
197 | data = self.call_api("ssh-keys", params=params)
198 | ssh_keys = list()
199 | for jsoned in data["ssh_keys"]:
200 | ssh_key = SSHKey(jsoned, self)
201 | ssh_keys.append(ssh_key)
202 | return ssh_keys
203 |
204 | def get_ssh_key(self, ssh_key_id):
205 | data = self.call_api("ssh-keys/%s" % ssh_key_id)
206 | return SSHKey(data, self)
207 |
208 | def create_ssh_key(self, label, public_key):
209 | params = {"key": public_key, "label": label}
210 | data = self.call_api("ssh-keys", type="POST", params=params)
211 | return SSHKey(data, self)
212 |
213 | def create_project_ssh_key(self, project_id, label, public_key):
214 | """
215 | Successfully creating an SSH key with a Project API Token results
216 | in a 404 from the API. If we get a 404, we try the request again.
217 |
218 | If the request actually failed with a 404, we will get another 404
219 | which we raise.
220 |
221 | If the request actually succeeded, we will get a 422. In this case,
222 | we will try to list all the keys and find the SSHKey we just
223 | received.
224 |
225 | Customer Report Reference: TUVD-0107-UIKB
226 | """
227 |
228 | def issue_req():
229 | try:
230 | params = {"key": public_key, "label": label}
231 | data = self.call_api(
232 | "projects/%s/ssh-keys" % project_id, type="POST", params=params
233 | )
234 | return SSHKey(data, self)
235 | except ResponseError as e:
236 | if e.response.status_code == 422:
237 | # Try to pluck the SSH key from the listing API
238 | keys = [
239 | key
240 | for key in self.list_ssh_keys()
241 | if key.key.strip() == public_key.strip()
242 | ]
243 | if len(keys) == 1:
244 | return keys.pop()
245 | raise
246 |
247 | try:
248 | return issue_req()
249 | except ResponseError as e:
250 | if e.response.status_code == 404:
251 | return issue_req()
252 | else:
253 | raise
254 |
255 | def list_volumes(self, project_id, params={}):
256 | params["include"] = "facility,attachments.device"
257 | data = self.call_api("projects/%s/storage" % project_id, params=params)
258 | volumes = list()
259 | for jsoned in data["volumes"]:
260 | volume = Volume(jsoned, self)
261 | volumes.append(volume)
262 | return volumes
263 |
264 | def create_volume(
265 | self,
266 | project_id,
267 | description,
268 | plan,
269 | size,
270 | facility,
271 | snapshot_count=0,
272 | snapshot_frequency=None,
273 | ):
274 | params = {
275 | "description": description,
276 | "plan": plan,
277 | "size": size,
278 | "facility": facility,
279 | }
280 |
281 | if snapshot_count > 0 and snapshot_frequency is not None:
282 | params["snapshot_policies"] = {
283 | "snapshot_count": snapshot_count,
284 | "snapshot_frequency": snapshot_frequency,
285 | }
286 |
287 | data = self.call_api(
288 | "projects/%s/storage?include=facility" % project_id,
289 | type="POST",
290 | params=params,
291 | )
292 |
293 | return Volume(data, self)
294 |
295 | def get_volume(self, volume_id):
296 | params = {"include": "facility,attachments.device"}
297 | data = self.call_api("storage/%s" % volume_id, params=params)
298 | return Volume(data, self)
299 |
300 | def get_capacity(self, legacy=None):
301 | """Get capacity of all facilities.
302 |
303 | :param legacy: Indicate set of server types to include in response
304 |
305 | Validation of `legacy` is left to the packet api to avoid going out
306 | of date if any new value is introduced.
307 | The currently known values are:
308 | - only (current default, will be switched "soon")
309 | - include
310 | - exclude (soon to be default)
311 | """
312 | params = None
313 | if legacy:
314 | params = {"legacy": legacy}
315 |
316 | return self.call_api("/capacity", params=params)["capacity"]
317 |
318 | # servers is a list of tuples of facility, plan, and quantity.
319 | def validate_capacity(self, servers):
320 | params = {"servers": []}
321 | for server in servers:
322 | params["servers"].append(
323 | {"facility": server[0], "plan": server[1], "quantity": server[2]}
324 | )
325 |
326 | try:
327 | data = self.call_api("/capacity", "POST", params)
328 | return all(s["available"] for s in data["servers"])
329 | except PacketError as e: # pragma: no cover
330 | if e.args[0] == "Error 503: Service Unavailable":
331 | return False
332 | else:
333 | raise e
334 |
335 | # servers is a list of tuples of metro, plan, and quantity.
336 | def validate_metro_capacity(self, servers):
337 | params = {"servers": []}
338 | for server in servers:
339 | params["servers"].append(
340 | {"metro": server[0], "plan": server[1], "quantity": server[2]}
341 | )
342 |
343 | try:
344 | data = self.call_api("/capacity/metros", "POST", params)
345 | return all(s["available"] for s in data["servers"])
346 | except PacketError as e: # pragma: no cover
347 | if e.args[0] == "Error 503: Service Unavailable":
348 | return False
349 | else:
350 | raise e
351 |
352 | def get_spot_market_prices(self, params={}):
353 | data = self.call_api("/market/spot/prices", params=params)
354 | return data["spot_market_prices"]
355 |
356 | # BGP Config
357 | def get_bgp_config(self, project_id):
358 | data = self.call_api("projects/%s/bgp-config" % project_id)
359 | return BGPConfig(data)
360 |
361 | def enable_project_bgp_config(
362 | self, project_id, asn, deployment_type, md5=None, use_case=None
363 | ):
364 | params = {
365 | "deployment_type": deployment_type,
366 | "asn": asn,
367 | "md5": md5,
368 | "use_case": use_case,
369 | }
370 | self.call_api(
371 | "/projects/%s/bgp-configs" % project_id, type="POST", params=params
372 | )
373 |
374 | # BGP Session
375 | def get_bgp_sessions(self, device_id, params={}):
376 | data = self.call_api(
377 | "/devices/%s/bgp/sessions" % device_id, type="GET", params=params
378 | )
379 | bgp_sessions = list()
380 | for jsoned in data["bgp_sessions"]:
381 | bpg_session = BGPSession(jsoned)
382 | bgp_sessions.append(bpg_session)
383 | return bgp_sessions
384 |
385 | def create_bgp_session(self, device_id, address_family):
386 | data = self.call_api(
387 | "/devices/%s/bgp/sessions" % device_id,
388 | type="POST",
389 | params={"address_family": address_family},
390 | )
391 | return BGPSession(data)
392 |
393 | # IP operations
394 | def list_device_ips(self, device_id):
395 | data = self.call_api("devices/%s/ips" % device_id, type="GET")
396 | ips = list()
397 | for jsoned in data["ip_addresses"]:
398 | ip = IPAddress(jsoned, self)
399 | ips.append(ip)
400 | return ips
401 |
402 | def get_ip(self, ip_id):
403 | data = self.call_api("ips/%s" % ip_id)
404 | return IPAddress(data, self)
405 |
406 | def delete_ip(self, ip_id):
407 | self.call_api("ips/%s" % ip_id, type="DELETE")
408 |
409 | def list_project_ips(self, project_id, params={}):
410 | data = self.call_api("projects/%s/ips" % project_id, type="GET", params=params)
411 | ips = list()
412 | for jsoned in data["ip_addresses"]:
413 | ip = IPAddress(jsoned, self)
414 | ips.append(ip)
415 | return ips
416 |
417 | def get_available_ip_subnets(self, ip_id, cidr):
418 | data = self.call_api(
419 | "/ips/%s/available" % ip_id, type="GET", params="cidr=%s" % cidr
420 | )
421 | return data
422 |
423 | def create_device_ip(self, device_id, address, manageable=True, customdata=None):
424 | params = {
425 | "address": address,
426 | "manageable": manageable,
427 | "customdata": customdata,
428 | }
429 |
430 | data = self.call_api("/devices/%s/ips" % device_id, params=params, type="POST")
431 | return IPAddress(data, self)
432 |
433 | def reserve_ip_address(
434 | self,
435 | project_id,
436 | type,
437 | quantity,
438 | facility="",
439 | details=None,
440 | comments=None,
441 | tags=list(),
442 | metro="",
443 | ):
444 | request = {
445 | "type": type,
446 | "quantity": quantity,
447 | "details": details,
448 | "comments": comments,
449 | "tags": tags,
450 | }
451 |
452 | if facility != "":
453 | request["facility"] = facility
454 | if metro != "":
455 | request["metro"] = metro
456 | data = self.call_api(
457 | "/projects/%s/ips" % project_id, params=request, type="POST"
458 | )
459 | return IPAddress(data, self)
460 |
461 | # Batches
462 | def create_batch(self, project_id, params):
463 | param = {"batches": params}
464 | data = self.call_api(
465 | "/projects/%s/devices/batch" % project_id, type="POST", params=param
466 | )
467 | batches = list()
468 | for b in data["batches"]:
469 | batch = Batch(b)
470 | batches.append(batch)
471 | return batches
472 |
473 | def list_batches(self, project_id, params=None):
474 | data = self.call_api(
475 | "/projects/%s/batches" % project_id, type="GET", params=params
476 | )
477 | batches = list()
478 | for b in data["batches"]:
479 | batch = Batch(b)
480 | batches.append(batch)
481 | return batches
482 |
483 | def delete_batch(self, batch_id, remove_associated_instances=False):
484 | self.call_api(
485 | "/batches/%s" % batch_id, type="DELETE", params=remove_associated_instances
486 | )
487 |
488 | # Snapshots
489 | def get_snapshots(self, volume_id, params=None):
490 | data = self.call_api(
491 | "storage/%s/snapshots" % volume_id, type="GET", params=params
492 | )
493 | snapshots = list()
494 | for ss in data["snapshots"]:
495 | snapshot = Snapshot(ss)
496 | snapshots.append(snapshot)
497 |
498 | return snapshots
499 |
500 | def restore_volume(self, volume_id, restore_point):
501 | params = {"restore_point": restore_point}
502 | self.call_api("storage/%s/restore" % volume_id, type="POST", params=params)
503 |
504 | # Organization
505 | def list_organizations(self, params=None):
506 | data = self.call_api("organizations", type="GET", params=params)
507 | orgs = list()
508 | for org in data["organizations"]:
509 | o = Organization(org)
510 | orgs.append(o)
511 |
512 | return orgs
513 |
514 | def get_organization(self, org_id, params=None):
515 | data = self.call_api("organizations/%s" % org_id, type="GET", params=params)
516 | return Organization(data)
517 |
518 | def list_organization_projects(self, org_id, params=None):
519 | data = self.call_api(
520 | "organizations/%s/projects" % org_id, type="GET", params=params
521 | )
522 | projects = list()
523 | for p in data["projects"]:
524 | projects.append(Project(p, self))
525 |
526 | return projects
527 |
528 | def list_organization_devices(self, org_id, params=None):
529 | data = self.call_api(
530 | "organizations/%s/devices" % org_id, type="GET", params=params
531 | )
532 | devices = list()
533 | for d in data["devices"]:
534 | devices.append(Device(d, self))
535 |
536 | return devices
537 |
538 | def create_organization_project(
539 | self, org_id, name, payment_method_id=None, customdata=None
540 | ):
541 | params = {
542 | "name": name,
543 | "payment_method_id": payment_method_id,
544 | "customdata": customdata,
545 | }
546 | data = self.call_api(
547 | "organizations/%s/projects" % org_id, type="POST", params=params
548 | )
549 | return Project(data, self)
550 |
551 | # Email
552 | def add_email(self, address, default=False):
553 | params = {"address": address, "default": default}
554 | data = self.call_api("emails", type="POST", params=params)
555 | return Email(data, self)
556 |
557 | def get_email(self, email_id):
558 | data = self.call_api("emails/%s" % email_id)
559 | return Email(data, self)
560 |
561 | # Event
562 | def list_events(self, params=None):
563 | data = self.call_api("events", type="GET", params=params)
564 | events = list()
565 | for e in data["events"]:
566 | events.append(Event(e))
567 |
568 | return events
569 |
570 | def get_event(self, event_id):
571 | data = self.call_api("events/%s" % event_id)
572 | return Event(data)
573 |
574 | def list_device_events(self, device_id, params=None):
575 | data = self.call_api("devices/%s/events" % device_id, type="GET", params=params)
576 | events = list()
577 | for e in data["events"]:
578 | events.append(Event(e))
579 |
580 | return events
581 |
582 | def list_project_events(self, project_id, params=None):
583 | data = self.call_api(
584 | "projects/%s/events" % project_id, type="GET", params=params
585 | )
586 | events = list()
587 | for e in data["events"]:
588 | events.append(Event(e))
589 |
590 | return events
591 |
592 | def get_volume_events(self, volume_id, params=None):
593 | data = self.call_api("volumes/%s/events" % volume_id, type="GET", params=params)
594 | events = list()
595 | for e in data["events"]:
596 | events.append(Event(e))
597 |
598 | return events
599 |
600 | # vlan operations
601 | def list_vlans(self, project_id, params=None):
602 | data = self.call_api(
603 | "projects/%s/virtual-networks" % project_id, type="GET", params=params
604 | )
605 | vlans = list()
606 | for vlan in data["virtual_networks"]:
607 | vlans.append(Vlan(vlan, self))
608 |
609 | return vlans
610 |
611 | def create_vlan(
612 | self, project_id, facility="", vxlan=None, vlan=None, description=None, metro=""
613 | ):
614 | params = {
615 | "project_id": project_id,
616 | "vxlan": vxlan,
617 | "vlan": vlan,
618 | "description": description,
619 | }
620 | if facility != "":
621 | params["facility"] = facility
622 | if metro != "":
623 | params["metro"] = metro
624 | data = self.call_api(
625 | "projects/%s/virtual-networks" % project_id, type="POST", params=params
626 | )
627 | return Vlan(data, self)
628 |
629 | def assign_port(self, port_id, vlan_id):
630 | params = {"vnid": vlan_id}
631 | self.call_api("ports/%s/assign" % port_id, type="POST", params=params)
632 |
633 | def remove_port(self, port_id, vlan_id):
634 | params = {"vnid": vlan_id}
635 | self.call_api("ports/%s/unassign" % port_id, type="POST", params=params)
636 |
637 | def disbond_ports(self, port_id, bulk_disable):
638 | params = {"bulk_disable": bulk_disable}
639 | self.call_api("ports/%s/disbond" % port_id, type="POST", params=params)
640 |
641 | def bond_ports(self, port_id, bulk_disable):
642 | params = {"bulk_disable": bulk_disable}
643 | self.call_api("ports/%s/bond" % port_id, type="POST", params=params)
644 |
645 | def convert_layer_2(self, port_id, vlan_id):
646 | params = {"vnid": vlan_id}
647 | self.call_api("ports/%s/convert/layer-2" % port_id, type="POST", params=params)
648 |
649 | def convert_layer_3(self, port_id, request_ips):
650 | params = {"request_ips": request_ips}
651 | self.call_api("ports/%s/convert/layer-3" % port_id, type="POST", params=params)
652 |
653 | def assign_native_vlan(self, port_id, vnid):
654 | params = {"vnid": vnid}
655 | self.call_api("ports/%s/native-vlan" % port_id, type="POST", params=params)
656 |
657 | def remove_native_vlan(self, port_id):
658 | self.call_api("ports/%s/native-vlan" % port_id, type="DELETE")
659 |
660 | def get_vpn_configuration(self, facilityCode):
661 | params = {"code": facilityCode}
662 | data = self.call_api("user/vpn", type="GET", params=params)
663 | return data
664 |
665 | def turn_on_vpn(self):
666 | return self.call_api("user/vpn", type="POST")
667 |
668 | def turn_off_vpn(self):
669 | return self.call_api("user/vpn", type="DELETE")
670 |
671 | # Packet connect
672 | def create_packet_connections(self, params):
673 | body = {
674 | "name": params["name"],
675 | "project_id": params["project_id"],
676 | "provider_id": params["provider_id"],
677 | "provider_payload": params["provider_payload"],
678 | "port_speed": params["port_speed"],
679 | "vlan": params["vlan"],
680 | }
681 | for opt in ["tags", "description", "facility", "metro"]:
682 | if opt in params:
683 | body[opt] = params[opt]
684 |
685 | data = self.call_api("/packet-connect/connections", type="POST", params=body)
686 | return data
687 |
688 | def get_packet_connection(self, connection_id):
689 | data = self.call_api("/packet-connect/connections/%s" % connection_id)
690 | return data
691 |
692 | def delete_packet_connection(self, connection_id):
693 | data = self.call_api(
694 | "/packet-connect/connections/%s" % connection_id, type="DELETE"
695 | )
696 | return data
697 |
698 | def provision_packet_connection(self, connection_id):
699 | data = self.call_api(
700 | "/packet-connect/connections/{id}/provision" % connection_id, type="POST"
701 | )
702 | return data
703 |
704 | def deprovision_packet_connection(self, connection_id, delete):
705 | params = {"delete": delete}
706 | data = self.call_api(
707 | "/packet-connect/connections/{id}/deprovision" % connection_id,
708 | type="POST",
709 | params=params,
710 | )
711 | return data
712 |
713 | def list_packet_connect_providers(self):
714 | data = self.call_api("/packet-connect/providers", type="GET")
715 | providers = list()
716 | for p in data["providers"]:
717 | providers.append(Provider(p))
718 | return providers
719 |
720 | def get_packet_connect_provider(self, provider_id):
721 | data = self.call_api("/packet-connect/providers/%s" % provider_id, type="GET")
722 | return Provider(data)
723 |
--------------------------------------------------------------------------------
/packet/Metro.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Metro:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.code = data.get("code")
9 | self.name = data.get("name")
10 | self.country = data.get("country")
11 |
12 | def __str__(self):
13 | return "%s" % self.code
14 |
15 | def __repr__(self):
16 | return "{}: {}".format(self.__class__.__name__, self.id)
17 |
--------------------------------------------------------------------------------
/packet/OperatingSystem.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class OperatingSystem(object):
6 | def __init__(self, data):
7 | self.slug = data.get("slug")
8 | self.name = data.get("name")
9 | self.distro = data.get("distro")
10 | self.version = data.get("version")
11 | self.provisionable_on = data.get("provisionable_on")
12 |
13 | def __str__(self):
14 | return "%s %s %s %s" % (self.slug, self.name, self.distro, self.version)
15 |
16 | def __repr__(self):
17 | return "{}: {}".format(self.__class__.__name__, self.slug)
18 |
--------------------------------------------------------------------------------
/packet/Organization.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Organization:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.name = data.get("name")
9 | self.description = data.get("description")
10 | self.website = data.get("website")
11 | self.twitter = data.get("twitter")
12 | self.created_at = data.get("created_at")
13 | self.updated_at = data.get("updated_at")
14 | self.tax_id = data.get("tax_id")
15 | self.main_phone = data.get("main_phone")
16 | self.billing_phone = data.get("billing_phone")
17 | self.credit_amount = data.get("credit_amount")
18 | self.personal = data.get("personal")
19 | self.customdata = data.get("customdata")
20 | self.attn = data.get("attn")
21 | self.purchase_order = data.get("purchase_order")
22 | self.billing_name = data.get("billing_name")
23 | self.enforce_2fa = data.get("enforce_2fa")
24 | self.enforce_2fa_at = data.get("enforce_2fa_at")
25 | self.short_id = data.get("short_id")
26 | self.account_id = data.get("account_id")
27 | self.enabled_features = data.get("enabled_features")
28 | self.maintenance_email = data.get("maintenance_email")
29 | self.abuse_email = data.get("abuse_email")
30 | self.address = data.get("address")
31 | self.billing_address = data.get("billing_address")
32 | self.account_manager = data.get("account_manager")
33 | self.logo = data.get("logo")
34 | self.logo_thumb = data.get("logo_thumb")
35 | self.projects = data.get("projects")
36 | self.plan = data.get("plan")
37 | self.monthly_spend = data.get("monthly_spend")
38 | self.current_user_abilities = data.get("current_user_abilities")
39 | self.href = data.get("href")
40 |
41 | def __str__(self):
42 | return "%s" % self.id
43 |
44 | def __repr__(self):
45 | return "{}: {}".format(self.__class__.__name__, self.id)
46 |
--------------------------------------------------------------------------------
/packet/Plan.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Plan:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.name = data.get("name")
9 | self.slug = data.get("slug")
10 | self.line = data.get("line")
11 | self.pricing = data.get("pricing")
12 | self.specs = data.get("specs")
13 | self.description = data.get("description")
14 | self.available_in = data.get("available_in")
15 |
16 | def __str__(self):
17 | return "%s" % self.slug
18 |
19 | def __repr__(self):
20 | return "{}: {}".format(self.__class__.__name__, self.id)
21 |
--------------------------------------------------------------------------------
/packet/Project.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Project:
6 | def __init__(self, data, manager):
7 | self.manager = manager
8 |
9 | self.id = data.get("id")
10 | self.name = data.get("name")
11 | self.payment_method = data.get("payment_method", [])
12 | self.max_projects = data.get("max_devices")
13 | self.created_at = data.get("created_at")
14 | self.updated_at = data.get("updated_at")
15 | self.devices = data.get("devices")
16 | self.invitations = data.get("invitations")
17 | self.memberships = data.get("memberships")
18 | self.members = data.get("members")
19 | self.ssh_keys = data.get("ssh_keys")
20 |
21 | def update(self):
22 | params = {"name": self.name}
23 |
24 | return self.manager.call_api(
25 | "projects/%s" % self.id, type="PATCH", params=params
26 | )
27 |
28 | def delete(self):
29 | return self.manager.call_api("projects/%s" % self.id, type="DELETE")
30 |
31 | def __str__(self):
32 | return "%s" % self.name
33 |
34 | def __repr__(self):
35 | return "{}: {}".format(self.__class__.__name__, self.id)
36 |
--------------------------------------------------------------------------------
/packet/Provider.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Provider:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.name = data.get("name")
9 | self.status = data.get("status")
10 | self.type = data.get("type")
11 | self.public = data.get("public")
12 |
13 | def __str__(self):
14 | return "%s" % self.id
15 |
16 | def __repr__(self):
17 | return "{}: {}".format(self.__class__.__name__, self.id)
18 |
--------------------------------------------------------------------------------
/packet/SSHKey.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class SSHKey:
6 | def __init__(self, data, manager):
7 | self.manager = manager
8 |
9 | self.id = data.get("id")
10 | self.key = data.get("key")
11 | self.label = data.get("label")
12 | self.fingerprint = data.get("fingerprint")
13 | self.href = data.get("href")
14 | self.created_at = data.get("created_at")
15 | self.updated_at = data.get("updated_at")
16 |
17 | try:
18 | self.owner = data["owner"]["href"]
19 | except (KeyError, TypeError):
20 | self.owner = None
21 |
22 | def update(self):
23 | params = {"label": self.label, "key": self.key}
24 |
25 | return self.manager.call_api(
26 | "ssh-keys/%s" % self.id, type="PATCH", params=params
27 | )
28 |
29 | def delete(self):
30 | return self.manager.call_api("ssh-keys/%s" % self.id, type="DELETE")
31 |
32 | def __str__(self):
33 | return "%s %s" % (self.id, self.label)
34 |
35 | def __repr__(self):
36 | return "{}: {}".format(self.__class__.__name__, self.id)
37 |
--------------------------------------------------------------------------------
/packet/Snapshot.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 |
5 | class Snapshot:
6 | def __init__(self, data):
7 | self.id = data.get("id")
8 | self.status = data.get("status")
9 | self.timestamp = data.get("timestamp")
10 | self.created_at = data.get("created_at")
11 | self.volume = data.get("volume")
12 |
13 | def __str__(self):
14 | return "%s" % self.name
15 |
16 | def __repr__(self):
17 | return "{}: {}".format(self.__class__.__name__, self.id)
18 |
--------------------------------------------------------------------------------
/packet/Vlan.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 | from packet import Project
4 | from .Facility import Facility
5 | from .Metro import Metro
6 |
7 |
8 | class Vlan:
9 | def __init__(self, data, manager):
10 | self.manager = manager
11 | if data is None:
12 | return
13 |
14 | self.id = data.get("id")
15 | self.description = data.get("description")
16 | self.vxlan = data.get("vxlan")
17 | self.internet_gateway = data.get("internet_gateway")
18 | self.facility_code = data.get("facility_code")
19 | self.metro_code = data.get("metro_code")
20 | self.created_at = data.get("created_at")
21 | facility = data.get("facility")
22 | self.facility = Facility(facility) if facility else None
23 | metro = data.get("metro")
24 | self.metro = Metro(metro) if metro else None
25 |
26 | try:
27 | project_data = self.manager.call_api(
28 | data["assigned_to"]["href"], type="GET"
29 | )
30 | self.assigned_to = Project(project_data, self.manager)
31 | except (KeyError, IndexError):
32 | self.attached_to = None
33 |
34 | def get(self):
35 | return self.manager.call_api("virtual-networks/%s" % self.id, type="GET")
36 |
37 | def delete(self):
38 | return self.manager.call_api("virtual-networks/%s" % self.id, type="DELETE")
39 |
40 | def create_internet_gateway(self, ip_reservation_length):
41 | """:param ip_reservation_length: (required) number of IP addresses possible 8 or 16"""
42 | params = {"length": ip_reservation_length}
43 | return self.manager.call_api(
44 | "/virtual-networks/%s/internet-gateways" % self.id,
45 | type="POST",
46 | params=params,
47 | )
48 |
49 | def assign_native_vlan(self, port_id):
50 | params = {"vnid": self.id}
51 | return self.manager.call_api(
52 | "/ports/%s/native-vlan" % port_id, type="POST", params=params
53 | )
54 |
55 | def remove_native_vlan(self, port_id):
56 | return self.manager.call_api("/ports/%s/native-vlan" % port_id, type="DELETE")
57 |
58 | def __str__(self):
59 | return "%s" % self.id
60 |
61 | def __repr__(self):
62 | return "{}: {}".format(self.__class__.__name__, self.id)
63 |
--------------------------------------------------------------------------------
/packet/Volume.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | from .Plan import Plan
5 | from .Facility import Facility
6 |
7 |
8 | class Volume:
9 | def __init__(self, data, manager):
10 | self.manager = manager
11 | if data is None:
12 | return
13 |
14 | self.id = data.get("id")
15 | self.name = data.get("name")
16 | self.description = data.get("description")
17 | self.size = data.get("size")
18 | self.state = data.get("state")
19 | self.locked = data.get("locked")
20 | self.billing_cycle = data.get("billing_cycle")
21 | self.created_at = data.get("created_at")
22 | self.updated_at = data.get("updated_at")
23 | self.attachments = data.get("attachments")
24 |
25 | self.plan = Plan(data.get("plan"))
26 | self.facility = Facility(data.get("facility"))
27 | try:
28 | self.attached_to = data["attachments"][0]["device"]["id"]
29 | except (KeyError, IndexError):
30 | self.attached_to = None
31 |
32 | def update(self):
33 | params = {
34 | "description": self.description,
35 | "size": self.size,
36 | "plan": self.plan.slug,
37 | "locked": self.locked,
38 | }
39 |
40 | return self.manager.call_api(
41 | "storage/%s" % self.id, type="PATCH", params=params
42 | )
43 |
44 | def delete(self):
45 | return self.manager.call_api("storage/%s" % self.id, type="DELETE")
46 |
47 | def attach(self, device_id):
48 | params = {"device_id": device_id}
49 | return self.manager.call_api(
50 | "storage/%s/attachments" % self.id, type="POST", params=params
51 | )
52 |
53 | def detach(self):
54 | for attachment in self.attachments:
55 | return self.manager.call_api(attachment["href"], type="DELETE")
56 |
57 | def list_snapshots(self, params={}):
58 | data = self.manager.call_api("storage/%s/snapshots" % (self.id))
59 | snapshots = list()
60 | for jsoned in data["snapshots"]:
61 | snapshot = VolumeSnapshot(jsoned, self)
62 | snapshots.append(snapshot)
63 | return snapshots
64 |
65 | def create_snapshot(self):
66 | self.manager.call_api("storage/%s/snapshots" % self.id, type="POST")
67 |
68 | def create_snapshot_policy(self, frequency, count):
69 | """Creates a new snapshot policy of your volume.
70 |
71 | :param frequency: (required) Snapshot frequency
72 |
73 | Validation of `frequency` is left to the packet api to avoid going out
74 | of date if any new value is introduced.
75 | The currently known values are:
76 | - 1hour,
77 | - 1day
78 | - 1week
79 | - 1month
80 | - 1year
81 | """
82 | data = self.manager.call_api(
83 | "storage/{0}/snapshot-policies?snapshot_frequency={1}&snapshot_count={2}".format(
84 | self.id, frequency, count
85 | ),
86 | type="POST",
87 | )
88 | return SnapshotPolicy(data, self)
89 |
90 | def clone(self):
91 | return Volume(
92 | self.manager.call_api("storage/%s/clone" % self.id, type="POST"),
93 | manager=self.manager,
94 | )
95 |
96 | def restore(self, restore_point):
97 | self.manager.restore_volume(self.id, restore_point=restore_point)
98 |
99 | def __str__(self):
100 | return "%s" % self.id
101 |
102 | def __repr__(self):
103 | return "{}: {}".format(self.__class__.__name__, self.id)
104 |
105 |
106 | class VolumeSnapshot:
107 | def __init__(self, data, volume):
108 | self.volume = volume
109 |
110 | self.id = data["id"]
111 | self.status = data["status"]
112 | self.timestamp = data["timestamp"]
113 | self.created_at = data["created_at"]
114 |
115 | def delete(self):
116 | return self.volume.manager.call_api(
117 | "/storage/%s/snapshots/%s" % (self.volume.id, self.id), type="DELETE"
118 | )
119 |
120 | def __str__(self):
121 | return "%s" % self.id
122 |
123 | def __repr__(self):
124 | return "{}: {}".format(self.__class__.__name__, self.id)
125 |
126 |
127 | class SnapshotPolicy:
128 | def __init__(self, data, policy):
129 | self.policy = policy
130 |
131 | self.id = data["id"]
132 | self.count = data["snapshot_count"]
133 | self.frequency = data["snapshot_frequency"]
134 | self.created_at = data["created_at"]
135 | self.updated_at = data["updated_at"]
136 |
137 | def delete(self):
138 | return self.policy.manager.call_api(
139 | "storage/snapshot-policies/%s" % self.id, type="DELETE"
140 | )
141 |
142 | def update_snapshot_policy(self, frequency, count):
143 | """Updates the volume snapshot policy.
144 |
145 | :param frequency: (required) Snapshot frequency
146 |
147 | Validation of `frequency` is left to the packet api to avoid going out
148 | of date if any new value is introduced.
149 | The currently known values are:
150 | - 1hour,
151 | - 1day
152 | - 1week
153 | - 1month
154 | - 1year
155 | """
156 | data = self.policy.manager.call_api(
157 | "storage/snapshot-policies/{0}?snapshot_frequency={1}&snapshot_count={2}".format(
158 | self.id, frequency, count
159 | ),
160 | type="PATCH",
161 | )
162 | return SnapshotPolicy(data, self)
163 |
164 | def __str__(self):
165 | return "%s" % self.id
166 |
167 | def __repr__(self):
168 | return "{}: {}".format(self.__class__.__name__, self.id)
169 |
--------------------------------------------------------------------------------
/packet/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | """library to interact with the Equinix Metal API"""
5 |
6 | __version__ = "1.44.3"
7 | __author__ = "Equinix Metal Engineers"
8 | __author_email__ = "support@equinixmetal.com"
9 | __license__ = "LGPL v3"
10 | __copyright__ = "Copyright (c) 2021, Equinix"
11 |
12 | from .Device import Device # noqa
13 | from .Email import Email # noqa
14 | from .Event import Event # noqa
15 | from .Facility import Facility # noqa
16 | from .Metro import Metro # noqa
17 | from .OperatingSystem import OperatingSystem # noqa
18 | from .Plan import Plan # noqa
19 | from .Project import Project # noqa
20 | from .SSHKey import SSHKey # noqa
21 | from .Volume import Volume # noqa
22 | from .BGPConfig import BGPConfig # noqa
23 | from .BGPSession import BGPSession # noqa
24 | from .DeviceBatch import DeviceBatch # noqa
25 | from .Manager import Manager # noqa
26 | from .Snapshot import Snapshot # noqa
27 | from .Organization import Organization # noqa
28 | from .Provider import Provider # noqa
29 | from .baseapi import Error # noqa
30 | from .baseapi import ResponseError # noqa
31 |
--------------------------------------------------------------------------------
/packet/baseapi.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import json
5 | import logging
6 | import requests
7 |
8 | from packet import __version__
9 |
10 |
11 | class Error(Exception):
12 | """Base exception class for this module"""
13 |
14 | def __init__(self, msg, cause=None):
15 | super(Error, self).__init__(msg)
16 | self._cause = cause
17 |
18 | @property
19 | def cause(self):
20 | """The underlying exception causing the error, if any."""
21 | return self._cause
22 |
23 |
24 | class ResponseError(Error):
25 | def __init__(self, resp, data, exception=None):
26 | if not data:
27 | msg = "(empty response)"
28 | elif not isinstance(data, dict):
29 | msg = str(data)
30 | elif "error" in data:
31 | msg = data["error"]
32 | elif "errors" in data:
33 | msg = ", ".join(data["errors"])
34 | super(ResponseError, self).__init__(
35 | "Error {0}: {1}".format(resp.status_code, msg), exception
36 | )
37 | self._response = resp
38 |
39 | @property
40 | def response(self):
41 | """The Requests response which failed"""
42 | return self._response
43 |
44 |
45 | class JSONReadError(Error):
46 | pass
47 |
48 |
49 | class BaseAPI(object):
50 | """
51 | Basic api class for
52 | """
53 |
54 | def __init__(self, auth_token, consumer_token, user_agent=""):
55 | self.auth_token = auth_token
56 | self.consumer_token = consumer_token
57 | self.end_point = "api.packet.net"
58 | self._user_agent_prefix = user_agent
59 | self._log = logging.getLogger(__name__)
60 |
61 | @property
62 | def user_agent(self):
63 | return "{}packet-python/{} {}".format(
64 | self._user_agent_prefix, __version__, requests.utils.default_user_agent()
65 | ).strip()
66 |
67 | def call_api(self, method, type="GET", params=None): # noqa
68 | if params is None:
69 | params = {}
70 |
71 | url = "https://" + self.end_point + "/" + method
72 |
73 | headers = {
74 | "X-Auth-Token": self.auth_token,
75 | "X-Consumer-Token": self.consumer_token,
76 | "Content-Type": "application/json",
77 | "User-Agent": self.user_agent,
78 | }
79 |
80 | # remove token from log
81 | headers_str = str(headers).replace(self.auth_token.strip(), "TOKEN")
82 | self._log.debug("%s %s %s %s" % (type, url, params, headers_str))
83 | try:
84 | if type == "GET":
85 | url = url + "%s" % self._parse_params(params)
86 | resp = requests.get(url, headers=headers)
87 | elif type == "POST":
88 | resp = requests.post(
89 | url,
90 | headers=headers,
91 | data=json.dumps(
92 | params, default=lambda o: o.__dict__, sort_keys=True, indent=4
93 | ),
94 | )
95 | elif type == "DELETE":
96 | resp = requests.delete(url, headers=headers)
97 | elif type == "PATCH":
98 | resp = requests.patch(url, headers=headers, data=json.dumps(params))
99 | else:
100 | raise Error(
101 | "method type not recognized as one of GET, POST, DELETE or PATCH: %s"
102 | % type
103 | )
104 | except requests.exceptions.RequestException as e: # pragma: no cover
105 | raise Error("Communcations error: %s" % str(e), e)
106 |
107 | if not resp.content:
108 | data = None
109 | elif resp.headers.get("content-type", "").startswith("application/json"):
110 | try:
111 | data = resp.json()
112 | except ValueError as e: # pragma: no cover
113 | raise JSONReadError("Read failed: %s" % e.message, e)
114 | else:
115 | data = resp.content # pragma: no cover
116 |
117 | if not resp.ok: # pragma: no cover
118 | raise ResponseError(resp, data)
119 |
120 | try:
121 | resp.raise_for_status()
122 | except requests.HTTPError as e: # pragma: no cover
123 | raise ResponseError(resp, data, e)
124 |
125 | self.meta = None
126 | try:
127 | if data and data["meta"]:
128 | self.meta = data["meta"]
129 | except (KeyError, IndexError):
130 | pass
131 |
132 | return data
133 |
134 | def _parse_params(self, params):
135 | vals = list()
136 | for k, v in params.items():
137 | vals.append(str("%s=%s" % (k, v)))
138 | return "?" + "&".join(vals)
139 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: LGPL-3.0-only
2 |
3 | [aliases]
4 | test=pytest
5 |
6 | [bdist_wheel]
7 | universal=1
8 |
9 | [pylama:pycodestyle]
10 | ignore = E501
11 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 | import codecs
4 | import os.path
5 |
6 | try:
7 | from setuptools import setup
8 | except ImportError:
9 | from ez_setup import use_setuptools
10 |
11 | use_setuptools()
12 | from setuptools import setup
13 |
14 |
15 | with open("README.md") as readme, open("CHANGELOG.md") as changelog:
16 | long_description = readme.read() + "\n" + changelog.read()
17 |
18 |
19 | def read(rel_path):
20 | here = os.path.abspath(os.path.dirname(__file__))
21 | with codecs.open(os.path.join(here, rel_path), "r") as fp:
22 | return fp.read()
23 |
24 |
25 | def get_version(rel_path):
26 | for line in read(rel_path).splitlines():
27 | if line.startswith("__version__"):
28 | delim = '"' if '"' in line else "'"
29 | return line.split(delim)[1]
30 | else:
31 | raise RuntimeError("Unable to find version string.")
32 |
33 |
34 | setup(
35 | name="packet-python",
36 | version=get_version("packet/__init__.py"),
37 | description="Equinix Metal (Packet) API client",
38 | long_description=long_description,
39 | long_description_content_type="text/markdown",
40 | url="https://github.com/packethost/packet-python",
41 | author="Equinix Metal Developers",
42 | author_email="support@equinixmetal.com",
43 | license="LGPL v3",
44 | keywords="equinix metal packet api client infrastructure",
45 | packages=["packet"],
46 | install_requires="requests",
47 | setup_requires=["pytest-runner"],
48 | tests_require=["pytest", "requests-mock"],
49 | classifiers=[
50 | "Development Status :: 5 - Production/Stable",
51 | "Intended Audience :: Developers",
52 | "Intended Audience :: Information Technology",
53 | "Topic :: Software Development :: Libraries",
54 | "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
55 | "Programming Language :: Python :: 2",
56 | "Programming Language :: Python :: 2.7",
57 | "Programming Language :: Python :: 3",
58 | "Programming Language :: Python :: 3.4",
59 | "Programming Language :: Python :: 3.5",
60 | "Programming Language :: Python :: 3.6",
61 | "Programming Language :: Python :: 3.7",
62 | "Programming Language :: Python :: 3.8",
63 | "Programming Language :: Python :: 3.9",
64 | ],
65 | )
66 |
--------------------------------------------------------------------------------
/shell.nix:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: LGPL-3.0-only
2 |
3 | let
4 | _pkgs = import {};
5 | in
6 | { pkgs ? import (_pkgs.fetchFromGitHub { owner = "NixOS";
7 | repo = "nixpkgs-channels";
8 | # nixos-unstable @2019-05-28
9 | rev = "eccb90a2d997d65dc514253b441e515d8e0241c3";
10 | sha256 = "0ffa84mp1fgmnqx2vn43q9pypm3ip9y67dkhigsj598d8k1chzzw";
11 | }) {}
12 | }:
13 |
14 | with pkgs;
15 |
16 | mkShell {
17 | buildInputs = [
18 | python3
19 | python3Packages.black
20 | python3Packages.flake8
21 | python3Packages.pylama
22 | python3Packages.pytest
23 | python3Packages.pytestcov
24 | python3Packages.requests
25 | python3Packages.requests-mock
26 | python3Packages.tox
27 | python3Packages.twine
28 | python3Packages.wheel
29 | ];
30 | }
31 |
--------------------------------------------------------------------------------
/test/fixtures/get__capacity.json:
--------------------------------------------------------------------------------
1 | {"capacity":{"ams1":{"baremetal_0":{"level":"limited","servers":"45+"},"baremetal_1":{"level":"critical","servers":"1+"},"baremetal_2":{"level":"normal","servers":"14+"},"baremetal_2a":{"level":"unavailable","servers":0},"baremetal_3":{"level":"normal","servers":"8+"}},"atl1":{"baremetal_1e":{"level":"unavailable","servers":0}},"dfw1":{"baremetal_1e":{"level":"normal","servers":"10+"}},"ewr1":{"baremetal_0":{"level":"limited","servers":"101+"},"baremetal_1":{"level":"critical","servers":"1+"},"baremetal_2":{"level":"critical","servers":"1+"},"baremetal_2a":{"level":"critical","servers":"1+"},"baremetal_3":{"level":"limited","servers":"9+"}},"iad1":{"baremetal_1e":{"level":"normal","servers":"10+"}},"lax1":{"baremetal_1e":{"level":"unavailable","servers":0}},"nrt1":{"baremetal_0":{"level":"normal","servers":"68+"},"baremetal_1":{"level":"normal","servers":"15+"},"baremetal_2":{"level":"unavailable","servers":0},"baremetal_2a":{"level":"limited","servers":"3+"}},"ord1":{"baremetal_1e":{"level":"normal","servers":"10+"}},"sea1":{"baremetal_1e":{"level":"normal","servers":"10+"}},"sjc1":{"baremetal_0":{"level":"critical","servers":"1+"},"baremetal_1":{"level":"critical","servers":"1+"},"baremetal_2":{"level":"critical","servers":"1+"},"baremetal_2a":{"level":"unavailable","servers":0},"baremetal_3":{"level":"normal","servers":"10+"}}}}
--------------------------------------------------------------------------------
/test/fixtures/get_devices_9dec7266.json:
--------------------------------------------------------------------------------
1 | {"id":"e781ae1b","short_id":"e781ae1b","hostname":"test-01","description":null,"state":"active","tags":[],"billing_cycle":"hourly","user":"root","iqn":"iqn.2016-06.net.packet:device.e781ae1b","locked":true,"bonding_mode":5,"created_at":"2016-06-01T19:27:28Z","updated_at":"2016-06-21T20:28:27Z","staff":true,"allow_pxe":false,"operating_system":{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","clli":null,"description":null,"npanxx":null,"emergency_phone":null,"public":true,"address":null,"discounts":[],"facility_rooms":[{"href":"/facility-rooms/6e8d7c53"}],"provider":{"href":"/providers/61fae8b0"},"href":"/facilities/e1e9c52e"},"project":{"href":"/projects/438659f0"},"volumes":[{"href":"/storage/8a3369fc"},{"href":"/storage/7f89830d"}],"ip_addresses":[{"id":"47e2d396","address_family":4,"netmask":"255.255.255.254","public":true,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e781ae1b"},"network":"147.75.1.2","address":"147.75.1.3","gateway":"147.75.1.2","href":"/ips/47e2d396"},{"id":"009d5f19","address_family":6,"netmask":"ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe","public":true,"cidr":127,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e781ae1b"},"network":"2604:::::","address":"2604:::::","gateway":"2604:::::","href":"/ips/009d5f19"},{"id":"965c286c","address_family":4,"netmask":"255.255.255.254","public":false,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e781ae1b"},"network":"10.100.1.0","address":"10.100.1.1","gateway":"10.100.1.0","href":"/ips/965c286c"}],"metering_limits":[],"server":{"href":"/hardware/3feec4bd"},"plan":{"id":"e69c0169-4726-46ea-98f1-939c9e8a3607","slug":"baremetal_0","name":"Type 0","description":"Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel Atom C2550 @ 2.4Ghz"}],"memory":{"total":"8GB"},"drives":[{"count":1,"size":"80GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":false,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#c19eb1f6-d283-4d52-a81d-e83c503f7b27"},{"href":"#69525607-1a1e-4843-8f67-1dd83e6bd21a"}],"pricing":{"hour":0.05}},"userdata":"","href":"/devices/e781ae1b"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_devices_e123s_ips.json:
--------------------------------------------------------------------------------
1 | {
2 | "ip_addresses": [
3 | {
4 | "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be1",
5 | "address_family": 4,
6 | "netmask": "255.255.255.254",
7 | "created_at": "2019-07-18T08:46:38Z",
8 | "details": null,
9 | "tags": [],
10 | "public": true,
11 | "cidr": 31,
12 | "management": true,
13 | "manageable": true,
14 | "enabled": true,
15 | "global_ip": null,
16 | "customdata": {},
17 | "project": {},
18 | "project_lite": {},
19 | "facility": {
20 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
21 | "name": "Amsterdam, NL",
22 | "code": "ams1",
23 | "features": [
24 | "baremetal",
25 | "storage",
26 | "global_ipv4",
27 | "backend_transfer",
28 | "layer_2"
29 | ],
30 | "address": {
31 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
32 | },
33 | "ip_ranges": [
34 | "2604:1380:2000::/36",
35 | "147.75.204.0/23",
36 | "147.75.100.0/22",
37 | "147.75.80.0/22",
38 | "147.75.32.0/23"
39 | ]
40 | },
41 | "assigned_to": {
42 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
43 | },
44 | "interface": {
45 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
46 | },
47 | "network": "147.75.84.94",
48 | "address": "147.75.84.95",
49 | "gateway": "147.75.84.94",
50 | "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1"
51 | },
52 | {
53 | "id": "06fd360b-8f6c-4f3c-a858-6ea8a9bc7cf8",
54 | "address_family": 6,
55 | "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe",
56 | "created_at": "2019-07-18T08:46:37Z",
57 | "details": null,
58 | "tags": [],
59 | "public": true,
60 | "cidr": 127,
61 | "management": true,
62 | "manageable": true,
63 | "enabled": true,
64 | "global_ip": null,
65 | "customdata": {},
66 | "project": {},
67 | "project_lite": {},
68 | "facility": {
69 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
70 | "name": "Amsterdam, NL",
71 | "code": "ams1",
72 | "features": [
73 | "baremetal",
74 | "storage",
75 | "global_ipv4",
76 | "backend_transfer",
77 | "layer_2"
78 | ],
79 | "address": {
80 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
81 | },
82 | "ip_ranges": [
83 | "2604:1380:2000::/36",
84 | "147.75.204.0/23",
85 | "147.75.100.0/22",
86 | "147.75.80.0/22",
87 | "147.75.32.0/23"
88 | ]
89 | },
90 | "assigned_to": {
91 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
92 | },
93 | "interface": {
94 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
95 | },
96 | "network": "2604:1380:2000:ae00::",
97 | "address": "2604:1380:2000:ae00::1",
98 | "gateway": "2604:1380:2000:ae00::",
99 | "href": "/ips/06fd360b-8f6c-4f3c-a858-6ea8a9bc7cf8"
100 | },
101 | {
102 | "id": "4edf9211-142e-476e-b926-ebd247fb7b66",
103 | "address_family": 4,
104 | "netmask": "255.255.255.254",
105 | "created_at": "2019-07-18T08:46:37Z",
106 | "details": null,
107 | "tags": [],
108 | "public": false,
109 | "cidr": 31,
110 | "management": true,
111 | "manageable": true,
112 | "enabled": true,
113 | "global_ip": null,
114 | "customdata": {},
115 | "project": {},
116 | "project_lite": {},
117 | "facility": {
118 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
119 | "name": "Amsterdam, NL",
120 | "code": "ams1",
121 | "features": [
122 | "baremetal",
123 | "storage",
124 | "global_ipv4",
125 | "backend_transfer",
126 | "layer_2"
127 | ],
128 | "address": {
129 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
130 | },
131 | "ip_ranges": [
132 | "2604:1380:2000::/36",
133 | "147.75.204.0/23",
134 | "147.75.100.0/22",
135 | "147.75.80.0/22",
136 | "147.75.32.0/23"
137 | ]
138 | },
139 | "assigned_to": {
140 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
141 | },
142 | "interface": {
143 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
144 | },
145 | "network": "10.80.110.0",
146 | "address": "10.80.110.1",
147 | "gateway": "10.80.110.0",
148 | "href": "/ips/4edf9211-142e-476e-b926-ebd247fb7b66"
149 | }
150 | ]
151 | }
--------------------------------------------------------------------------------
/test/fixtures/get_facilities.json:
--------------------------------------------------------------------------------
1 | {"facilities":[{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"address":null,"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},{"id":"2b70eb8f-fa18-47c0-aba7-222a842362fd","name":"Sunnyvale, CA","code":"sjc1","features":["baremetal"],"address":null,"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"id":"8e6470b3-b75e-47d1-bb93-45b225750975","name":"Amsterdam, NL","code":"ams1","features":["baremetal"],"address":null,"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_ips_e123s.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be1",
3 | "address_family": 4,
4 | "netmask": "255.255.255.254",
5 | "created_at": "2019-07-18T08:46:38Z",
6 | "details": null,
7 | "tags": [],
8 | "public": true,
9 | "cidr": 31,
10 | "management": true,
11 | "manageable": true,
12 | "enabled": true,
13 | "global_ip": null,
14 | "customdata": {},
15 | "project": {},
16 | "project_lite": {},
17 | "facility": {
18 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
19 | "name": "Amsterdam, NL",
20 | "code": "ams1",
21 | "features": [
22 | "baremetal",
23 | "storage",
24 | "global_ipv4",
25 | "backend_transfer",
26 | "layer_2"
27 | ],
28 | "address": {
29 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
30 | },
31 | "ip_ranges": [
32 | "2604:1380:2000::/36",
33 | "147.75.204.0/23",
34 | "147.75.100.0/22",
35 | "147.75.80.0/22",
36 | "147.75.32.0/23"
37 | ]
38 | },
39 | "assigned_to": {
40 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
41 | },
42 | "interface": {
43 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
44 | },
45 | "network": "147.75.84.94",
46 | "address": "147.75.84.95",
47 | "gateway": "147.75.84.94",
48 | "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1"
49 | }
--------------------------------------------------------------------------------
/test/fixtures/get_locations_metros.json:
--------------------------------------------------------------------------------
1 | {"metros":[{"id":"0446ac38-aae0-45f5-959c-54d26c4fc3c7","name":"Phoenix","code":"px","country":"US"},{"id":"833225d2-a35a-49cf-a67c-1f84d5d11654","name":"Marseille","code":"mr","country":"FR"},{"id":"96a57b6d-c62c-41b5-ab8e-f8d63a7f9887","name":"Washington DC","code":"dc","country":"US"},{"id":"a04e45e4-4356-458c-b25a-b72cc54bdad1","name":"Atlanta","code":"at","country":"US"},{"id":"932eecda-6808-44b9-a3be-3abef49796ef","name":"New York","code":"ny","country":"US"},{"id":"108b2cfb-246b-45e3-885a-bf3e82fce1a0","name":"Amsterdam","code":"am","country":"NL"},{"id":"5afb3744-f80d-4b49-bf21-50ede9252cfd","name":"Toronto","code":"tr","country":"CA"},{"id":"d2a8e94f-2b42-4440-9c8e-8ef01defcd27","name":"Seoul","code":"sl","country":"KR"},{"id":"d50fd052-34ec-4977-a173-ad6f9266995d","name":"Hong Kong","code":"hk","country":"HK"},{"id":"f6ada324-8226-4bfc-99a8-453d47caf2dc","name":"Singapore","code":"sg","country":"SG"},{"id":"5f72cbf6-96e4-44f2-ae60-213888fa2b9f","name":"Tokyo","code":"ty","country":"JP"},{"id":"b1ac82b2-616c-4405-9424-457ef6edf9ae","name":"Frankfurt","code":"fr","country":"DE"},{"id":"d2f09853-a1aa-4b29-aa9d-682462fd8d1d","name":"Sydney","code":"sy","country":"AU"},{"id":"5f1d3910-2059-4737-8e7d-d4907cfbf27f","name":"London","code":"ld","country":"GB"},{"id":"91fa81e8-db1a-4da1-b218-7beeaab6f8bc","name":"Kansas City","code":"kc","country":"US"},{"id":"f2f0f0b3-5359-4296-a402-56abad842f56","name":"Paris","code":"pa","country":"FR"},{"id":"56c55e99-8125-4f65-af21-a8682e033804","name":"Houston","code":"ho","country":"US"},{"id":"b8ffa6c9-3da2-4cf1-b48d-049002b208fe","name":"Seattle","code":"se","country":"US"},{"id":"2991b022-b8c4-497e-8db7-5a407c3a209b","name":"Silicon Valley","code":"sv","country":"US"},{"id":"bb059cc0-0b2a-4f5b-8a55-219e6b4240da","name":"Los Angeles","code":"la","country":"US"},{"id":"60666d92-e00f-43a8-a9f8-fddf665390ca","name":"Chicago","code":"ch","country":"US"},{"id":"d3d6b29f-042d-43b7-b3ce-0bf53d5754ca","name":"Dallas","code":"da","country":"US"},{"id":"543f8059-65f6-4c2c-b7ad-6dac5eb87085","name":"Pittsburgh","code":"pi","country":"US"},{"id":"583a553a-465a-49f3-a8a9-5fa6b44aa519","name":"Detroit","code":"dt","country":"US"}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_operating-systems.json:
--------------------------------------------------------------------------------
1 | {"operating_systems":[{"slug":"centos_7","name":"CentOS 7","distro":"centos","version":"7","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"coreos_stable","name":"CoreOS (stable)","distro":"coreos","version":"stable","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"coreos_beta","name":"CoreOS (beta)","distro":"coreos","version":"beta","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"coreos_alpha","name":"CoreOS (alpha)","distro":"coreos","version":"alpha","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"debian_8","name":"Debian 8","distro":"debian","version":"8","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"rancher","name":"RancherOS","distro":"rancher","version":"latest","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"ubuntu_14_04_image","name":"Ubuntu 14.04 LTS","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"ubuntu_16_04_image","name":"Ubuntu 16.04 LTS (beta)","distro":"ubuntu","version":"16.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"windows_2012_rc2","name":"Windows 2012 RC2","distro":"windows","version":"2012 RC2","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},{"slug":"vmware_esxi_6","name":"VMWare ESXi 6","distro":"vmware","version":"6.0.0","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]}]}
--------------------------------------------------------------------------------
/test/fixtures/get_plans.json:
--------------------------------------------------------------------------------
1 | {"plans":[{"id":"49f1faba-e51b-4118-b7db-a88375f0abb5","slug":"bandwidth","name":"Outbound Bandwidth","description":"TBD","line":"bandwidth","specs":{},"available_in":[],"plan_versions":[{"href":"#931f7994-c259-4d50-975f-7158012fed69"}],"pricing":{"GB":0.05}},{"id":"e69c0169-4726-46ea-98f1-939c9e8a3607","slug":"baremetal_0","name":"Type 0","description":"Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel Atom C2550 @ 2.4Ghz"}],"memory":{"total":"8GB"},"drives":[{"count":1,"size":"80GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":false,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#c19eb1f6-d283-4d52-a81d-e83c503f7b27"},{"href":"#69525607-1a1e-4843-8f67-1dd83e6bd21a"}],"pricing":{"hour":0.05}},{"id":"6d1f1ffa-7912-4b78-b50d-88cc7c8ab40f","slug":"baremetal_1","name":"Type 1","description":"Our Type 1 configuration is a zippy general use server, with an Intel E3-1240 v3 processor and 32GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel E3-1240 v3"}],"memory":{"total":"32GB"},"drives":[{"count":2,"size":"120GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":true,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#fe2790c8-8e8f-4997-bbf5-2fe59f98e007"},{"href":"#7facb54b-1b89-4f44-a6b3-b0dab1125a2f"},{"href":"#4f5ec112-f740-461e-8937-1068ba5fe346"}],"pricing":{"hour":0.4}},{"id":"a3729923-fdc4-4e85-a972-aafbad3695db","slug":"baremetal_2","name":"Type 2 (Beta)","description":"Our Type 2 configuration is the perfect all purpose virtualization server, with dual E5-2650 v4 processors, 256 GB of DDR4 RAM, and six SSDs totaling 2.8 TB of storage.","line":"baremetal","specs":{"cpus":[{"count":2,"type":"Intel Xeon E5-2650 v4 @2.2GHz"}],"memory":{"total":"256GB"},"drives":[{"count":6,"size":"480GB","type":"SSD"}],"nics":[{"count":2,"type":"10Gbps"}],"features":{"raid":true,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"}],"plan_versions":[{"href":"#68eea7e2-9e38-4390-8013-ce8fb352dcce"}],"pricing":{"hour":1.25}},{"id":"741f3afb-bb2f-4694-93a0-fcbad7cd5e78","slug":"baremetal_3","name":"Type 3","description":"Our Type 3 configuration is a high core, high IO server, with dual Intel E5-2640 v3 processors, 128GB of DDR4 RAM and ultra fast NVME flash drives.","line":"baremetal","specs":{"cpus":[{"count":2,"type":"Intel E5-2640 v3"}],"memory":{"total":"128GB"},"drives":[{"count":2,"size":"120GB","type":"SSD"},{"count":1,"size":"1.6TB","type":"NVME"}],"nics":[{"count":2,"type":"10Gbps"}],"features":{"raid":true,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#5d7b25a6-dc53-4e2a-8f0d-d15ab45fe127"},{"href":"#32ad93cc-be69-408d-b85b-e6230c1df33e"},{"href":"#22f231bc-45be-4bb9-816d-6e68adad96a2"},{"href":"#9344bb8a-09da-43dc-884f-91c641e442d4"}],"pricing":{"hour":1.75}},{"id":"c85c8fe2-7edb-4d63-82da-17f6506663d5","slug":"global_ipv4","name":"Global IPv4","description":"TBD","line":"network","specs":{},"available_in":[],"plan_versions":[{"href":"#771ee12d-41ae-4b78-be41-e47bc8851f7f"}],"pricing":{"hour":0.03}},{"id":"141d0eff-d5fc-4d72-9466-0c623b66a82b","slug":"public_ipv4","name":"Public IPv4","description":"TBD","line":"network","specs":{},"available_in":[],"plan_versions":[{"href":"#a9d53f2b-69cd-476c-b6dd-41ab6e1ff499"}],"pricing":{"hour":0.005}},{"id":"87728148-3155-4992-a730-8d1e6aca8a32","slug":"storage_1","name":"Standard","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#63f8bcdb-06ce-4c27-b1bb-22de20ae8ba6"}],"pricing":{"hour":0.000104}},{"id":"d6570cfb-38fa-4467-92b3-e45d059bb249","slug":"storage_2","name":"Performance","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#9969070d-dacb-45f3-bf38-f4f8332b678e"}],"pricing":{"hour":0.000223}},{"id":"1f302828-c5aa-42e7-b219-d9c40b4e3483","slug":"storage_snapshot","name":"Snapshot","description":"TBD","line":"storage_snapshot","specs":{},"available_in":[],"plan_versions":[{"href":"#be177745-d210-4896-a5a7-942b2e30985b"}],"pricing":{"hour":0.0}}]}
--------------------------------------------------------------------------------
/test/fixtures/get_projects.json:
--------------------------------------------------------------------------------
1 | {"projects":[{"id":"261a69b7","name":"test project 1","created_at":"2015-08-21T09:13:54Z","updated_at":"2016-02-28T03:45:32Z","max_devices":{"baremetal_0":10,"baremetal_1":5,"baremetal_3":1,"storage_1":1,"storage_2":1},"auto_charge":true,"billable":true,"staff":false,"members":[{"href":"/users/e34b8eec"}],"memberships":[{"href":"/memberships/89c5741a"}],"invitations":[],"payment_method":{"href":"/payment-methods/8bed021a"},"devices":[],"ssh_keys":[],"volumes":[],"price_policies":[],"discounts":[],"metering_limits":[],"href":"/projects/261a69b7"},{"id":"a4209ba5","name":"test project 2","created_at":"2015-12-12T06:18:17Z","updated_at":"2016-02-28T03:45:34Z","max_devices":{"baremetal_0":10,"baremetal_1":5,"baremetal_3":1,"storage_1":1,"storage_2":1},"auto_charge":true,"billable":true,"staff":false,"members":[{"href":"/users/7f014d49"}],"memberships":[{"href":"/memberships/d5e192c7"}],"invitations":[],"payment_method":{"href":"/payment-methods/90f69800"},"devices":[],"ssh_keys":[{"href":"/ssh-keys/aedbc017"},{"href":"/ssh-keys/e5045903"}],"volumes":[],"price_policies":[],"discounts":[],"metering_limits":[],"href":"/projects/a4209ba5"},{"id":"c9b4bd73","name":"test project 3","created_at":"2015-08-21T15:53:06Z","updated_at":"2016-02-28T03:45:35Z","max_devices":{"baremetal_0":10,"baremetal_1":5,"baremetal_3":1,"storage_1":1,"storage_2":1},"auto_charge":true,"billable":true,"staff":false,"members":[{"href":"/users/d6187c99"}],"memberships":[{"href":"/memberships/1e4f6b13"}],"invitations":[],"payment_method":{"href":"/payment-methods/0a98073b"},"devices":[],"ssh_keys":[],"volumes":[],"price_policies":[],"discounts":[],"metering_limits":[],"href":"/projects/c9b4bd73"}],"meta":{"first":{"href":"/projects?page=1"},"previous":null,"self":{"href":"/projects?page=1"},"next":{"href":"/projects?page=2"},"last":{"href":"/projects?page=187"},"total":18}}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_projects_1234_bgp-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "913317a6-8baf-4142-94be-87de68474947",
3 | "status": "enabled",
4 | "deployment_type": "local",
5 | "asn": 65000,
6 | "md5": "c3RhY2twb2ludDIwMTgK",
7 | "route_object": null,
8 | "max_prefix": 10,
9 | "created_at": "2018-04-05T20:13:17Z",
10 | "requested_at": "2018-07-12T15:38:49Z",
11 | "project": {
12 | "href": "/projects/93125c2a-8b78-4d4f-a3c4-7367d6b7cca8"
13 | },
14 | "sessions": [],
15 | "ranges": [],
16 | "href": "/bgp-configs/913317a6-8baf-4142-94be-87de68474947"
17 | }
18 |
--------------------------------------------------------------------------------
/test/fixtures/get_projects_438659f0.json:
--------------------------------------------------------------------------------
1 | {"id":"438659f0","name":"test project","created_at":"2016-05-22T13:23:11Z","updated_at":"2016-07-05T12:52:25Z","max_devices":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"storage_1":null,"storage_2":null},"auto_charge":true,"billable":true,"staff":false,"members":[{"href":"/users/1140617d"}],"memberships":[{"href":"/memberships/2f06a0b7"}],"invitations":[],"payment_method":{"href":"/payment-methods/b2bb4ee0"},"devices":[{"href":"/devices/e781ae1b"},{"href":"/devices/e9a2a61e"},{"href":"/devices/b5b4f31f"}],"ssh_keys":[{"href":"/ssh-keys/a3d8bebe"},{"href":"/ssh-keys/084a5dec"}],"volumes":[{"href":"/storage/f9a8a263"},{"href":"/storage/f2df1dd1"},{"href":"/storage/bf595211"},{"href":"/storage/7f89830d"},{"href":"/storage/8a3369fc"}],"price_policies":[],"discounts":[],"metering_limits":[],"href":"/projects/438659f0"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_projects_438659f0_devices.json:
--------------------------------------------------------------------------------
1 | {"devices":[{"id":"e781ae1b","short_id":"e781ae1b","hostname":"test-01","description":null,"state":"active","tags":[],"billing_cycle":"hourly","user":"root","iqn":"iqn.2016-06.net.packet:device.e781ae1b","locked":true,"bonding_mode":5,"created_at":"2016-06-01T19:27:28Z","updated_at":"2016-06-21T20:28:27Z","staff":true,"allow_pxe":false,"operating_system":{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","clli":null,"description":null,"npanxx":null,"emergency_phone":null,"public":true,"address":null,"discounts":[],"facility_rooms":[{"href":"/facility-rooms/6e8d7c53"}],"provider":{"href":"/providers/61fae8b0"},"href":"/facilities/e1e9c52e"},"project":{"href":"/projects/438659f0"},"volumes":[{"href":"/storage/8a3369fc"},{"href":"/storage/7f89830d"}],"ip_addresses":[{"id":"47e2d396","address_family":4,"netmask":"255.255.255.254","public":true,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e781ae1b"},"network":"147.75.1.2","address":"147.75.1.3","gateway":"147.75.1.2","href":"/ips/47e2d396"},{"id":"009d5f19","address_family":6,"netmask":"ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe","public":true,"cidr":127,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e781ae1b"},"network":"2604:::::","address":"2604:::::","gateway":"2604:::::","href":"/ips/009d5f19"},{"id":"965c286c","address_family":4,"netmask":"255.255.255.254","public":false,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e781ae1b"},"network":"10.100.1.0","address":"10.100.1.1","gateway":"10.100.1.0","href":"/ips/965c286c"}],"metering_limits":[],"server":{"href":"/hardware/3feec4bd"},"plan":{"id":"e69c0169-4726-46ea-98f1-939c9e8a3607","slug":"baremetal_0","name":"Type 0","description":"Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel Atom C2550 @ 2.4Ghz"}],"memory":{"total":"8GB"},"drives":[{"count":1,"size":"80GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":false,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#c19eb1f6-d283-4d52-a81d-e83c503f7b27"},{"href":"#69525607-1a1e-4843-8f67-1dd83e6bd21a"}],"pricing":{"hour":0.05}},"userdata":"","href":"/devices/e781ae1b"},{"id":"e9a2a61e","short_id":"e9a2a61e","hostname":"test-02","description":null,"state":"active","tags":[],"billing_cycle":"hourly","user":"root","iqn":"iqn.2016-06.net.packet:device.e9a2a61e","locked":true,"bonding_mode":5,"created_at":"2016-06-01T18:56:44Z","updated_at":"2016-06-17T16:23:20Z","staff":true,"allow_pxe":false,"operating_system":{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","clli":null,"description":null,"npanxx":null,"emergency_phone":null,"public":true,"address":null,"discounts":[],"facility_rooms":[{"href":"/facility-rooms/6e8d7c53"}],"provider":{"href":"/providers/61fae8b0"},"href":"/facilities/e1e9c52e"},"project":{"href":"/projects/438659f0"},"volumes":[],"ip_addresses":[{"id":"20865b6b","address_family":4,"netmask":"255.255.255.254","public":true,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e9a2a61e"},"network":"147.75.1.1","address":"147.75.1.1","gateway":"147.75.1.1","href":"/ips/20865b6b"},{"id":"26961028","address_family":6,"netmask":"ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe","public":true,"cidr":127,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e9a2a61e"},"network":"2604:::::","address":"2604:::::","gateway":"2604:::::","href":"/ips/26961028"},{"id":"62d46348","address_family":4,"netmask":"255.255.255.254","public":false,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/e9a2a61e"},"network":"10.100.1.1","address":"10.100.1.1","gateway":"10.100.1.1","href":"/ips/62d46348"}],"metering_limits":[],"server":{"href":"/hardware/3c06c622"},"plan":{"id":"e69c0169-4726-46ea-98f1-939c9e8a3607","slug":"baremetal_0","name":"Type 0","description":"Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel Atom C2550 @ 2.4Ghz"}],"memory":{"total":"8GB"},"drives":[{"count":1,"size":"80GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":false,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#c19eb1f6-d283-4d52-a81d-e83c503f7b27"},{"href":"#69525607-1a1e-4843-8f67-1dd83e6bd21a"}],"pricing":{"hour":0.05}},"userdata":"","href":"/devices/e9a2a61e"}],"meta":{"first":{"href":"/projects/438659f0/devices?page=1"},"previous":null,"self":{"href":"/projects/438659f0/devices?page=1"},"next":null,"last":{"href":"/projects/438659f0/devices?page=1"},"total":2}}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_projects_438659f0_ips.json:
--------------------------------------------------------------------------------
1 | {"ip_addresses": [{
2 | "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be1",
3 | "address_family": 4,
4 | "netmask": "255.255.255.254",
5 | "created_at": "2019-07-18T08:46:38Z",
6 | "details": null,
7 | "tags": [],
8 | "public": true,
9 | "cidr": 31,
10 | "management": true,
11 | "manageable": true,
12 | "enabled": true,
13 | "global_ip": null,
14 | "customdata": {},
15 | "project": {},
16 | "project_lite": {},
17 | "facility": {
18 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
19 | "name": "Amsterdam, NL",
20 | "code": "ams1",
21 | "features": [
22 | "baremetal",
23 | "storage",
24 | "global_ipv4",
25 | "backend_transfer",
26 | "layer_2"
27 | ],
28 | "address": {
29 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
30 | },
31 | "ip_ranges": [
32 | "2604:1380:2000::/36",
33 | "147.75.204.0/23",
34 | "147.75.100.0/22",
35 | "147.75.80.0/22",
36 | "147.75.32.0/23"
37 | ]
38 | },
39 | "assigned_to": {
40 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
41 | },
42 | "interface": {
43 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
44 | },
45 | "network": "147.75.84.94",
46 | "address": "147.75.84.95",
47 | "gateway": "147.75.84.94",
48 | "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1"
49 | },
50 | {
51 | "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be2",
52 | "address_family": 4,
53 | "netmask": "255.255.255.254",
54 | "created_at": "2019-07-18T08:46:38Z",
55 | "details": null,
56 | "tags": [],
57 | "public": true,
58 | "cidr": 31,
59 | "management": true,
60 | "manageable": true,
61 | "enabled": true,
62 | "global_ip": null,
63 | "customdata": {},
64 | "project": {},
65 | "project_lite": {},
66 | "facility": {
67 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
68 | "name": "Amsterdam, NL",
69 | "code": "ams1",
70 | "features": [
71 | "baremetal",
72 | "storage",
73 | "global_ipv4",
74 | "backend_transfer",
75 | "layer_2"
76 | ],
77 | "address": {
78 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
79 | },
80 | "ip_ranges": [
81 | "2604:1380:2000::/36",
82 | "147.75.204.0/23",
83 | "147.75.100.0/22",
84 | "147.75.80.0/22",
85 | "147.75.32.0/23"
86 | ]
87 | },
88 | "assigned_to": {
89 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
90 | },
91 | "interface": {
92 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
93 | },
94 | "network": "147.75.84.94",
95 | "address": "147.75.84.95",
96 | "gateway": "147.75.84.94",
97 | "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1"
98 | }]}
--------------------------------------------------------------------------------
/test/fixtures/get_projects_438659f0_storage.json:
--------------------------------------------------------------------------------
1 | {"volumes":[{"id":"f9a8a263","name":"volume-f9a8a263","description":"test volume 1","size":100,"locked":false,"billing_cycle":"hourly","state":"active","created_at":"2016-05-22T13:40:06Z","updated_at":"2016-06-17T18:36:06Z","access":{"ips":["10.144.1.1","10.144.1.2"],"iqn":"iqn.2013-05.com.daterainc:tc:01:sn:"},"project":{"href":"/projects/438659f0"},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","address":null,"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"snapshot_policies":[{"href":"/storage/snapshot-policies/73a5252a"}],"attachments":[],"plan":{"id":"87728148-3155-4992-a730-8d1e6aca8a32","slug":"storage_1","name":"Standard","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#63f8bcdb-06ce-4c27-b1bb-22de20ae8ba6"}],"pricing":{"hour":0.000104}},"href":"/storage/f9a8a263"},{"id":"f2df1dd1","name":"volume-f2df1dd1","description":"test volume 2","size":10,"locked":false,"billing_cycle":"hourly","state":"active","created_at":"2016-06-04T20:55:11Z","updated_at":"2016-06-04T20:58:16Z","access":{"ips":["10.144.1.2","10.144.1.2"],"iqn":"iqn.2013-05.com.daterainc:tc:01:sn:"},"project":{"href":"/projects/438659f0"},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"address":null,"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"snapshot_policies":[],"attachments":[{"id":"cdafcbfa","created_at":"2016-06-04T20:58:16Z","volume":{"href":"/storage/f2df1dd1"},"device":{"id":"b5b4f31f","short_id":"b5b4f31f","hostname":"test-node-01","description":null,"state":"active","tags":[],"billing_cycle":"hourly","user":"root","iqn":"iqn.2016-06.net.packet:device.b5b4f31f","locked":true,"bonding_mode":5,"created_at":"2016-06-01T19:27:28Z","updated_at":"2016-06-21T20:28:24Z","allow_pxe":false,"operating_system":{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"address":null,"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"project":{"href":"/projects/438659f0"},"volumes":[{"href":"/storage/f2df1dd1"},{"href":"/storage/bf595211"}],"ip_addresses":[{"id":"a2660aa4","address_family":4,"netmask":"255.255.255.254","public":true,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/b5b4f31f"},"network":"147.75.1.1","address":"147.75.1.1","gateway":"147.75.1.1","href":"/ips/a2660aa4"},{"id":"aa2010b3","address_family":6,"netmask":"ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe","public":true,"cidr":127,"management":true,"manageable":true,"assigned_to":{"href":"/devices/b5b4f31f"},"network":"2604:::::","address":"2604:::::","gateway":"2604:::::","href":"/ips/aa2010b3"},{"id":"d0263124","address_family":4,"netmask":"255.255.255.254","public":false,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/b5b4f31f"},"network":"10.100.1.1","address":"10.100.1.1","gateway":"10.100.1.1","href":"/ips/d0263124"}],"metering_limits":[],"server":{"href":"/hardware/6fe0f5d5"},"plan":{"id":"e69c0169-4726-46ea-98f1-939c9e8a3607","slug":"baremetal_0","name":"Type 0","description":"Our Type 0 configuration is a general use \"cloud killer\" server, with a Intel Atom 2.4Ghz processor and 8GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel Atom C2550 @ 2.4Ghz"}],"memory":{"total":"8GB"},"drives":[{"count":1,"size":"80GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":false,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#c19eb1f6-d283-4d52-a81d-e83c503f7b27"},{"href":"#69525607-1a1e-4843-8f67-1dd83e6bd21a"}],"pricing":{"hour":0.05}},"userdata":"","href":"/devices/b5b4f31f"},"href":"/storage/attachments/cdafcbfa"}],"plan":{"id":"87728148-3155-4992-a730-8d1e6aca8a32","slug":"storage_1","name":"Standard","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#63f8bcdb-06ce-4c27-b1bb-22de20ae8ba6"}],"pricing":{"hour":0.000104}},"href":"/storage/f2df1dd1"}],"meta":{"first":{"href":"/projects/438659f0/storage?page=1"},"previous":null,"self":{"href":"/projects/438659f0/storage?page=1"},"next":null,"last":{"href":"/projects/438659f0/storage?page=1"},"total":2}}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_projects_438659f1_ips.json:
--------------------------------------------------------------------------------
1 | {"ip_addresses": [{
2 | "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be1",
3 | "address_family": 4,
4 | "netmask": "255.255.255.254",
5 | "created_at": "2019-07-18T08:46:38Z",
6 | "details": null,
7 | "tags": [],
8 | "public": true,
9 | "cidr": 31,
10 | "management": true,
11 | "manageable": true,
12 | "enabled": true,
13 | "global_ip": null,
14 | "customdata": {},
15 | "project": {},
16 | "project_lite": {},
17 | "facility": null,
18 | "assigned_to": {
19 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
20 | },
21 | "interface": {
22 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
23 | },
24 | "network": "147.75.84.94",
25 | "address": "147.75.84.95",
26 | "gateway": "147.75.84.94",
27 | "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1"
28 | },
29 | {
30 | "id": "99d5d741-3756-4ebe-a014-34ea7a2e2be2",
31 | "address_family": 4,
32 | "netmask": "255.255.255.254",
33 | "created_at": "2019-07-18T08:46:38Z",
34 | "details": null,
35 | "tags": [],
36 | "public": true,
37 | "cidr": 31,
38 | "management": true,
39 | "manageable": true,
40 | "enabled": true,
41 | "global_ip": null,
42 | "customdata": {},
43 | "project": {},
44 | "project_lite": {},
45 | "facility": {
46 | "id": "8e6470b3-b75e-47d1-bb93-45b225750975",
47 | "name": "Amsterdam, NL",
48 | "code": "ams1",
49 | "features": [
50 | "baremetal",
51 | "storage",
52 | "global_ipv4",
53 | "backend_transfer",
54 | "layer_2"
55 | ],
56 | "address": {
57 | "href": "#0688e909-647e-4b21-bdf2-fc056d993fc5"
58 | },
59 | "ip_ranges": [
60 | "2604:1380:2000::/36",
61 | "147.75.204.0/23",
62 | "147.75.100.0/22",
63 | "147.75.80.0/22",
64 | "147.75.32.0/23"
65 | ]
66 | },
67 | "assigned_to": {
68 | "href": "/devices/54ffd20d-d972-4c8d-8628-9da18e67ae17"
69 | },
70 | "interface": {
71 | "href": "/ports/02ea0556-df04-4554-b339-760a0d227b44"
72 | },
73 | "network": "147.75.84.94",
74 | "address": "147.75.84.95",
75 | "gateway": "147.75.84.94",
76 | "href": "/ips/99d5d741-3756-4ebe-a014-34ea7a2e2be1"
77 | }]}
--------------------------------------------------------------------------------
/test/fixtures/get_ssh-keys.json:
--------------------------------------------------------------------------------
1 | {"ssh_keys":[{"id":"084a5dec-30be-415a-8937-9c615932e459","label":"tzara","key":"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo2AptpeHO53KoVwfnOG+aHUQkXlE0Y4GUmtLdP2ofV+GnJsEnmbQNi0FRQT4qeHmdKH2caYbQh275QB8e6ROJdgEO97AaHCJ2XaGI8Eq3AqIfzuUE2lDW2k1ck/94GXVTCzDNtBf5FVVP51RxlxNgSSwkBRh2SoiezoOr/uClGoBvEM0tSDChVINkl4Sq73hVeOc2gT+ehag9kx+hv4FMvrMTJJ5WvdBwfKaWniQWcsOE61ae4vaPPMs9a8wyQ3gC9m8Y7Nsi8ylJYpC+wPzwVm/TZUaVe58r8N2kecgPMUhUD9ySwMOnIOGn4zZxcRJR4vTzbOhwQ3wImqugajpCw== welch@tzara","fingerprint":"ce:65:20:86:d2:fa:34:12:74:b9:54:b2:30:d3:51:2d","created_at":"2015-02-15T16:37:46Z","updated_at":"2015-02-15T16:37:46Z","owner":{"href":"/users/1140617d-262d-4502-a3d6-771d83c930da"},"href":"/ssh-keys/084a5dec-30be-415a-8937-9c615932e459"},{"id":"a3d8bebe-574f-427d-80ee-bc2ba17f7074","label":"canary","key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqRMl8kmT0Xwg2ZWdNIsPtMlEMPc5TzV4eFkhRaSESkVe0KSZyrwUpaJa8/A/Bg4t3iXNd/T+JuxVJJHRupstwZSVgSERqXJi933SlLrYsOKbUpjFD0ecaL1YepHi4Bt8epZtJoLFykcDxYGTvR3wMtwkPI56ijfeaE61AAYU3teCG8xa8mPAOSD6rHfrWCcrkyu3IiY08t7mgB9Rx34/nmy4aXmu3vlsNXi16NAPbZ9cWIsgaOD+wmOwL2dpGFt/82mWuvSAEsJiyQOKyGbnUs4mXxOplYBHCpUyg2nuCO67B0H8tdv+Gjm3hBM6tXMGF3EA3gi8k3dcSiqd2BNLl root@canary.packet.net","fingerprint":"51:24:c3:3a:cd:b9:31:5d:65:09:93:b7:6e:da:3f:fa","created_at":"2016-03-24T18:57:18Z","updated_at":"2016-03-24T18:57:18Z","owner":{"href":"/users/1140617d-262d-4502-a3d6-771d83c930da"},"href":"/ssh-keys/a3d8bebe-574f-427d-80ee-bc2ba17f7074"}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_ssh-keys_084a5dec.json:
--------------------------------------------------------------------------------
1 | {"id":"084a5dec","label":"test key","key":"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo2AptpeHO53KoVwfnOG+aHUQkXlE0Y4GUmtLdP2ofV+GnJsEnmbQNi0FRQT4qeHmdKH2caYbQh275QB8e6ROJdgEO97AaHCJ2XaGI8Eq3AqIfzuUE2lDW2k1ck/94GXVTCzDNtBf5FVVP51RxlxNgSSwkBRh2SoiezoOr/uClGoBvEM0tSDChVINkl4Sq73hVeOc2gT+ehag9kx+hv4FMvrMTJJ5WvdBwfKaWniQWcsOE61ae4vaPPMs9a8wyQ3gC9m8Y7Nsi8ylJYpC+wPzwVm/TZUaVe58r8N2kecgPMUhUD9ySwMOnIOGn4zZxcRJR4vTzbOhwQ3wImqugajpCw== welch@tzara","fingerprint":"ce:65:20:86:d2:fa:34:12:74:b9:54:b2:30:d3:51:2d","created_at":"2015-02-15T16:37:46Z","updated_at":"2015-02-15T16:37:46Z","owner":{"href":"/users/1140617d"},"href":"/ssh-keys/084a5dec"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_storage_f9a8a263.json:
--------------------------------------------------------------------------------
1 | {"id":"f9a8a263","name":"volume-f9a8a263","description":"volume description","size":100,"locked":false,"billing_cycle":"hourly","state":"active","created_at":"2016-05-22T13:40:06Z","updated_at":"2016-06-17T18:36:06Z","access":{"ips":["10.144.1.1","10.144.1.2"],"iqn":"iqn.2013-05.com.daterainc:tc:01:sn:"},"project":{"href":"/projects/438659f0"},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","clli":null,"description":null,"npanxx":null,"emergency_phone":null,"public":true,"address":null,"discounts":[],"facility_rooms":[{"href":"/facility-rooms/6e8d7c53"}],"provider":{"href":"/providers/61fae8b0"},"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"snapshot_policies":[{"href":"/storage/snapshot-policies/73a5252a"}],"attachments":[{"href":"63f8bcdb"}],"plan":{"id":"87728148-3155-4992-a730-8d1e6aca8a32","slug":"storage_0","name":"Standard","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#63f8bcdb"}],"pricing":{"hour":0.000004}},"href":"/storage/f9a8a263"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_storage_f9a8a263_snapshots.json:
--------------------------------------------------------------------------------
1 | {"snapshots":[{"id":"8ddc76fc","status":"available","timestamp":"1467921608.991500438","created_at":"2016-07-07T20:00:08.991Z","volume":{"href":"/storage/a2be91ef"}},{"id":"141ffa69","status":"available","timestamp":"1468411189.988721398","created_at":"2016-07-13T11:59:49.988Z","volume":{"href":"/storage/a2be91ef"}},{"id":"0533695e","status":"available","timestamp":"1468414789.989156024","created_at":"2016-07-13T12:59:49.989Z","volume":{"href":"/storage/a2be91ef"}},{"id":"4ed6005e","status":"available","timestamp":"1468418389.988653005","created_at":"2016-07-13T13:59:49.988Z","volume":{"href":"/storage/a2be91ef"}},{"id":"0d029893","status":"available","timestamp":"1468421989.985048469","created_at":"2016-07-13T14:59:49.985Z","volume":{"href":"/storage/a2be91ef"}},{"id":"83153524","status":"available","timestamp":"1468425589.98895032","created_at":"2016-07-13T15:59:49.988Z","volume":{"href":"/storage/a2be91ef"}},{"id":"a2cd093f","status":"available","timestamp":"1468429189.988680424","created_at":"2016-07-13T16:59:49.988Z","volume":{"href":"/storage/a2be91ef"}},{"id":"681b1717","status":"available","timestamp":"1468432789.985096888","created_at":"2016-07-13T17:59:49.985Z","volume":{"href":"/storage/a2be91ef"}}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/get_user.json:
--------------------------------------------------------------------------------
1 | {"id":"1140617d","short_id":"1140617d","first_name":"Aaron","last_name":"Welch","full_name":"Aaron Welch","email":"welch@packet.net","two_factor_auth":"sms","avatar_url":"https://www.gravatar.com/avatar/d248672800ce5311a76f8d6bbb3a03ed?d=https://app.packet.net/client-portal/assets/images/default_gravatar.png","max_projects":null,"credit_amount":0.0,"created_at":"2015-02-05T19:09:00Z","updated_at":"2016-07-12T15:44:04Z","timezone":"America/New_York","verified_at":"2015-02-05T19:09:00Z","verification_stage":"verified","last_login_at":"2016-07-12T18:17:13Z","features":["advanced_ips","credits_migrated","invoices_migrated","block_storage"],"title":null,"company_name":"Packet","company_url":"https://www.packet.net","fraud_details":null,"social_accounts":{},"level":{"name":"","max_projects":null,"max_services_per_project":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"storage_1":null,"storage_2":null},"max_instances_per_project":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"storage_1":null,"storage_2":null},"slug":"staff"},"emails":[{"href":"/emails/661d3b15"}],"projects":[{"href":"/projects/0491f260"},{"href":"/projects/746ea6dd"}],"payment_methods":[{"href":"/payment-methods/b2bb4ee0"}],"price_policies":[],"discounts":[],"metering_limits":[],"last_login_from":{"href":"/applications/9c7d0b35"},"company_address":null,"coupon_usages":[],"href":"/users/1140617d","phone_number":"","otp_uri":""}
2 |
--------------------------------------------------------------------------------
/test/fixtures/patch_devices_e781ae1b.json:
--------------------------------------------------------------------------------
1 | {"id":"9dec7266","short_id":"9dec7266","hostname":"test123","description":null,"state":"provisioning","tags":[],"billing_cycle":"hourly","user":"root","iqn":"iqn.2016-07.net.packet:device.9dec7266","locked":false,"bonding_mode":4,"created_at":"2016-07-12T19:44:19Z","updated_at":"2016-07-12T19:47:22Z","staff":true,"provisioning_percentage":57.1428571428571,"allow_pxe":true,"operating_system":{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","clli":null,"description":null,"npanxx":null,"emergency_phone":null,"public":true,"address":null,"discounts":[],"facility_rooms":[{"href":"/facility-rooms/6e8d7c53-cef1-4c95-8ee7-eca91bf704ba"}],"provider":{"href":"/providers/61fae8b0-3ca5-4fcc-9148-4309dbf646fa"},"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"project":{"href":"/projects/438659f0-3c3b-4eb0-8737-e39f678b964a"},"volumes":[],"ip_addresses":[{"id":"2ac5c64d-6ddd-4caa-ad95-571c1eba97d5","address_family":4,"netmask":"255.255.255.254","public":true,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/9dec7266-e447-4cd9-9490-b2128ad6dc04"},"network":"147.75.196.166","address":"147.75.196.167","gateway":"147.75.196.166","href":"/ips/2ac5c64d-6ddd-4caa-ad95-571c1eba97d5"},{"id":"b340b44f-eb19-4b0d-bff5-de74f37868d4","address_family":6,"netmask":"ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe","public":true,"cidr":127,"management":true,"manageable":true,"assigned_to":{"href":"/devices/9dec7266-e447-4cd9-9490-b2128ad6dc04"},"network":"2604:1380:1:c500::4","address":"2604:1380:1:c500::5","gateway":"2604:1380:1:c500::4","href":"/ips/b340b44f-eb19-4b0d-bff5-de74f37868d4"},{"id":"7087ce4c-9fa6-48aa-bea5-1d8460e059c4","address_family":4,"netmask":"255.255.255.254","public":false,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/9dec7266-e447-4cd9-9490-b2128ad6dc04"},"network":"10.100.84.4","address":"10.100.84.5","gateway":"10.100.84.4","href":"/ips/7087ce4c-9fa6-48aa-bea5-1d8460e059c4"}],"provisioning_events":[{"id":"a9b31a00-3579-42c8-9039-5a9e36cd413f","type":"provisioning.101","body":"Provisioning started","created_at":"2016-07-12T19:44:19Z","context":null,"whodunnit":null,"relationships":[{"href":"#74550b01-6839-4974-b0a7-672320334535"}],"interpolated":"Provisioning started","href":"/events/a9b31a00-3579-42c8-9039-5a9e36cd413f"},{"id":"4dc7090c-83a5-43af-abb8-596c53d759f9","type":"provisioning.102","body":"Network configured with addresses 147.75.196.167, 2604:1380:1:c500::5, and 10.100.84.5","created_at":"2016-07-12T19:45:45Z","context":null,"whodunnit":null,"relationships":[{"href":"#9e978000-45d1-4262-92f0-054c803b778c"}],"interpolated":"Network configured with addresses 147.75.196.167, 2604:1380:1:c500::5, and 10.100.84.5","href":"/events/4dc7090c-83a5-43af-abb8-596c53d759f9"},{"id":"ab7c6b7d-56aa-4cc7-8e3c-d6b917d16765","type":"provisioning.103","body":"Configuration written, restarting device","created_at":"2016-07-12T19:45:56Z","context":null,"whodunnit":null,"relationships":[{"href":"#c4913f52-1d61-47ba-a8d8-e68e0f6909ba"}],"interpolated":"Configuration written, restarting device","href":"/events/ab7c6b7d-56aa-4cc7-8e3c-d6b917d16765"},{"id":"e8974940-d32d-4b02-b7d6-84b425a97f3e","type":"provisioning.104","body":"Connected to magic install system","created_at":"2016-07-12T19:47:22Z","context":{"ip":"198.41.235.149","app_id":"84913430-7aa2-4c9f-91df-8d38c98beb2a","request_id":"f33a20f8-ca42-43cf-88e5-310ec6efe138","user_agent":"Go-http-client/1.1"},"whodunnit":{"href":"/users/0ff7f54a-8999-4102-8c74-f64813ebc57c"},"relationships":[{"href":"#69642f8e-5f12-4add-be0f-db563447db83"}],"interpolated":"Connected to magic install system","href":"/events/e8974940-d32d-4b02-b7d6-84b425a97f3e"},{"id":null,"type":"provisioning.107","body":"Server networking interfaces configured","created_at":null,"context":null,"whodunnit":null,"relationships":[],"interpolated":"Server networking interfaces configured"},{"id":null,"type":"provisioning.109","body":"Installation finished, rebooting server","created_at":null,"context":null,"whodunnit":null,"relationships":[],"interpolated":"Installation finished, rebooting server"},{"id":null,"type":"provisioning.109","body":"Installation finished, rebooting server","created_at":null,"context":null,"whodunnit":null,"relationships":[],"interpolated":"Installation finished, rebooting server"}],"metering_limits":[],"server":{"href":"/hardware/2fa510fb-c520-4c9c-ab29-8d9c760d78d6"},"plan":{"id":"6d1f1ffa-7912-4b78-b50d-88cc7c8ab40f","slug":"baremetal_1","name":"Type 1","description":"Our Type 1 configuration is a zippy general use server, with an Intel E3-1240 v3 processor and 32GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel E3-1240 v3"}],"memory":{"total":"32GB"},"drives":[{"count":2,"size":"120GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":true,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f-fa18-47c0-aba7-222a842362fd"},{"href":"/facilities/8e6470b3-b75e-47d1-bb93-45b225750975"},{"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"}],"plan_versions":[{"href":"#fe2790c8-8e8f-4997-bbf5-2fe59f98e007"},{"href":"#7facb54b-1b89-4f44-a6b3-b0dab1125a2f"},{"href":"#4f5ec112-f740-461e-8937-1068ba5fe346"}],"pricing":{"hour":0.4}},"userdata":"","root_password":"2fjyehdks3","href":"/devices/9dec7266-e447-4cd9-9490-b2128ad6dc04","crypted_root_password":"$6$z3ldgx5x3co71IDk$ksrNsz0zZwOPL8WdYrWMAU8R0LYTGc84Q6Vv2iJLdQb9KGLGNO6w820tgef2ZhEmgjN5qXDug/1HUvg2xJsNa/"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/patch_projects_438659f0.json:
--------------------------------------------------------------------------------
1 | {"id":"438659f0","name":"edited name","created_at":"2016-05-22T13:23:11Z","updated_at":"2016-07-05T12:52:25Z","max_devices":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"storage_1":null,"storage_2":null},"auto_charge":true,"billable":true,"staff":false,"members":[{"href":"/users/1140617d"}],"memberships":[{"href":"/memberships/2f06a0b7"}],"invitations":[],"payment_method":{"href":"/payment-methods/b2bb4ee0"},"devices":[{"href":"/devices/e781ae1b"},{"href":"/devices/e9a2a61e"},{"href":"/devices/b5b4f31f"}],"ssh_keys":[{"href":"/ssh-keys/a3d8bebe"},{"href":"/ssh-keys/084a5dec"}],"volumes":[{"href":"/storage/f9a8a263"},{"href":"/storage/f2df1dd1"},{"href":"/storage/bf595211"},{"href":"/storage/7f89830d"},{"href":"/storage/8a3369fc"}],"price_policies":[],"discounts":[],"metering_limits":[],"href":"/projects/438659f0"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/patch_ssh-keys_084a5dec.json:
--------------------------------------------------------------------------------
1 | {"id":"084a5dec","label":"updated label","key":"ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAo2AptpeHO53KoVwfnOG+aHUQkXlE0Y4GUmtLdP2ofV+GnJsEnmbQNi0FRQT4qeHmdKH2caYbQh275QB8e6ROJdgEO97AaHCJ2XaGI8Eq3AqIfzuUE2lDW2k1ck/94GXVTCzDNtBf5FVVP51RxlxNgSSwkBRh2SoiezoOr/uClGoBvEM0tSDChVINkl4Sq73hVeOc2gT+ehag9kx+hv4FMvrMTJJ5WvdBwfKaWniQWcsOE61ae4vaPPMs9a8wyQ3gC9m8Y7Nsi8ylJYpC+wPzwVm/TZUaVe58r8N2kecgPMUhUD9ySwMOnIOGn4zZxcRJR4vTzbOhwQ3wImqugajpCw==","fingerprint":"ce:65:20:86:d2:fa:34:12:74:b9:54:b2:30:d3:51:2d","created_at":"2015-02-15T16:37:46Z","updated_at":"2015-02-15T16:37:46Z","owner":{"href":"/users/1140617d"},"href":"/ssh-keys/084a5dec"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/patch_storage_f9a8a263.json:
--------------------------------------------------------------------------------
1 | {"id":"f9a8a263","name":"volume-f9a8a263","description":"updated description","size":100,"locked":false,"billing_cycle":"hourly","state":"active","created_at":"2016-05-22T13:40:06Z","updated_at":"2016-06-17T18:36:06Z","access":{"ips":["10.144.1.1","10.144.1.2"],"iqn":"iqn.2013-05.com.daterainc:tc:01:sn:"},"project":{"href":"/projects/438659f0"},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"address":null,"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"snapshot_policies":[{"href":"/storage/snapshot-policies/73a5252a"}],"attachments":[],"plan":{"id":"87728148-3155-4992-a730-8d1e6aca8a32","slug":"storage_0","name":"Standard","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#63f8bcdb"}],"pricing":{"hour":0.000004}},"href":"/storage/f9a8a263"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post__capacity.json:
--------------------------------------------------------------------------------
1 | {"servers":[{"facility":"ewr1","plan":"baremetal_0","quantity":10,"available":true}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post__capacity_metros.json:
--------------------------------------------------------------------------------
1 | {"servers":[{"metro":"sv","plan":"baremetal_0","quantity":10,"available":true}]}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_devices_e781ae1b_actions.json:
--------------------------------------------------------------------------------
1 | null
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_projects.json:
--------------------------------------------------------------------------------
1 | {"id":"438659f0","name":"test project","created_at":"2016-05-22T13:23:11Z","updated_at":"2016-07-05T12:52:25Z","max_devices":{"baremetal_0":null,"baremetal_1":null,"baremetal_2":null,"baremetal_3":null,"storage_1":null,"storage_2":null},"auto_charge":true,"billable":true,"staff":true,"members":[{"href":"/users/1140617d"}],"memberships":[{"href":"/memberships/2f06a0b7"}],"invitations":[],"payment_method":{"href":"/payment-methods/b2bb4ee0"},"devices":[],"ssh_keys":[{"href":"/ssh-keys/a3d8bebe"},{"href":"/ssh-keys/084a5dec"}],"volumes":[],"price_policies":[],"discounts":[],"metering_limits":[],"href":"/projects/438659f0"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_projects_438659f0_devices.json:
--------------------------------------------------------------------------------
1 | {"id":"9dec7266","short_id":"9dec7266","hostname":"test123","description":null,"state":"provisioning","tags":[],"billing_cycle":"hourly","user":"root","iqn":"iqn.2016-07.net.packet:device.9dec7266","locked":false,"bonding_mode":4,"created_at":"2016-07-12T19:44:19Z","updated_at":"2016-07-12T19:47:22Z","staff":true,"provisioning_percentage":57.1428571428571,"allow_pxe":true,"operating_system":{"slug":"ubuntu_14_04","name":"Ubuntu 14.04 LTS (legacy)","distro":"ubuntu","version":"14.04","provisionable_on":["baremetal_0","baremetal_1","baremetal_2","baremetal_3"]},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"internal_name":"Parsippany, NJ","clli":null,"description":null,"npanxx":null,"emergency_phone":null,"public":true,"address":null,"discounts":[],"facility_rooms":[{"href":"/facility-rooms/6e8d7c53"}],"provider":{"href":"/providers/61fae8b0"},"href":"/facilities/e1e9c52e"},"project":{"href":"/projects/438659f0"},"volumes":[],"ip_addresses":[{"id":"2ac5c64d","address_family":4,"netmask":"255.255.255.254","public":true,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/9dec7266"},"network":"147.75.1.1","address":"147.75.1.1","gateway":"147.75.1.1","href":"/ips/2ac5c64d"},{"id":"b340b44f","address_family":6,"netmask":"ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe","public":true,"cidr":127,"management":true,"manageable":true,"assigned_to":{"href":"/devices/9dec7266"},"network":"2604:::::","address":"2604:::::","gateway":"2604:::::","href":"/ips/b340b44f"},{"id":"7087ce4c","address_family":4,"netmask":"255.255.255.254","public":false,"cidr":31,"management":true,"manageable":true,"assigned_to":{"href":"/devices/9dec7266"},"network":"10.100.0.1","address":"10.100.0.1","gateway":"10.100.0.1","href":"/ips/7087ce4c"}],"provisioning_events":[{"id":"a9b31a00","type":"provisioning.101","body":"Provisioning started","created_at":"2016-07-12T19:44:19Z","context":null,"whodunnit":null,"relationships":[{"href":"#74550b01"}],"interpolated":"Provisioning started","href":"/events/a9b31a00"},{"id":"4dc7090c","type":"provisioning.102","body":"Network configured with addresses 147.75.1.1, 2604:::::, and 10.100.1.1","created_at":"2016-07-12T19:45:45Z","context":null,"whodunnit":null,"relationships":[{"href":"#9e978000"}],"interpolated":"Network configured with addresses 147.75.1.1, 2604:::::, and 10.100.1.1","href":"/events/4dc7090c"},{"id":"ab7c6b7d","type":"provisioning.103","body":"Configuration written, restarting device","created_at":"2016-07-12T19:45:56Z","context":null,"whodunnit":null,"relationships":[{"href":"#c4913f52"}],"interpolated":"Configuration written, restarting device","href":"/events/ab7c6b7d"},{"id":"e8974940","type":"provisioning.104","body":"Connected to magic install system","created_at":"2016-07-12T19:47:22Z","context":{"ip":"198.41.1.1","app_id":"84913430","request_id":"f33a20f8","user_agent":"Go-http-client/1.1"},"whodunnit":{"href":"/users/0ff7f54a"},"relationships":[{"href":"#69642f8e"}],"interpolated":"Connected to magic install system","href":"/events/e8974940"},{"id":null,"type":"provisioning.107","body":"Server networking interfaces configured","created_at":null,"context":null,"whodunnit":null,"relationships":[],"interpolated":"Server networking interfaces configured"},{"id":null,"type":"provisioning.109","body":"Installation finished, rebooting server","created_at":null,"context":null,"whodunnit":null,"relationships":[],"interpolated":"Installation finished, rebooting server"},{"id":null,"type":"provisioning.109","body":"Installation finished, rebooting server","created_at":null,"context":null,"whodunnit":null,"relationships":[],"interpolated":"Installation finished, rebooting server"}],"metering_limits":[],"server":{"href":"/hardware/2fa510fb"},"plan":{"id":"6d1f1ffa","slug":"baremetal_1","name":"Type 1","description":"Our Type 1 configuration is a zippy general use server, with an Intel E3-1240 v3 processor and 32GB of RAM.","line":"baremetal","specs":{"cpus":[{"count":1,"type":"Intel E3-1240 v3"}],"memory":{"total":"32GB"},"drives":[{"count":2,"size":"120GB","type":"SSD"}],"nics":[{"count":2,"type":"1Gbps"}],"features":{"raid":true,"txt":true}},"available_in":[{"href":"/facilities/2b70eb8f"},{"href":"/facilities/8e6470b3"},{"href":"/facilities/e1e9c52e"}],"plan_versions":[{"href":"#fe2790c8"},{"href":"#7facb54b"},{"href":"#4f5ec112"}],"pricing":{"hour":0.4}},"userdata":"","root_password":"","href":"/devices/9dec7266","crypted_root_password":""}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_projects_438659f0_storage.json:
--------------------------------------------------------------------------------
1 | {"id":"f9a8a263","name":"volume-f9a8a263","description":"volume description","size":100,"locked":false,"billing_cycle":"hourly","state":"active","created_at":"2016-05-22T13:40:06Z","updated_at":"2016-06-17T18:36:06Z","access":{"ips":["10.144.1.1","10.144.1.2"],"iqn":"iqn.2013-05.com.daterainc:tc:01:sn:"},"project":{"href":"/projects/438659f0"},"facility":{"id":"e1e9c52e-a0bc-4117-b996-0fc94843ea09","name":"Parsippany, NJ","code":"ewr1","features":["baremetal","storage"],"address":null,"href":"/facilities/e1e9c52e-a0bc-4117-b996-0fc94843ea09"},"snapshot_policies":[{"href":"/storage/snapshot-policies/73a5252a"}],"attachments":[],"plan":{"id":"87728148-3155-4992-a730-8d1e6aca8a32","slug":"storage_0","name":"Standard","description":"TBD","line":"storage","specs":{},"available_in":[],"plan_versions":[{"href":"#63f8bcdb"}],"pricing":{"hour":0.000004}},"href":"/storage/f9a8a263"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_ssh-keys.json:
--------------------------------------------------------------------------------
1 | {"id":"084a5dec","label":"sshkey-name","key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDI4pIqzpb5g3992h+yr527VRcaB68KE4vPjWPPoiQws49KIs2NMcOzS9QE4641uW1u5ML2HgQdfYKMF/YFGnI1Y6xV637DjhDyZYV9LasUH49npSSJjsBcsk9JGfUpNAOdcgpFzK8V90eiOrOC5YncxdwwG8pwjFI9nNVPCl4hYEu1iXdyysHvkFfS2fklsNjLWrzfafPlaen+qcBxygCA0sFdW/7er50aJeghdBHnE2WhIKLUkJxnKadznfAge7oEe+3LLAPfP+3yHyvp2+H0IzmVfYvAjnzliYetqQ8pg5ZW2BiJzvqz5PebGS70y/ySCNW1qQmJURK/Wc1bt9en","fingerprint":"ce:65:20:86:d2:fa:34:12:74:b9:54:b2:30:d3:51:2d","created_at":"2015-02-15T16:37:46Z","updated_at":"2015-02-15T16:37:46Z","owner":{"href":"/users/1140617d"},"href":"/ssh-keys/084a5dec"}
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_storage_f9a8a263_attachments.json:
--------------------------------------------------------------------------------
1 | null
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_storage_f9a8a263_clone.json:
--------------------------------------------------------------------------------
1 | null
2 |
--------------------------------------------------------------------------------
/test/fixtures/post_storage_f9a8a263_snapshots.json:
--------------------------------------------------------------------------------
1 | null
2 |
--------------------------------------------------------------------------------
/test/test_baseapi.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import sys
5 | import unittest
6 |
7 | import packet
8 |
9 |
10 | class obj(object):
11 | def __init__(self, dict_):
12 | self.__dict__.update(dict_)
13 |
14 |
15 | class ErrorTest(unittest.TestCase):
16 | def test_cause(self):
17 | msg = "boom"
18 | cause = "cause"
19 | error = packet.Error(msg, cause)
20 | self.assertIn(error.cause, cause)
21 |
22 |
23 | class BaseAPITest(unittest.TestCase):
24 | def setUp(self):
25 | self.auth_token = "fake_auth_token"
26 | self.consumer_token = "fake_consumer_token"
27 | self.end_point = "api.packet.net"
28 | self._user_agent_prefix = "fake_user_agent"
29 |
30 | def test_init_all(self):
31 | base = packet.baseapi.BaseAPI(
32 | self.auth_token, self.consumer_token, self._user_agent_prefix
33 | )
34 | self.assertEqual(base.end_point, self.end_point)
35 | self.assertEqual(base.auth_token, self.auth_token)
36 | self.assertEqual(base.consumer_token, self.consumer_token)
37 | self.assertEqual(base._user_agent_prefix, self._user_agent_prefix)
38 |
39 | def test_call_api_with_end_point(self):
40 | base = packet.baseapi.BaseAPI(
41 | self.auth_token, self.consumer_token, self._user_agent_prefix
42 | )
43 |
44 | if int(sys.version[0]) == 3:
45 | self.assertRaisesRegex(
46 | packet.Error,
47 | "method type not recognized as one of",
48 | base.call_api,
49 | "fake_path",
50 | "bad_method",
51 | )
52 |
53 |
54 | class ResponseErrorTest(unittest.TestCase):
55 | def setUp(self):
56 | self.resp500 = obj({"status_code": 500})
57 | self.errBoom = {"error": "boom"}
58 | self.errBangBoom = {"errors": ["bang", "boom"]}
59 | self.exception = Exception("x")
60 |
61 | def test_init_empty(self):
62 | error = packet.ResponseError(self.resp500, None, None)
63 | self.assertIn("empty", str(error))
64 |
65 | def test_init_string(self):
66 | error = packet.ResponseError(self.resp500, "whoops", None)
67 | self.assertIn("whoops", str(error))
68 |
69 | def test_init_error(self):
70 | error = packet.ResponseError(self.resp500, self.errBoom, self.exception)
71 | self.assertIn("Error 500: boom", str(error))
72 | self.assertEqual(500, error.response.status_code)
73 | self.assertEqual(self.exception, error.cause)
74 |
75 | def test_init_errors(self):
76 | error = packet.ResponseError(self.resp500, self.errBangBoom, self.exception)
77 | self.assertIn("Error 500: bang, boom", str(error))
78 | self.assertEqual(500, error.response.status_code)
79 | self.assertEqual(self.exception, error.cause)
80 |
81 |
82 | if __name__ == "__main__":
83 | sys.exit(unittest.main())
84 |
--------------------------------------------------------------------------------
/test/test_batch.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import time
7 | import unittest
8 | import packet
9 |
10 | from datetime import datetime
11 |
12 |
13 | @unittest.skipIf(
14 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
15 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
16 | )
17 | class TestBatches(unittest.TestCase):
18 | @classmethod
19 | def setUpClass(self):
20 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
21 | org_id = self.manager.list_organizations()[0].id
22 | self.project = self.manager.create_organization_project(
23 | org_id=org_id,
24 | name="Int-Tests-Batch_{}".format(
25 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
26 | ),
27 | )
28 | self.batches = list()
29 |
30 | def test_create_batch(self):
31 | params = list()
32 | batch01 = packet.DeviceBatch(
33 | {
34 | "hostname": "batchtest01",
35 | "quantity": 1,
36 | "facility": "ewr1",
37 | "operating_system": "centos_7",
38 | "plan": "baremetal_0",
39 | }
40 | )
41 |
42 | params.append(batch01)
43 | data = self.manager.create_batch(project_id=self.project.id, params=params)
44 | self.batches = data
45 | time.sleep(10)
46 |
47 | def test_list_batches(self):
48 | self.manager.list_batches(project_id=self.project.id)
49 |
50 | def test_delete_batches(self):
51 | self.batches = self.manager.list_batches(project_id=self.project.id)
52 | for batch in self.batches:
53 | self.manager.delete_batch(batch.id, remove_associated_instances=True)
54 |
55 | @classmethod
56 | def tearDownClass(self):
57 | devices = self.manager.list_devices(project_id=self.project.id)
58 | for device in devices:
59 | if device.hostname == "batchtest01":
60 | if device.state != "active":
61 | while True:
62 | if self.manager.get_device(device.id).state != "active":
63 | time.sleep(2)
64 | else:
65 | device.delete()
66 | break
67 | self.project.delete()
68 |
69 |
70 | if __name__ == "__main__":
71 | sys.exit(unittest.main())
72 |
--------------------------------------------------------------------------------
/test/test_device.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import unittest
7 | import time
8 | import packet
9 |
10 | from datetime import datetime
11 |
12 |
13 | @unittest.skipIf(
14 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
15 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
16 | )
17 | class TestDevice(unittest.TestCase):
18 | @classmethod
19 | def setUpClass(self):
20 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
21 | org_id = self.manager.list_organizations()[0].id
22 | self.project = self.manager.create_organization_project(
23 | org_id=org_id,
24 | name="Int-Tests-Device_{}".format(
25 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
26 | ),
27 | )
28 |
29 | self.manager.enable_project_bgp_config(
30 | project_id=self.project.id, deployment_type="local", asn=65000
31 | )
32 |
33 | self.device = self.manager.create_device(
34 | self.project.id, "devicetest", "baremetal_0", "ewr1", "centos_7"
35 | )
36 |
37 | while True:
38 | if self.manager.get_device(self.device.id).state == "active":
39 | break
40 | time.sleep(2)
41 |
42 | def test_get_device(self):
43 | device = self.manager.get_device(self.device.id)
44 | self.assertEqual(device.hostname, self.device.hostname)
45 |
46 | def test_list_devices(self):
47 | devices = self.manager.list_devices(self.project.id)
48 | for device in devices:
49 | if device.id is self.device.id:
50 | break
51 | self.assertRaises(TypeError)
52 |
53 | def test_update_device(self):
54 | self.device.hostname = "newname"
55 | self.device.update()
56 | device = self.manager.get_device(self.device.id)
57 | self.assertEqual(self.device.hostname, device.hostname)
58 |
59 | def test_create_bgp_session(self):
60 | bgp_session = self.manager.create_bgp_session(
61 | self.device.id, address_family="ipv4"
62 | )
63 | self.assertIsNotNone(bgp_session)
64 |
65 | def test_get_bgp_sessions(self):
66 | data = self.manager.get_bgp_sessions(self.device.id)
67 | self.assertIsNotNone(self, data)
68 |
69 | def test_get_device_events(self):
70 | events = self.manager.list_device_events(self.device.id)
71 | self.assertGreater(len(events), 0)
72 |
73 | def test_get_device_ips(self):
74 | ips = self.device.ips()
75 | self.assertTrue(len(ips) > 0)
76 |
77 | @classmethod
78 | def tearDownClass(self):
79 | self.device.delete()
80 | self.project.delete()
81 |
82 |
83 | if __name__ == "__main__":
84 | sys.exit(unittest.main())
85 |
--------------------------------------------------------------------------------
/test/test_email.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import unittest
7 | import packet
8 | import random
9 |
10 |
11 | @unittest.skipIf(
12 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
13 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
14 | )
15 | class TestEmail(unittest.TestCase):
16 | @classmethod
17 | def setUpClass(cls):
18 | cls.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
19 |
20 | cls.email = cls.manager.add_email(
21 | "john.doe{}@packet.com".format(random.randint(1, 1001))
22 | )
23 |
24 | def test_get_email(self):
25 | email = self.manager.get_email(self.email.id)
26 | self.assertEqual(email.address, self.email.address)
27 |
28 | def test_update_email(self):
29 | self.email.address = "john.doe{}@packet.com".format(random.randint(1, 1001))
30 | self.email.update()
31 | # email address cannot be updated?
32 | # email = self.manager.get_email(self.email.id)
33 | # self.assertEqual(email.address, self.email.address)
34 |
35 | @classmethod
36 | def tearDownClass(cls):
37 | cls.email.delete()
38 |
39 |
40 | if __name__ == "__main__":
41 | sys.exit(unittest.main())
42 |
--------------------------------------------------------------------------------
/test/test_event.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import unittest
7 | import packet
8 |
9 | from datetime import datetime
10 |
11 |
12 | @unittest.skipIf(
13 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
14 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
15 | )
16 | class TestEvent(unittest.TestCase):
17 | @classmethod
18 | def setUpClass(cls):
19 | cls.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
20 |
21 | cls.events = cls.manager.list_events()
22 |
23 | org_id = cls.manager.list_organizations()[0].id
24 | cls.project = cls.manager.create_organization_project(
25 | org_id=org_id,
26 | name="Int-Tests-Events_{}".format(
27 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
28 | ),
29 | )
30 |
31 | def test_list_events(self):
32 | self.assertTrue(len(self.events) > 0)
33 |
34 | def test_get_event(self):
35 | event = self.manager.get_event(self.events[0].id)
36 | self.assertEqual(event.id, self.events[0].id)
37 |
38 | def test_get_project_events(self):
39 | events = self.manager.list_project_events(self.project.id)
40 | self.assertGreaterEqual(len(events), 0)
41 |
42 | @classmethod
43 | def tearDownClass(cls):
44 | cls.project.delete()
45 |
46 |
47 | if __name__ == "__main__":
48 | sys.exit(unittest.main())
49 |
--------------------------------------------------------------------------------
/test/test_ips.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import time
7 | import unittest
8 | import packet
9 |
10 | from datetime import datetime
11 |
12 |
13 | @unittest.skipIf(
14 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
15 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
16 | )
17 | class TestIps(unittest.TestCase):
18 | @classmethod
19 | def setUpClass(cls):
20 | cls.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
21 |
22 | org_id = cls.manager.list_organizations()[0].id
23 | cls.project = cls.manager.create_organization_project(
24 | org_id=org_id,
25 | name="Int-Tests-IPs_{}".format(
26 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
27 | ),
28 | )
29 |
30 | cls.ip_block = cls.manager.reserve_ip_address(
31 | project_id=cls.project.id,
32 | type="public_ipv4",
33 | quantity=1,
34 | facility="ewr1",
35 | details="delete me",
36 | tags=["deleteme"],
37 | )
38 |
39 | cls.device = cls.manager.create_device(
40 | cls.project.id, "iptest", "baremetal_0", "ewr1", "centos_7"
41 | )
42 |
43 | while True:
44 | if cls.manager.get_device(cls.device.id).state == "active":
45 | break
46 | time.sleep(2)
47 |
48 | def test_reserve_ip_address(self):
49 | self.assertEqual(32, self.ip_block.cidr)
50 | self.assertEqual("delete me", self.ip_block.details)
51 |
52 | def test_list_project_ips(self):
53 | ips = self.manager.list_project_ips(self.project.id)
54 | self.assertGreater(len(ips), 0)
55 |
56 | def test_create_device_ip(self):
57 | ip = self.manager.create_device_ip(
58 | self.device.id, address=self.ip_block.address
59 | )
60 | self.assertIsNotNone(ip)
61 | self.assertEqual(ip.address, self.ip_block.address)
62 |
63 | @classmethod
64 | def tearDownClass(cls):
65 | cls.device.delete()
66 | cls.ip_block.delete()
67 | cls.project.delete()
68 |
69 |
70 | if __name__ == "__main__":
71 | sys.exit(unittest.main())
72 |
--------------------------------------------------------------------------------
/test/test_organization.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import unittest
7 | import packet
8 |
9 |
10 | @unittest.skipIf(
11 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
12 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
13 | )
14 | class TestOrganization(unittest.TestCase):
15 | @classmethod
16 | def setUpClass(self):
17 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
18 | orgs = self.manager.list_organizations()
19 | self.org_id = orgs[0].id
20 |
21 | def test_organization(self):
22 | org = self.manager.get_organization(org_id=self.org_id)
23 | self.assertEqual(self.org_id, org.id)
24 |
25 | def test_create_organization_project(self):
26 | project = self.manager.create_organization_project(
27 | org_id=self.org_id,
28 | name="live-tests-project",
29 | payment_method_id=None,
30 | customdata={"tag": "delete me"},
31 | )
32 | self.assertIsNotNone(project)
33 | project.delete()
34 |
35 | def test_list_organization_projects(self):
36 | projects = self.manager.list_organization_projects(org_id=self.org_id)
37 | self.assertGreater(len(projects), 0)
38 |
39 | def test_list_organization_devices(self):
40 | devices = self.manager.list_organization_devices(org_id=self.org_id)
41 | self.assertGreaterEqual(len(devices), 0)
42 |
43 | @classmethod
44 | def tearDownClass(self):
45 | pass
46 |
47 |
48 | if __name__ == "__main__":
49 | sys.exit(unittest.main())
50 |
--------------------------------------------------------------------------------
/test/test_packet.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import sys
5 | import json
6 | import unittest
7 |
8 | import packet
9 | import requests_mock
10 |
11 |
12 | class PacketManagerTest(unittest.TestCase):
13 | def setUp(self):
14 | self.manager = PacketMockManager(auth_token="foo")
15 |
16 | def test_get_user(self):
17 | user = self.manager.get_user()
18 |
19 | self.assertEqual(user.get("full_name"), "Aaron Welch")
20 |
21 | def test_list_facilities(self):
22 | facilities = self.manager.list_facilities()
23 | for facility in facilities:
24 | str(facility)
25 | repr(facility)
26 | self.assertIsInstance(facility, packet.Facility)
27 |
28 | def test_list_metros(self):
29 | metros = self.manager.list_metros()
30 | for metro in metros:
31 | str(metro)
32 | repr(metro)
33 | self.assertIsInstance(metro, packet.Metro)
34 |
35 | def test_list_plans(self):
36 | plans = self.manager.list_plans()
37 | for plan in plans:
38 | str(plan)
39 | repr(plan)
40 | self.assertIsInstance(plan, packet.Plan)
41 |
42 | def test_list_operating_systems(self):
43 | oss = self.manager.list_operating_systems()
44 | for o in oss:
45 | str(o)
46 | repr(o)
47 | self.assertIsInstance(o, packet.OperatingSystem)
48 |
49 | def test_list_projects(self):
50 | projects = self.manager.list_projects()
51 | self.assertIsInstance(projects, list)
52 | for project in projects:
53 | str(project)
54 | repr(project)
55 | self.assertIsInstance(project, packet.Project)
56 |
57 | def test_get_project(self):
58 | project = self.manager.get_project("438659f0")
59 | self.assertIsInstance(project, packet.Project)
60 |
61 | def test_create_project(self):
62 | project = self.manager.create_project("test project")
63 | self.assertIsInstance(project, packet.Project)
64 |
65 | def test_update_project(self):
66 | name = "updated name"
67 | project = self.manager.get_project("438659f0")
68 | project.name = name
69 | project.update()
70 | self.assertEqual(project.name, name)
71 | self.assertIsInstance(project, packet.Project)
72 |
73 | def test_delete_project(self):
74 | project = self.manager.get_project("438659f0")
75 | self.assertIsNone(project.delete())
76 |
77 | def test_list_devices(self):
78 | devices = self.manager.list_devices("438659f0")
79 | for device in devices:
80 | str(device)
81 | repr(device)
82 | self.assertIsInstance(device, packet.Device)
83 |
84 | # TODO figure out how to properly handle this test case
85 | # def test_list_all_devices(self):
86 | # devices = self.manager.list_all_devices("438659f0")
87 | # for device in devices:
88 | # str(device)
89 | # repr(device)
90 | # self.assertIsInstance(device, packet.Device)
91 |
92 | def test_create_device(self):
93 | device = self.manager.create_device(
94 | "438659f0", "hostname", "baremetal_0", "ewr1", "ubuntu_14_04"
95 | )
96 | self.assertIsInstance(device, packet.Device)
97 |
98 | def test_create_device_ipxe(self):
99 | device = self.manager.create_device(
100 | "438659f0",
101 | "hostname",
102 | "baremetal_0",
103 | "ewr1",
104 | "custom_ipxe",
105 | ipxe_script_url="https://example.com",
106 | always_pxe=True,
107 | )
108 | self.assertIsInstance(device, packet.Device)
109 |
110 | def test_get_device(self):
111 | device = self.manager.get_device("9dec7266")
112 | self.assertIsInstance(device, packet.Device)
113 |
114 | def test_device_actions(self):
115 | device = self.manager.get_device("9dec7266")
116 | self.assertIsNone(device.power_off())
117 | self.assertIsNone(device.power_on())
118 | self.assertIsNone(device.rescue())
119 | self.assertIsNone(device.reboot())
120 |
121 | def test_update_device(self):
122 | hostname = "updated hostname"
123 | device = self.manager.get_device("9dec7266")
124 | device.hostname = hostname
125 | device.update()
126 | self.assertEqual(device.hostname, hostname)
127 | self.assertIsInstance(device, packet.Device)
128 |
129 | def test_delete_device(self):
130 | device = self.manager.get_device("9dec7266")
131 | self.assertIsNone(device.delete())
132 |
133 | def test_list_ssh_keys(self):
134 | keys = self.manager.list_ssh_keys()
135 | for key in keys:
136 | str(key)
137 | repr(key)
138 | self.assertIsInstance(key, packet.SSHKey)
139 |
140 | def test_get_ssh_key(self):
141 | key = self.manager.get_ssh_key("084a5dec")
142 | self.assertIsInstance(key, packet.SSHKey)
143 |
144 | def test_create_ssh_key(self):
145 | public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDI4pIqzpb5g3992h+yr527VRcaB68KE4vPjWPPoiQws49KIs2NMcOzS9QE4641uW1u5ML2HgQdfYKMF/YFGnI1Y6xV637DjhDyZYV9LasUH49npSSJjsBcsk9JGfUpNAOdcgpFzK8V90eiOrOC5YncxdwwG8pwjFI9nNVPCl4hYEu1iXdyysHvkFfS2fklsNjLWrzfafPlaen+qcBxygCA0sFdW/7er50aJeghdBHnE2WhIKLUkJxnKadznfAge7oEe+3LLAPfP+3yHyvp2+H0IzmVfYvAjnzliYetqQ8pg5ZW2BiJzvqz5PebGS70y/ySCNW1qQmJURK/Wc1bt9en"
146 |
147 | key = self.manager.create_ssh_key(label="sshkey-name", public_key=public_key)
148 | self.assertIsInstance(key, packet.SSHKey)
149 | self.assertEqual(key.key, public_key)
150 |
151 | def test_delete_ssh_key(self):
152 | key = self.manager.get_ssh_key("084a5dec")
153 | self.assertIsNone(key.delete())
154 |
155 | def test_update_ssh_key(self):
156 | label = "updated label"
157 | key = self.manager.get_ssh_key("084a5dec")
158 | key.label = label
159 | key.update()
160 | self.assertEqual(key.label, label)
161 | self.assertIsInstance(key, packet.SSHKey)
162 |
163 | def test_list_volumes(self):
164 | volumes = self.manager.list_volumes("438659f0")
165 | for volume in volumes:
166 | self.assertIsInstance(volume, packet.Volume)
167 |
168 | def test_create_volume(self):
169 | volume = self.manager.create_volume(
170 | "438659f0", "volume description", "storage_0", "100", "ewr1", 7, "1day"
171 | )
172 | self.assertIsInstance(volume, packet.Volume)
173 |
174 | def test_get_volume(self):
175 | volume = self.manager.get_volume("f9a8a263")
176 | str(volume)
177 | repr(volume)
178 | self.assertIsInstance(volume, packet.Volume)
179 |
180 | def test_update_volume(self):
181 | description = "updated description"
182 | volume = self.manager.get_volume("f9a8a263")
183 | volume.description = description
184 | volume.update()
185 | self.assertEqual(volume.description, description)
186 | self.assertIsInstance(volume, packet.Volume)
187 |
188 | def test_delete_volume(self):
189 | volume = self.manager.get_volume("f9a8a263")
190 | self.assertIsNone(volume.delete())
191 |
192 | def test_list_volume_snapshots(self):
193 | volume = self.manager.get_volume("f9a8a263")
194 | snaps = volume.list_snapshots()
195 | for snap in snaps:
196 | str(snap)
197 | repr(snap)
198 | snap.delete()
199 |
200 | def test_attach_volume(self):
201 | volume = self.manager.get_volume("f9a8a263")
202 | self.assertIsNone(volume.attach("9dec7266"))
203 |
204 | def test_detach_volume(self):
205 | volume = self.manager.get_volume("f9a8a263")
206 | self.assertIsNone(volume.detach())
207 |
208 | def test_volume_create_snapshot(self):
209 | volume = self.manager.get_volume("f9a8a263")
210 | volume.create_snapshot()
211 |
212 | def test_volume_create_clone(self):
213 | volume = self.manager.get_volume("f9a8a263")
214 | volume.clone()
215 |
216 | def test_capacity(self):
217 | self.manager.get_capacity()
218 |
219 | def test_get_bgp_config(self):
220 | bgp = self.manager.get_bgp_config("1234")
221 | self.assertIsNotNone(bgp)
222 |
223 | def test_validate_capacity(self):
224 | capacity = self.manager.validate_capacity([("ewr1", "baremetal_0", 10)])
225 | self.assertTrue(capacity)
226 |
227 | def test_validate_metro_capacity(self):
228 | capacity = self.manager.validate_metro_capacity([("sv", "baremetal_1", 10)])
229 | self.assertTrue(capacity)
230 |
231 | # IP Addresses
232 | def test_list_device_ips(self):
233 | ips = self.manager.list_device_ips("e123s")
234 | self.assertIsNotNone(ips)
235 |
236 | def test_list_projects_ips(self):
237 | ips = self.manager.list_project_ips("438659f0")
238 | self.assertIsNotNone(ips)
239 | for ip in ips:
240 | self.assertIsInstance(ip.facility, packet.Facility)
241 |
242 | def test_list_projects_ips_state_all(self):
243 | ips = self.manager.list_project_ips("438659f1", params={"state": "all"})
244 | self.assertIsNotNone(ips)
245 | self.assertIsNone(ips[0].facility)
246 | self.assertIsInstance(ips[1].facility, packet.Facility)
247 |
248 |
249 | class PacketMockManager(packet.Manager):
250 | def call_api(self, method, type="GET", params=None):
251 | with requests_mock.Mocker() as m:
252 | mock = {
253 | "DELETE": m.delete,
254 | "GET": m.get,
255 | "PUT": m.put,
256 | "POST": m.post,
257 | "PATCH": m.patch,
258 | }[type]
259 |
260 | if type == "DELETE":
261 | mock(requests_mock.ANY)
262 | return super(PacketMockManager, self).call_api(method, type, params)
263 |
264 | fixture = "%s_%s" % (type.lower(), method.lower())
265 | fixture = fixture.replace("/", "_").split("?")[0]
266 | fixture = "test/fixtures/%s.json" % fixture
267 |
268 | headers = {"content-type": "application/json"}
269 |
270 | with open(fixture) as data_file:
271 | j = json.load(data_file)
272 |
273 | mock(requests_mock.ANY, headers=headers, json=j)
274 | return super(PacketMockManager, self).call_api(method, type, params)
275 |
276 |
277 | if __name__ == "__main__":
278 | sys.exit(unittest.main())
279 |
--------------------------------------------------------------------------------
/test/test_ports.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import time
7 | import unittest
8 | import packet
9 |
10 | from datetime import datetime
11 |
12 |
13 | @unittest.skipIf(
14 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
15 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
16 | )
17 | class TestPorts(unittest.TestCase):
18 | @classmethod
19 | def setUpClass(self):
20 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
21 |
22 | org_id = self.manager.list_organizations()[0].id
23 | self.project = self.manager.create_organization_project(
24 | org_id=org_id,
25 | name="Int-Tests-Ports_{}".format(
26 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
27 | ),
28 | )
29 |
30 | self.device = self.manager.create_device(
31 | self.project.id, "networktestingdevice", "baremetal_2", "ewr1", "centos_7"
32 | )
33 | self.vlan = self.manager.create_vlan(self.project.id, "ewr1")
34 | self.vlan2 = self.manager.create_vlan(self.project.id, "ewr1")
35 |
36 | while True:
37 | if self.manager.get_device(self.device.id).state == "active":
38 | break
39 | time.sleep(2)
40 | self.device_port_id = self.device["network_ports"][0]["id"]
41 | self.device_eth0_port_id = self.device["network_ports"][1]["id"]
42 |
43 | def test01_convert_layer2(self):
44 | self.manager.convert_layer_2(self.device_port_id, self.vlan.id)
45 |
46 | def test02_remove_port(self):
47 | self.manager.remove_port(self.device_port_id, self.vlan.id)
48 |
49 | def test03_assign_port(self):
50 | self.manager.assign_port(self.device_port_id, self.vlan.id)
51 |
52 | def test04_bond_port(self):
53 | self.manager.bond_ports(self.device_port_id, False)
54 |
55 | def test05_disbond_port(self):
56 | self.manager.disbond_ports(self.device_port_id, False)
57 |
58 | def test06_assign_native_vlan(self):
59 | # must remove vlan from any previous association and attach more than one vlan to the eth0 port to be able to
60 | # choose a native vlan
61 | self.manager.remove_port(self.device_port_id, self.vlan.id)
62 | self.manager.assign_port(self.device_eth0_port_id, self.vlan.id)
63 | self.manager.assign_port(self.device_eth0_port_id, self.vlan2.id)
64 | self.manager.assign_native_vlan(self.device_eth0_port_id, self.vlan.id)
65 |
66 | def test07_remove_native_vlan(self):
67 | self.manager.remove_native_vlan(self.device_eth0_port_id)
68 |
69 | def test08_convert_layer3(self):
70 | ipadresses = list({"address_family": 6, "public": False})
71 | self.manager.convert_layer_3(self.device_port_id, ipadresses)
72 |
73 | @classmethod
74 | def tearDownClass(self):
75 | self.manager.remove_port(self.device_eth0_port_id, self.vlan.id)
76 | self.device.delete()
77 | self.vlan2.delete()
78 | self.vlan.delete()
79 | self.project.delete()
80 |
81 |
82 | if __name__ == "__main__":
83 | sys.exit(unittest.main())
84 |
--------------------------------------------------------------------------------
/test/test_vlan.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import time
7 | import unittest
8 | import packet
9 |
10 | from datetime import datetime
11 |
12 |
13 | @unittest.skipIf(
14 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
15 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
16 | )
17 | class TestVlan(unittest.TestCase):
18 | @classmethod
19 | def setUpClass(self):
20 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
21 |
22 | org_id = self.manager.list_organizations()[0].id
23 | self.project = self.manager.create_organization_project(
24 | org_id=org_id,
25 | name="Int-Tests-VLAN_{}".format(
26 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
27 | ),
28 | )
29 |
30 | self.device = self.manager.create_device(
31 | self.project.id, "vlantesting", "baremetal_2", "ewr1", "centos_7"
32 | )
33 |
34 | self.vlan = self.manager.create_vlan(self.project.id, "ewr1")
35 | self.vlan2 = self.manager.create_vlan(self.project.id, "ewr1")
36 | while True:
37 | if self.manager.get_device(self.device.id).state == "active":
38 | break
39 | time.sleep(2)
40 | self.device_port_id = self.device["network_ports"][0]["id"]
41 | self.device_eth0_port_id = self.device["network_ports"][1]["id"]
42 | # must convert to layer 2 to work with vlans
43 | self.manager.convert_layer_2(self.device_port_id, self.vlan.id)
44 |
45 | def test_list_vlan(self):
46 | vlans = self.manager.list_vlans(self.project.id)
47 | self.assertTrue(len(vlans) > 0)
48 |
49 | def test_get_vlan(self):
50 | vlan = self.vlan.get()
51 | self.assertEqual(vlan["id"], self.vlan.id)
52 |
53 | def test_assign_port(self):
54 | self.manager.disbond_ports(self.device_eth0_port_id, False)
55 | self.manager.remove_port(self.device_port_id, self.vlan.id)
56 | self.manager.assign_port(self.device_eth0_port_id, self.vlan.id)
57 | self.manager.assign_port(self.device_eth0_port_id, self.vlan2.id)
58 | self.vlan.assign_native_vlan(self.device_eth0_port_id)
59 |
60 | def test_remove_port(self):
61 | self.vlan.remove_native_vlan(self.device_eth0_port_id)
62 |
63 | @classmethod
64 | def tearDownClass(self):
65 | self.device.delete()
66 | self.vlan.delete()
67 | self.vlan2.delete()
68 | self.project.delete()
69 |
70 |
71 | if __name__ == "__main__":
72 | sys.exit(unittest.main())
73 |
--------------------------------------------------------------------------------
/test/test_volume.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | import os
5 | import sys
6 | import unittest
7 | import time
8 | import packet
9 |
10 | from datetime import datetime
11 |
12 |
13 | @unittest.skipIf(
14 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
15 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
16 | )
17 | class TestVolume(unittest.TestCase):
18 | @classmethod
19 | def setUpClass(self):
20 | self.timestamp = ""
21 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
22 | self.projectId = self.manager.list_projects()[0].id
23 |
24 | org_id = self.manager.list_organizations()[0].id
25 | self.project = self.manager.create_organization_project(
26 | org_id=org_id,
27 | name="Int-Tests-Volume_{}".format(
28 | datetime.utcnow().strftime("%Y%m%dT%H%M%S.%f")[:-3]
29 | ),
30 | )
31 |
32 | self.volume = self.manager.create_volume(
33 | self.project.id, "volume description", "storage_1", "100", "ewr1", 7, "1day"
34 | )
35 |
36 | while True:
37 | if self.manager.get_volume(self.volume.id).state == "active":
38 | break
39 | time.sleep(2)
40 |
41 | self.device = self.manager.create_device(
42 | self.project.id, "devicevolumestest", "baremetal_0", "ewr1", "centos_7"
43 | )
44 |
45 | self.policy = self.volume.create_snapshot_policy("1day", 2)
46 | self.clone = self.volume.clone()
47 |
48 | while True:
49 | if self.manager.get_device(self.device.id).state == "active":
50 | break
51 | time.sleep(2)
52 |
53 | def test_get_volume(self):
54 | volume = self.manager.get_volume(self.volume.id)
55 | self.assertEqual(volume.description, self.volume.description)
56 |
57 | def test_list_volumes(self):
58 | volumes = self.manager.list_volumes(self.project.id)
59 | for volume in volumes:
60 | if volume.id is self.volume.id:
61 | break
62 | self.assertRaises(TypeError)
63 |
64 | def test_update_volume(self):
65 | self.volume.description = "newdescription"
66 | self.volume.update()
67 | volume = self.manager.get_volume(self.volume.id)
68 | self.assertEqual(self.volume.description, volume.description)
69 |
70 | def test_attach_volume(self):
71 | self.volume.attach(self.device.id)
72 | while True:
73 | if self.manager.get_device(self.device.id).state == "active":
74 | break
75 | time.sleep(1)
76 |
77 | def test_detach_volume(self):
78 | self.volume.detach()
79 | while True:
80 | if self.manager.get_device(self.device.id).state == "active":
81 | break
82 | time.sleep(1)
83 |
84 | def test_create_snapshot(self):
85 | self.volume.create_snapshot()
86 |
87 | def test_get_snapshots(self):
88 | snapshots = self.manager.get_snapshots(self.volume.id)
89 | self.assertIsNotNone(snapshots)
90 | self.__class__.timestamp = snapshots[0].timestamp
91 |
92 | def test_update_snapshot_policy(self):
93 | self.policy = self.policy.update_snapshot_policy("1month", 3)
94 | assert self.policy.frequency == "1month"
95 | assert self.policy.count == 3
96 |
97 | def test_restore_volume(self):
98 | self.volume.restore(self.__class__.timestamp)
99 |
100 | @classmethod
101 | def tearDownClass(self):
102 | self.policy.delete()
103 | self.volume.delete()
104 | self.device.delete()
105 | self.clone.delete()
106 | self.project.delete()
107 |
108 |
109 | if __name__ == "__main__":
110 | sys.exit(unittest.main())
111 |
--------------------------------------------------------------------------------
/test/test_vpn.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 |
4 | from __future__ import print_function
5 |
6 | import os
7 | import sys
8 | import unittest
9 | import packet
10 |
11 |
12 | @unittest.skipIf(
13 | "PACKET_PYTHON_TEST_ACTUAL_API" not in os.environ,
14 | "PACKET_PYTHON_TEST_ACTUAL_API is missing from environment",
15 | )
16 | class TestVpn(unittest.TestCase):
17 | @classmethod
18 | def setUpClass(self):
19 | self.manager = packet.Manager(auth_token=os.environ["PACKET_AUTH_TOKEN"])
20 | self.manager.turn_on_vpn()
21 |
22 | # def test_get_vpn_config(self):
23 | # config = self.manager.get_vpn_configuration("ewr1")
24 | # print(config)
25 |
26 | @classmethod
27 | def tearDownClass(self):
28 | self.manager.turn_off_vpn()
29 |
30 |
31 | if __name__ == "__main__":
32 | sys.exit(unittest.main())
33 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | # SPDX-License-Identifier: LGPL-3.0-only
2 |
3 | [tox]
4 |
5 | envlist = py27,py38,py39,py310
6 | skip_missing_interpreters=True
7 |
8 | [tool:pytest]
9 | testpaths = test
10 | # addopts =
11 |
12 | [gh-actions]
13 | python =
14 | 2.7: py27
15 | 3.8: py38
16 | 3.9: py39
17 | 3.10: py310, mypy
18 |
19 | [testenv]
20 | usedevelop=True
21 | setenv =
22 | PACKET_AUTH_TOKEN = {env:PACKET_AUTH_TOKEN:}
23 |
24 | deps =
25 | pytest
26 | coverage
27 | pytest-cov
28 | requests_mock
29 |
30 | commands=
31 | py.test --cov-append --cov-report=term-missing --cov packet --cov-report xml:{envdir}/../../coverage.xml --junitxml=unittest_{envname}.xml {posargs:-vv}
32 |
33 | depends =
34 | {py39}: clean
35 | report: py39
36 |
37 | [testenv:report]
38 | deps = coverage
39 | skip_install = true
40 | commands =
41 | coverage report
42 | coverage xml:coverage.xml
43 |
44 | [testenv:clean]
45 | deps = coverage
46 | skip_install = true
47 | commands = coverage erase
48 |
--------------------------------------------------------------------------------