├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── feature.yml │ └── help.yml ├── dependabot.yml ├── labels.yml ├── pull_request_template.md ├── release.yml └── workflows │ ├── ci.yml │ ├── codeql.yml │ ├── dependency-review.yml │ ├── e2e-test-pr.yml │ ├── e2e-test.yml │ ├── labeler.yml │ ├── nightly-smoke-tests.yml │ ├── publish-pypi.yaml │ ├── release-cross-repo-test.yml │ └── release-notify-slack.yml ├── .gitignore ├── .gitmodules ├── .pylintrc ├── .python-version ├── .readthedocs.yaml ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── docs ├── Makefile ├── conf.py ├── guides │ ├── core_concepts.rst │ ├── event_polling.rst │ ├── getting_started.rst │ ├── oauth.rst │ └── upgrading_from_linode-api.rst ├── index.rst └── linode_api4 │ ├── linode_client.rst │ ├── login_client.rst │ ├── objects │ ├── filtering.rst │ └── models.rst │ ├── paginated_list.rst │ └── polling.rst ├── examples └── install-on-linode │ ├── .gitignore │ ├── README.md │ ├── app.py │ ├── config.py.example │ ├── make_stackscript.py │ ├── requirements.txt │ └── templates │ ├── base.html │ ├── configure.html │ ├── error.html │ └── success.html ├── linode_api4 ├── __init__.py ├── common.py ├── errors.py ├── groups │ ├── __init__.py │ ├── account.py │ ├── beta.py │ ├── database.py │ ├── domain.py │ ├── group.py │ ├── image.py │ ├── linode.py │ ├── lke.py │ ├── lke_tier.py │ ├── longview.py │ ├── monitor.py │ ├── networking.py │ ├── nodebalancer.py │ ├── object_storage.py │ ├── placement.py │ ├── polling.py │ ├── profile.py │ ├── region.py │ ├── support.py │ ├── tag.py │ ├── volume.py │ └── vpc.py ├── linode_client.py ├── login_client.py ├── objects │ ├── __init__.py │ ├── account.py │ ├── base.py │ ├── beta.py │ ├── database.py │ ├── dbase.py │ ├── domain.py │ ├── filtering.py │ ├── image.py │ ├── linode.py │ ├── lke.py │ ├── longview.py │ ├── monitor.py │ ├── networking.py │ ├── nodebalancer.py │ ├── object_storage.py │ ├── placement.py │ ├── profile.py │ ├── region.py │ ├── serializable.py │ ├── support.py │ ├── tag.py │ ├── volume.py │ └── vpc.py ├── paginated_list.py ├── polling.py ├── util.py └── version.py ├── pyproject.toml ├── scripts ├── lke-policy.yaml └── lke_calico_rules_e2e.sh ├── setup.py ├── test ├── __init__.py ├── fixtures │ ├── account.json │ ├── account_availability.json │ ├── account_availability_us-east.json │ ├── account_betas.json │ ├── account_betas_cool.json │ ├── account_child-accounts.json │ ├── account_child-accounts_123456.json │ ├── account_child-accounts_123456_token.json │ ├── account_events_123.json │ ├── account_invoices.json │ ├── account_invoices_123.json │ ├── account_invoices_123456_items.json │ ├── account_logins.json │ ├── account_logins_123.json │ ├── account_maintenance.json │ ├── account_notifications.json │ ├── account_oauth-clients_2737bf16b39ab5d7b4a1.json │ ├── account_payment-method_123.json │ ├── account_payment-methods.json │ ├── account_payments.json │ ├── account_promo-codes.json │ ├── account_service-transfers.json │ ├── account_service-transfers_12345.json │ ├── account_settings.json │ ├── account_transfer.json │ ├── account_users_test-user.json │ ├── betas.json │ ├── betas_active.json │ ├── databases_engines.json │ ├── databases_instances.json │ ├── databases_mysql_config.json │ ├── databases_mysql_instances.json │ ├── databases_mysql_instances_123_backups.json │ ├── databases_mysql_instances_123_backups_456_restore.json │ ├── databases_mysql_instances_123_credentials.json │ ├── databases_mysql_instances_123_credentials_reset.json │ ├── databases_mysql_instances_123_patch.json │ ├── databases_mysql_instances_123_resume.json │ ├── databases_mysql_instances_123_ssl.json │ ├── databases_mysql_instances_123_suspend.json │ ├── databases_postgresql_config.json │ ├── databases_postgresql_instances.json │ ├── databases_postgresql_instances_123_backups.json │ ├── databases_postgresql_instances_123_backups_456_restore.json │ ├── databases_postgresql_instances_123_credentials.json │ ├── databases_postgresql_instances_123_credentials_reset.json │ ├── databases_postgresql_instances_123_patch.json │ ├── databases_postgresql_instances_123_resume.json │ ├── databases_postgresql_instances_123_ssl.json │ ├── databases_postgresql_instances_123_suspend.json │ ├── databases_types.json │ ├── domains.json │ ├── domains_12345_clone.json │ ├── domains_12345_records.json │ ├── domains_12345_zone-file.json │ ├── domains_import.json │ ├── images.json │ ├── images_private_123_regions.json │ ├── images_private_1337.json │ ├── images_upload.json │ ├── linode_instances.json │ ├── linode_instances_123_backups.json │ ├── linode_instances_123_configs.json │ ├── linode_instances_123_configs_456789.json │ ├── linode_instances_123_configs_456789_interfaces.json │ ├── linode_instances_123_configs_456789_interfaces_123.json │ ├── linode_instances_123_configs_456789_interfaces_123_put.json │ ├── linode_instances_123_configs_456789_interfaces_321.json │ ├── linode_instances_123_configs_456789_interfaces_456.json │ ├── linode_instances_123_disks.json │ ├── linode_instances_123_disks_12345_clone.json │ ├── linode_instances_123_firewalls.json │ ├── linode_instances_123_ips.json │ ├── linode_instances_123_nodebalancers.json │ ├── linode_instances_123_transfer.json │ ├── linode_instances_123_transfer_2023_4.json │ ├── linode_instances_123_volumes.json │ ├── linode_stackscripts_10079.json │ ├── linode_types.json │ ├── linode_types_g6-nanode-1.json │ ├── lke_clusters.json │ ├── lke_clusters_18881.json │ ├── lke_clusters_18881_control__plane__acl.json │ ├── lke_clusters_18881_dashboard.json │ ├── lke_clusters_18881_nodes_123456.json │ ├── lke_clusters_18881_pools_456.json │ ├── lke_clusters_18882.json │ ├── lke_clusters_18882_pools_789.json │ ├── lke_tiers_standard_versions.json │ ├── lke_types.json │ ├── lke_versions.json │ ├── longview_clients.json │ ├── longview_plan.json │ ├── longview_subscriptions.json │ ├── mongodb.json │ ├── monitor_dashboards.json │ ├── monitor_dashboards_1.json │ ├── monitor_services.json │ ├── monitor_services_dbaas.json │ ├── monitor_services_dbaas_dashboards.json │ ├── monitor_services_dbaas_metric-definitions.json │ ├── monitor_services_dbaas_token.json │ ├── monitor_services_linode_token.json │ ├── network-transfer_prices.json │ ├── networking_firewalls.json │ ├── networking_firewalls_123.json │ ├── networking_firewalls_123_devices.json │ ├── networking_firewalls_123_devices_123.json │ ├── networking_firewalls_123_history.json │ ├── networking_firewalls_123_history_rules_2.json │ ├── networking_firewalls_123_rules.json │ ├── networking_ips_127.0.0.1.json │ ├── networking_ipv6_pools.json │ ├── networking_ipv6_ranges.json │ ├── networking_ipv6_ranges_2600%3A3c01%3A%3A.json │ ├── networking_vlans.json │ ├── nodebalancers.json │ ├── nodebalancers_123456.json │ ├── nodebalancers_123456_configs.json │ ├── nodebalancers_123456_configs_65432_nodes.json │ ├── nodebalancers_12345_configs_4567_rebuild.json │ ├── nodebalancers_12345_firewalls.json │ ├── nodebalancers_12345_stats.json │ ├── nodebalancers_types.json │ ├── object-storage_buckets.json │ ├── object-storage_buckets_us-east-1.json │ ├── object-storage_buckets_us-east-1_example-bucket.json │ ├── object-storage_buckets_us-east-1_example-bucket_object-acl.json │ ├── object-storage_buckets_us-east-1_example-bucket_object-list.json │ ├── object-storage_buckets_us-east-1_example-bucket_object-url.json │ ├── object-storage_buckets_us-east-1_example-bucket_ssl.json │ ├── object-storage_buckets_us-east_example-bucket_access.json │ ├── object-storage_clusters.json │ ├── object-storage_keys.json │ ├── object-storage_quotas.json │ ├── object-storage_quotas_obj-objects-us-ord-1.json │ ├── object-storage_quotas_obj-objects-us-ord-1_usage.json │ ├── object-storage_transfer.json │ ├── object-storage_types.json │ ├── placement_groups.json │ ├── placement_groups_123.json │ ├── profile.json │ ├── profile_device_123.json │ ├── profile_devices.json │ ├── profile_logins.json │ ├── profile_logins_123.json │ ├── profile_preferences.json │ ├── profile_security-questions.json │ ├── profile_sshkeys.json │ ├── regions.json │ ├── regions_availability.json │ ├── regions_us-east_availability.json │ ├── support_tickets_123.json │ ├── tags.json │ ├── tags_nothing.json │ ├── tags_something.json │ ├── testmappedobj1.json │ ├── volumes.json │ ├── volumes_types.json │ ├── vpcs.json │ ├── vpcs_123456.json │ ├── vpcs_123456_ips.json │ ├── vpcs_123456_subnets.json │ ├── vpcs_123456_subnets_789.json │ └── vpcs_ips.json ├── integration │ ├── __init__.py │ ├── conftest.py │ ├── helpers.py │ ├── linode_client │ │ ├── __init__.py │ │ ├── test_errors.py │ │ ├── test_linode_client.py │ │ └── test_retry.py │ ├── login_client │ │ └── test_login_client.py │ └── models │ │ ├── __init__.py │ │ ├── account │ │ └── test_account.py │ │ ├── database │ │ ├── helpers.py │ │ ├── test_database.py │ │ └── test_database_engine_config.py │ │ ├── domain │ │ └── test_domain.py │ │ ├── firewall │ │ └── test_firewall.py │ │ ├── image │ │ └── test_image.py │ │ ├── linode │ │ └── test_linode.py │ │ ├── lke │ │ └── test_lke.py │ │ ├── longview │ │ └── test_longview.py │ │ ├── monitor │ │ └── test_monitor.py │ │ ├── networking │ │ └── test_networking.py │ │ ├── nodebalancer │ │ └── test_nodebalancer.py │ │ ├── object_storage │ │ ├── test_obj.py │ │ └── test_obj_quotas.py │ │ ├── placement │ │ └── test_placement.py │ │ ├── profile │ │ └── test_profile.py │ │ ├── tag │ │ └── test_tag.py │ │ ├── volume │ │ └── test_volume.py │ │ └── vpc │ │ └── test_vpc.py └── unit │ ├── __init__.py │ ├── base.py │ ├── errors_test.py │ ├── fixtures.py │ ├── groups │ ├── __init__.py │ ├── database_test.py │ ├── image_test.py │ ├── linode_test.py │ ├── lke_test.py │ ├── lke_tier_test.py │ ├── object_storage_test.py │ ├── placement_test.py │ ├── polling_test.py │ ├── region_test.py │ └── vpc_test.py │ ├── linode_client_test.py │ ├── login_client_test.py │ ├── objects │ ├── account_test.py │ ├── beta_test.py │ ├── database_test.py │ ├── domain_test.py │ ├── firewall_test.py │ ├── image_test.py │ ├── linode_test.py │ ├── lke_test.py │ ├── longview_test.py │ ├── mapped_object_test.py │ ├── monitor_test.py │ ├── networking_test.py │ ├── nodebalancers_test.py │ ├── object_storage_test.py │ ├── placement_test.py │ ├── profile_test.py │ ├── region_test.py │ ├── serializable_test.py │ ├── support_test.py │ ├── tag_test.py │ ├── volume_test.py │ └── vpc_test.py │ ├── paginated_list_test.py │ └── util_test.py └── tox.ini /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: ["bug"] 5 | body: 6 | - type: input 7 | id: package-version 8 | attributes: 9 | label: Package 10 | description: What version of the linode_api4 package are you using? 11 | placeholder: 5.3.0 12 | validations: 13 | required: true 14 | 15 | - type: textarea 16 | id: expected 17 | attributes: 18 | label: Expected Behavior 19 | description: What should have happened? 20 | 21 | - type: textarea 22 | id: actual 23 | attributes: 24 | label: Actual Behavior 25 | description: What actually happened? 26 | 27 | - type: textarea 28 | id: reproduce 29 | attributes: 30 | label: Steps to Reproduce 31 | description: List any custom configurations and the steps to reproduce this error 32 | 33 | - type: textarea 34 | id: error 35 | attributes: 36 | label: Error Output 37 | description: If you received an error output that is too long, use Gists -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature.yml: -------------------------------------------------------------------------------- 1 | name: Enhancement 2 | description: Request a feature 3 | title: "[Feature]: " 4 | labels: ["enhancement"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: What would you like this feature to do in detail? 11 | validations: 12 | required: true 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/help.yml: -------------------------------------------------------------------------------- 1 | name: Help 2 | description: You're pretty sure it's not a bug but you can't figure out why it's not working 3 | title: "[Help]: " 4 | labels: ["help wanted"] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Description 10 | description: What are you attempting to do, what error messages are you getting? 11 | validations: 12 | required: true -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pip" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | # PR Labels 2 | - name: new-feature 3 | description: for new features in the changelog. 4 | color: 225fee 5 | - name: project 6 | description: for new projects in the changelog. 7 | color: 46BAF0 8 | - name: improvement 9 | description: for improvements in existing functionality in the changelog. 10 | color: 22ee47 11 | - name: repo-ci-improvement 12 | description: for improvements in the repository or CI workflow in the changelog. 13 | color: c922ee 14 | - name: bugfix 15 | description: for any bug fixes in the changelog. 16 | color: ed8e21 17 | - name: documentation 18 | description: for updates to the documentation in the changelog. 19 | color: d3e1e6 20 | - name: dependencies 21 | description: dependency updates usually from dependabot 22 | color: 5c9dff 23 | - name: testing 24 | description: for updates to the testing suite in the changelog. 25 | color: 933ac9 26 | - name: breaking-change 27 | description: for breaking changes in the changelog. 28 | color: ff0000 29 | - name: ignore-for-release 30 | description: PRs you do not want to render in the changelog 31 | color: 7b8eac 32 | - name: do-not-merge 33 | description: PRs that should not be merged until the commented issue is resolved 34 | color: eb1515 35 | # Issue Labels 36 | - name: enhancement 37 | description: issues that request a enhancement 38 | color: 22ee47 39 | - name: bug 40 | description: issues that report a bug 41 | color: ed8e21 42 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## 📝 Description 2 | 3 | **What does this PR do and why is this change necessary?** 4 | 5 | ## ✔️ How to Test 6 | 7 | **What are the steps to reproduce the issue or verify the changes?** 8 | 9 | **How do I run the relevant unit/integration tests?** 10 | 11 | ## 📷 Preview 12 | 13 | **If applicable, include a screenshot or code snippet of this change. Otherwise, please remove this section.** -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | exclude: 3 | labels: 4 | - ignore-for-release 5 | categories: 6 | - title: 📋 New Project 7 | labels: 8 | - project 9 | - title: ⚠️ Breaking Change 10 | labels: 11 | - breaking-change 12 | - title: 🐛 Bug Fixes 13 | labels: 14 | - bugfix 15 | - title: 🚀 New Features 16 | labels: 17 | - new-feature 18 | - title: 💡 Improvements 19 | labels: 20 | - improvement 21 | - title: 🧪 Testing Improvements 22 | labels: 23 | - testing 24 | - title: ⚙️ Repo/CI Improvements 25 | labels: 26 | - repo-ci-improvement 27 | - title: 📖 Documentation 28 | labels: 29 | - documentation 30 | - title: 📦 Dependency Updates 31 | labels: 32 | - dependencies 33 | - title: Other Changes 34 | labels: 35 | - "*" 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Continuous Integration 3 | 4 | on: 5 | push: 6 | branches: 7 | - dev 8 | - main 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | lint: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: checkout repo 17 | uses: actions/checkout@v4 18 | 19 | - name: setup python 3 20 | uses: actions/setup-python@v5 21 | with: 22 | python-version: '3.x' 23 | 24 | - name: install dependencies 25 | run: make dev-install 26 | 27 | - name: run linter 28 | run: make lint 29 | 30 | build: 31 | runs-on: ubuntu-latest 32 | strategy: 33 | matrix: 34 | python-version: ['3.9', '3.10', '3.11', '3.12', '3.13'] 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: actions/setup-python@v5 38 | with: 39 | python-version: ${{ matrix.python-version }} 40 | - name: Run tests 41 | run: | 42 | pip install ".[test]" 43 | tox 44 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL Advanced" 2 | 3 | on: 4 | push: 5 | branches: [ "dev", "main", "proj/*" ] 6 | pull_request: 7 | branches: [ "dev", "main", "proj/*" ] 8 | schedule: 9 | - cron: '39 0 * * 6' 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze (${{ matrix.language }}) 14 | runs-on: ubuntu-latest 15 | permissions: 16 | security-events: write 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | include: 22 | - language: python 23 | build-mode: none 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | - name: Initialize CodeQL 29 | uses: github/codeql-action/init@v3 30 | with: 31 | languages: ${{ matrix.language }} 32 | build-mode: ${{ matrix.build-mode }} 33 | queries: security-and-quality 34 | 35 | - name: Perform CodeQL Analysis 36 | uses: github/codeql-action/analyze@v3 37 | with: 38 | category: "/language:${{matrix.language}}" 39 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | name: 'Dependency review' 2 | on: 3 | pull_request: 4 | branches: [ "dev", "main", "proj/*" ] 5 | permissions: 6 | contents: read 7 | pull-requests: write 8 | 9 | jobs: 10 | dependency-review: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: 'Checkout repository' 14 | uses: actions/checkout@v4 15 | - name: 'Dependency Review' 16 | uses: actions/dependency-review-action@v4 17 | with: 18 | comment-summary-in-pr: on-failure 19 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: labeler 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | paths: 8 | - '.github/labels.yml' 9 | - '.github/workflows/labeler.yml' 10 | pull_request: 11 | paths: 12 | - '.github/labels.yml' 13 | - '.github/workflows/labeler.yml' 14 | 15 | jobs: 16 | labeler: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - 20 | name: Checkout 21 | uses: actions/checkout@v4 22 | - 23 | name: Run Labeler 24 | uses: crazy-max/ghaction-github-labeler@24d110aa46a59976b8a7f35518cb7f14f434c916 25 | with: 26 | github-token: ${{ secrets.GITHUB_TOKEN }} 27 | yaml-file: .github/labels.yml 28 | dry-run: ${{ github.event_name == 'pull_request' }} 29 | exclude: | 30 | help* 31 | *issue 32 | -------------------------------------------------------------------------------- /.github/workflows/nightly-smoke-tests.yml: -------------------------------------------------------------------------------- 1 | name: Nightly Smoke Tests 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 * * *" 6 | workflow_dispatch: 7 | inputs: 8 | sha: 9 | description: 'Commit SHA to test' 10 | required: false 11 | default: '' 12 | type: string 13 | 14 | 15 | jobs: 16 | smoke_tests: 17 | if: github.repository == 'linode/linode_api4-python' || github.event_name == 'workflow_dispatch' 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout code 22 | uses: actions/checkout@v4 23 | with: 24 | ref: dev 25 | 26 | - name: Set up Python 27 | uses: actions/setup-python@v5 28 | with: 29 | python-version: '3.x' 30 | 31 | - name: Install Python deps 32 | run: pip install -U setuptools wheel boto3 certifi 33 | 34 | - name: Install Python SDK 35 | run: make dev-install 36 | env: 37 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: Run smoke tests 40 | id: smoke_tests 41 | run: | 42 | make test-smoke 43 | env: 44 | LINODE_TOKEN: ${{ secrets.LINODE_TOKEN }} 45 | 46 | - name: Notify Slack 47 | if: always() && github.repository == 'linode/linode_api4-python' 48 | uses: slackapi/slack-github-action@v2.1.0 49 | with: 50 | method: chat.postMessage 51 | token: ${{ secrets.SLACK_BOT_TOKEN }} 52 | payload: | 53 | channel: ${{ secrets.SLACK_CHANNEL_ID }} 54 | blocks: 55 | - type: section 56 | text: 57 | type: mrkdwn 58 | text: ":rocket: *${{ github.workflow }} Completed in: ${{ github.repository }}* :white_check_mark:" 59 | - type: divider 60 | - type: section 61 | fields: 62 | - type: mrkdwn 63 | text: "*Build Result:*\n${{ steps.smoke_tests.outcome == 'success' && ':large_green_circle: Build Passed' || ':red_circle: Build Failed' }}" 64 | - type: mrkdwn 65 | text: "*Branch:*\n`${{ github.ref_name }}`" 66 | - type: section 67 | fields: 68 | - type: mrkdwn 69 | text: "*Commit Hash:*\n<${{ github.server_url }}/${{ github.repository }}/commit/${{ github.sha }}|${{ github.sha }}>" 70 | - type: mrkdwn 71 | text: "*Run URL:*\n<${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Run Details>" 72 | - type: divider 73 | - type: context 74 | elements: 75 | - type: mrkdwn 76 | text: "Triggered by: :bust_in_silhouette: `${{ github.actor }}`" 77 | 78 | -------------------------------------------------------------------------------- /.github/workflows/publish-pypi.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | on: 3 | workflow_dispatch: null 4 | release: 5 | types: [ published ] 6 | jobs: 7 | pypi-release: 8 | permissions: 9 | # IMPORTANT: this permission is mandatory for trusted publishing 10 | id-token: write 11 | runs-on: ubuntu-latest 12 | environment: pypi-release 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.x' 21 | 22 | - name: Install Python deps 23 | run: pip install -U wheel build certifi 24 | 25 | - name: Build the package 26 | run: make build 27 | env: 28 | LINODE_SDK_VERSION: ${{ github.event.release.tag_name }} 29 | 30 | - name: Publish the release artifacts to PyPI 31 | uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # pin@release/v1.12.4 32 | -------------------------------------------------------------------------------- /.github/workflows/release-cross-repo-test.yml: -------------------------------------------------------------------------------- 1 | name: Release Ansible cross repository test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | types: [opened] # Workflow will only be executed when PR is opened to main branch 8 | workflow_dispatch: # Manual trigger 9 | 10 | 11 | jobs: 12 | ansible_integration_test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout linode_api4 repository 16 | uses: actions/checkout@v4 17 | with: 18 | fetch-depth: 0 19 | submodules: 'recursive' 20 | 21 | - name: update packages 22 | run: sudo apt-get update -y 23 | 24 | - name: install make 25 | run: sudo apt-get install -y build-essential 26 | 27 | - name: Set up Python 28 | uses: actions/setup-python@v5 29 | with: 30 | python-version: '3.10' 31 | 32 | - name: Checkout ansible repo 33 | uses: actions/checkout@v4 34 | with: 35 | repository: linode/ansible_linode 36 | path: .ansible/collections/ansible_collections/linode/cloud 37 | fetch-depth: 0 38 | submodules: 'recursive' 39 | 40 | - name: install dependencies 41 | run: | 42 | cd .ansible/collections/ansible_collections/linode/cloud 43 | pip install -r requirements.txt -r requirements-dev.txt --upgrade-strategy only-if-needed 44 | 45 | - name: install ansible dependencies 46 | run: ansible-galaxy collection install amazon.aws:==9.1.0 47 | 48 | - name: install collection 49 | run: | 50 | cd .ansible/collections/ansible_collections/linode/cloud 51 | make install 52 | 53 | - name: Install linode_api4 # Need to install from source after all ansible dependencies have been installed 54 | run: make install 55 | 56 | - name: replace existing keys 57 | run: | 58 | cd .ansible/collections/ansible_collections/linode/cloud 59 | rm -rf ~/.ansible/test && mkdir -p ~/.ansible/test && ssh-keygen -m PEM -q -t rsa -N '' -f ~/.ansible/test/id_rsa 60 | 61 | - name: Run Ansible Tests 62 | run: | 63 | cd .ansible/collections/ansible_collections/linode/cloud 64 | make testall 65 | env: 66 | LINODE_API_TOKEN: ${{ secrets.LINODE_TOKEN }} 67 | -------------------------------------------------------------------------------- /.github/workflows/release-notify-slack.yml: -------------------------------------------------------------------------------- 1 | name: Notify Dev DX Channel on Release 2 | on: 3 | release: 4 | types: [published] 5 | workflow_dispatch: null 6 | 7 | jobs: 8 | notify: 9 | if: github.repository == 'linode/linode_api4-python' 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Notify Slack - Main Message 13 | id: main_message 14 | uses: slackapi/slack-github-action@v2.1.0 15 | with: 16 | method: chat.postMessage 17 | token: ${{ secrets.SLACK_BOT_TOKEN }} 18 | payload: | 19 | channel: ${{ secrets.DEV_DX_SLACK_CHANNEL_ID }} 20 | blocks: 21 | - type: section 22 | text: 23 | type: mrkdwn 24 | text: "*New Release Published: _linode_api4-python_ <${{ github.event.release.html_url }}|${{ github.event.release.tag_name }}> is now live!* :tada:" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | build 4 | dist 5 | *.egg-info 6 | .eggs/* 7 | docs/_build/* 8 | .cache/* 9 | .coverage 10 | .pytest_cache/* 11 | .tox/* 12 | venv 13 | baked_version 14 | .vscode 15 | .DS_Store 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "e2e_scripts"] 2 | path = e2e_scripts 3 | url = https://github.com/linode/dx-e2e-test-scripts 4 | -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | linode_api4-python 2 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file for Sphinx projects 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | version: 2 5 | build: 6 | os: ubuntu-lts-latest 7 | tools: 8 | python: latest 9 | sphinx: 10 | configuration: docs/conf.py 11 | python: 12 | install: 13 | - method: pip 14 | path: . 15 | extra_requirements: 16 | - doc -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @linode/dx 2 | 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | :+1::tada: First off, we appreciate you taking the time to contribute! THANK YOU! :tada::+1: 4 | 5 | We put together the handy guide below to help you get support for your work. Read on! 6 | 7 | ## I Just Want to Ask the Maintainers a Question 8 | 9 | The [Linode Community](https://www.linode.com/community/questions/) is a great place to get additional support. 10 | 11 | ## How Do I Submit A (Good) Bug Report or Feature Request 12 | 13 | Please open a [GitHub issue](../../issues/new/choose) to report bugs or suggest features. 14 | 15 | Please accurately fill out the appropriate GitHub issue form. 16 | 17 | When filing an issue or feature request, help us avoid duplication and redundant effort -- check existing open or recently closed issues first. 18 | 19 | Detailed bug reports and requests are easier for us to work with. Please include the following in your issue: 20 | 21 | * A reproducible test case or series of steps 22 | * The version of our code being used 23 | * Any modifications you've made, relevant to the bug 24 | * Anything unusual about your environment or deployment 25 | * Screenshots and code samples where illustrative and helpful 26 | 27 | ## How to Open a Pull Request 28 | 29 | We follow the [fork and pull model](https://opensource.guide/how-to-contribute/#opening-a-pull-request) for open source contributions. 30 | 31 | Tips for a faster merge: 32 | * address one feature or bug per pull request. 33 | * large formatting changes make it hard for us to focus on your work. 34 | * follow language coding conventions. 35 | * make sure that tests pass. 36 | * make sure your commits are atomic, [addressing one change per commit](https://chris.beams.io/posts/git-commit/). 37 | * add tests! 38 | 39 | ## Code of Conduct 40 | 41 | This project follows the [Linode Community Code of Conduct](https://www.linode.com/community/questions/conduct). 42 | 43 | ## Vulnerability Reporting 44 | 45 | If you discover a potential security issue in this project we ask that you notify Linode Security via our [vulnerability reporting process](https://hackerone.com/linode). Please do **not** create a public github issue. 46 | 47 | ## Licensing 48 | 49 | See the [LICENSE file](/LICENSE) for our project's licensing. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Linode, LLC 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | 3. Neither the name of the copyright holder nor the names of its contributors 15 | may be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft test 2 | global-exclude *.pyc 3 | include baked_version -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PYTHON ?= python3 2 | 3 | LINODE_SDK_VERSION ?= "0.0.0.dev" 4 | VERSION_MODULE_DOCSTRING ?= \"\"\"\nThe version of this linode_api4 package.\n\"\"\"\n\n 5 | VERSION_FILE := ./linode_api4/version.py 6 | 7 | .PHONY: clean 8 | clean: 9 | mkdir -p dist 10 | rm -r dist 11 | rm -f baked_version 12 | 13 | .PHONY: build 14 | build: clean create-version 15 | $(PYTHON) -m build --wheel --sdist 16 | 17 | .PHONY: create-version 18 | create-version: 19 | @printf "${VERSION_MODULE_DOCSTRING}__version__ = \"${LINODE_SDK_VERSION}\"\n" > $(VERSION_FILE) 20 | 21 | .PHONY: release 22 | release: build 23 | $(PYTHON) -m twine upload dist/* 24 | 25 | .PHONY: dev-install 26 | dev-install: clean 27 | $(PYTHON) -m pip install -e ".[dev]" 28 | 29 | .PHONY: install 30 | install: clean create-version 31 | $(PYTHON) -m pip install . 32 | 33 | .PHONY: black 34 | black: 35 | $(PYTHON) -m black linode_api4 test 36 | 37 | .PHONY: isort 38 | isort: 39 | $(PYTHON) -m isort linode_api4 test 40 | 41 | .PHONY: autoflake 42 | autoflake: 43 | $(PYTHON) -m autoflake linode_api4 test 44 | 45 | .PHONY: format 46 | format: black isort autoflake 47 | 48 | .PHONY: lint 49 | lint: build 50 | $(PYTHON) -m isort --check-only linode_api4 test 51 | $(PYTHON) -m autoflake --check linode_api4 test 52 | $(PYTHON) -m black --check --verbose linode_api4 test 53 | $(PYTHON) -m pylint linode_api4 54 | $(PYTHON) -m twine check dist/* 55 | 56 | # Integration Test Arguments 57 | # TEST_SUITE: Optional, specify a test suite (e.g. domain), Default to run everything if not set 58 | # TEST_CASE: Optional, specify a test case (e.g. 'test_image_replication') 59 | # TEST_ARGS: Optional, additional arguments for pytest (e.g. '-v' for verbose mode) 60 | 61 | TEST_COMMAND = $(if $(TEST_SUITE),$(if $(filter $(TEST_SUITE),linode_client login_client),$(TEST_SUITE),models/$(TEST_SUITE))) 62 | 63 | .PHONY: test-int 64 | test-int: 65 | $(PYTHON) -m pytest test/integration/${TEST_COMMAND} $(if $(TEST_CASE),-k $(TEST_CASE)) ${TEST_ARGS} 66 | 67 | .PHONY: test-unit 68 | test-unit: 69 | $(PYTHON) -m pytest test/unit 70 | 71 | .PHONY: test-smoke 72 | test-smoke: 73 | $(PYTHON) -m pytest -m smoke test/integration -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = linode_api4 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | linode_api4 2 | =========== 3 | 4 | This is the documentation for the official Python bindings of the Linode 5 | API v4. For API documentation, see `techdocs.akamai.com`_. 6 | 7 | This library can be used to interact with all features of the Linode API. 8 | 9 | .. _techdocs.akamai.com: https://techdocs.akamai.com/linode-api/reference/api 10 | 11 | Installation 12 | ------------ 13 | 14 | To install through pypi:: 15 | 16 | pip install linode_api4 17 | 18 | To install from source:: 19 | 20 | git clone https://github.com/linode/linode_api4-python 21 | cd linode_api4 22 | python -m pip install . 23 | 24 | For more information, see our :doc:`Getting Started` 25 | guide. 26 | 27 | Table of Contents 28 | ----------------- 29 | 30 | .. toctree:: 31 | :maxdepth: 2 32 | 33 | guides/getting_started 34 | guides/core_concepts 35 | guides/event_polling 36 | guides/oauth 37 | linode_api4/linode_client 38 | linode_api4/login_client 39 | linode_api4/objects/models 40 | linode_api4/polling 41 | linode_api4/paginated_list 42 | linode_api4/objects/filtering 43 | -------------------------------------------------------------------------------- /docs/linode_api4/login_client.rst: -------------------------------------------------------------------------------- 1 | Linode Login Client 2 | =================== 3 | 4 | .. module:: linode_api4 5 | 6 | The :any:`LinodeLoginClient` is the primary interface to the 7 | `login.linode.com`_ OAuth service, and only needs to be used if writing an 8 | OAuth application. For an example OAuth application, see `Install on Linode`_, 9 | and for a more comprehensive overview of OAuth, read our :doc:`OAuth 10 | guide<../guides/oauth>`. 11 | 12 | .. _login.linode.com: https://login.linode.com 13 | .. _Install on Linode: https://github.com/linode/linode_api4-python/tree/master/examples/install-on-linode 14 | 15 | LinodeLoginClient class 16 | ----------------------- 17 | 18 | Your interface to Linode's OAuth authentication server. 19 | 20 | .. autoclass:: linode_api4.LinodeLoginClient 21 | :members: 22 | 23 | .. automethod:: __init__ 24 | 25 | OAuth Scopes 26 | ------------ 27 | 28 | When requesting authorization to a user's account, OAuth Scopes allow you to 29 | specify the level of access you are requesting. 30 | 31 | .. autoclass:: linode_api4.login_client.OAuthScopes 32 | :members: 33 | -------------------------------------------------------------------------------- /docs/linode_api4/objects/filtering.rst: -------------------------------------------------------------------------------- 1 | Filtering Collections 2 | ===================== 3 | 4 | .. automodule:: linode_api4.objects.filtering 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/linode_api4/paginated_list.rst: -------------------------------------------------------------------------------- 1 | Pagination 2 | ========== 3 | 4 | The Linode API V4 returns collections of resources one page at a time. While 5 | this is useful, this library abstracts away the details of pagination and makes 6 | collections of resources appear as a single, uniform list that can be accessed, 7 | iterated over, and indexed as any normal Python list would be:: 8 | 9 | regions = client.regions() # get a collection of Regions 10 | 11 | for region in regions: 12 | print(region.id) 13 | 14 | first_region = regions[0] 15 | last_region = regions[-1] 16 | 17 | Pagination is handled transparently, and as requested. For example, if you had 18 | three pages of Linode Instances, accessing your collection of Instances would 19 | behave like this:: 20 | 21 | instances = client.linode.instances() # loads the first page only 22 | 23 | instances[0] # no additional data is loaded 24 | 25 | instances[-1] # third page is loaded to retrieve the last Linode in the collection 26 | 27 | for instance in instances: 28 | # the second page will be loaded as soon as the first Linode on that page 29 | # is required. The first and third pages are already loaded, and will not 30 | # be loaded again. 31 | print(instance.label) 32 | 33 | The first page of a collection is always loaded when the collection is 34 | returned, and subsequent pages are loaded as they are required. When slicing 35 | a paginated list, only the pages required for the slice are loaded. 36 | 37 | PaginatedList class 38 | ------------------- 39 | 40 | .. autoclass:: linode_api4.PaginatedList 41 | :members: first, only, last 42 | -------------------------------------------------------------------------------- /docs/linode_api4/polling.rst: -------------------------------------------------------------------------------- 1 | Event Polling 2 | ========== 3 | 4 | This project exposes a framework for dynamically polling on long-running Linode Events. 5 | 6 | See the :doc:`Event Polling Guide<../guides/event_polling>` for more details. 7 | 8 | EventPoller class 9 | ------------------- 10 | 11 | .. autoclass:: linode_api4.EventPoller 12 | :members: 13 | -------------------------------------------------------------------------------- /examples/install-on-linode/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.pyc 3 | config.py 4 | -------------------------------------------------------------------------------- /examples/install-on-linode/config.py.example: -------------------------------------------------------------------------------- 1 | """ 2 | These are all your configuration values. Copy this file to config.py and 3 | substitute these placeholders for your own. config.py is excluded from source 4 | control. 5 | 6 | OAuth Client Details 7 | ==================== 8 | These values are obtained by creating a new OAuth Client on 9 | https://cloud.linode.com/profile/clients - see the README included here for 10 | more information. 11 | """ 12 | client_id = 'my-client-id' 13 | client_secret = 'my-client-secret' 14 | 15 | """ 16 | Application Details 17 | =================== 18 | stackscirpt_id - the stackscript to deploy on Linodes we are creating in 19 | this example application. Run ./make_stackscript.py to generate a public 20 | stackscript and put the ID it returns here. 21 | 22 | application_name - displayed to the user of this example application and 23 | used in the new Linode's label. Can be any string. 24 | 25 | secret_key - this flask application's secret key. Not very important since 26 | this is an example application and not for production deployment. 27 | """ 28 | stackscript_id = 320826 29 | application_name = 'my-application-name' 30 | secret_key = 'my-secret-key' 31 | -------------------------------------------------------------------------------- /examples/install-on-linode/make_stackscript.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python3 2 | 3 | from linode_api4 import LinodeClient, Image 4 | import config 5 | 6 | token = input("Please provide an OAuth Token: ") 7 | client = LinodeClient(token) 8 | s = client.linode.stackscript_create('Demonstration_Public', '#!/bin/bash', 9 | client.images(Image.is_public==True), is_public=True) 10 | print("StackScript created, use this ID: {}".format(s.id)) 11 | -------------------------------------------------------------------------------- /examples/install-on-linode/requirements.txt: -------------------------------------------------------------------------------- 1 | linode_api4 2 | Flask 3 | -------------------------------------------------------------------------------- /examples/install-on-linode/templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Install On Linode 4 | 6 | 56 | 57 | 58 |
59 | {% block content %} 60 | {% endblock %} 61 |
62 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /examples/install-on-linode/templates/configure.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |
5 |

Deploy {{application_name}} to a Linode

6 |

7 | This will create a brand new linode running {{application_name}} on your 8 | account and give you the credentials. 9 |

10 |
11 |
12 |
13 | 14 | 20 |
21 |
22 | 23 | 29 |
30 |
31 | 32 | 38 |
39 |
40 |
41 | 42 |
43 |
44 | 52 | {% endblock %} 53 | -------------------------------------------------------------------------------- /examples/install-on-linode/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |

Error

5 |

{{error}}

6 |
7 |
8 | Try Again 9 |
10 | {% endblock %} 11 | -------------------------------------------------------------------------------- /examples/install-on-linode/templates/success.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% block content %} 3 |
4 |

Success!

5 |

{{application_name}} has been deployed to {{linode.label}} in the {{linode.group}} group.

6 |
7 |
8 |
9 |

You can access your linode with the following command:

10 | ssh root@{{linode.ipv4[0]}} 11 |
12 |
13 |

Your root password is:

14 | {{password}} 15 |
16 |
17 | {% endblock %} 18 | -------------------------------------------------------------------------------- /linode_api4/__init__.py: -------------------------------------------------------------------------------- 1 | # isort: skip_file 2 | from linode_api4.objects import * 3 | from linode_api4.errors import ApiError, UnexpectedResponseError 4 | from linode_api4.linode_client import LinodeClient 5 | from linode_api4.login_client import LinodeLoginClient, OAuthScopes 6 | from linode_api4.paginated_list import PaginatedList 7 | from linode_api4.polling import EventPoller 8 | -------------------------------------------------------------------------------- /linode_api4/common.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dataclasses import dataclass 3 | 4 | from linode_api4.objects import JSONObject 5 | 6 | SSH_KEY_TYPES = ( 7 | "ssh-dss", 8 | "ssh-rsa", 9 | "ssh-ed25519", 10 | "ecdsa-sha2-nistp256", 11 | "ecdsa-sha2-nistp384", 12 | "ecdsa-sha2-nistp521", 13 | ) 14 | 15 | 16 | def load_and_validate_keys(authorized_keys): 17 | """ 18 | Loads authorized_keys as taken by :any:`instance_create`, 19 | :any:`disk_create` or :any:`rebuild`, and loads in any keys from any files 20 | provided. 21 | 22 | :param authorized_keys: A list of keys or paths to keys, or a single key 23 | 24 | :returns: A list of raw keys 25 | :raises: ValueError if keys in authorized_keys don't appear to be a raw 26 | key and can't be opened. 27 | """ 28 | if not authorized_keys: 29 | return None 30 | 31 | if not isinstance(authorized_keys, list): 32 | authorized_keys = [authorized_keys] 33 | 34 | ret = [] 35 | 36 | for k in authorized_keys: 37 | accepted_types = ( 38 | "ssh-dss", 39 | "ssh-rsa", 40 | "ecdsa-sha2-nistp", 41 | "ssh-ed25519", 42 | ) 43 | if any( 44 | [t for t in accepted_types if k.startswith(t)] 45 | ): # pylint: disable=use-a-generator 46 | # this looks like a key, cool 47 | ret.append(k) 48 | else: 49 | # it doesn't appear to be a key.. is it a path to the key? 50 | k = os.path.expanduser(k) 51 | if os.path.isfile(k): 52 | with open(k) as f: 53 | ret.append(f.read().rstrip()) 54 | else: 55 | raise ValueError( 56 | "authorized_keys must either be paths " 57 | "to the key files or a list of raw " 58 | "public key of one of these types: {}".format( 59 | accepted_types 60 | ) 61 | ) 62 | return ret 63 | 64 | 65 | @dataclass 66 | class Price(JSONObject): 67 | """ 68 | Price contains the core fields of a price object returned by various pricing endpoints. 69 | """ 70 | 71 | hourly: int = 0 72 | monthly: int = 0 73 | 74 | 75 | @dataclass 76 | class RegionPrice(JSONObject): 77 | """ 78 | RegionPrice contains the core fields of a region_price object returned by various pricing endpoints. 79 | """ 80 | 81 | id: int = 0 82 | hourly: int = 0 83 | monthly: int = 0 84 | -------------------------------------------------------------------------------- /linode_api4/groups/__init__.py: -------------------------------------------------------------------------------- 1 | # Group needs to be imported first 2 | from .group import * # isort: skip 3 | 4 | from .account import * 5 | from .beta import * 6 | from .database import * 7 | from .domain import * 8 | from .image import * 9 | from .linode import * 10 | from .lke import * 11 | from .lke_tier import * 12 | from .longview import * 13 | from .monitor import * 14 | from .networking import * 15 | from .nodebalancer import * 16 | from .object_storage import * 17 | from .placement import * 18 | from .polling import * 19 | from .profile import * 20 | from .region import * 21 | from .support import * 22 | from .tag import * 23 | from .volume import * 24 | from .vpc import * 25 | -------------------------------------------------------------------------------- /linode_api4/groups/beta.py: -------------------------------------------------------------------------------- 1 | from linode_api4.groups import Group 2 | from linode_api4.objects import BetaProgram 3 | 4 | 5 | class BetaProgramGroup(Group): 6 | """ 7 | This group encapsulates all endpoints under /betas, including viewing 8 | available active beta programs. 9 | """ 10 | 11 | def betas(self, *filters): 12 | """ 13 | Returns a list of available active Beta Programs. 14 | 15 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-beta-programs 16 | 17 | :param filters: Any number of filters to apply to this query. 18 | See :doc:`Filtering Collections` 19 | for more details on filtering. 20 | 21 | :returns: A list of Beta Programs that matched the query. 22 | :rtype: PaginatedList of BetaProgram 23 | """ 24 | return self.client._get_and_filter(BetaProgram, *filters) 25 | -------------------------------------------------------------------------------- /linode_api4/groups/domain.py: -------------------------------------------------------------------------------- 1 | from linode_api4.errors import UnexpectedResponseError 2 | from linode_api4.groups import Group 3 | from linode_api4.objects import Domain 4 | 5 | 6 | class DomainGroup(Group): 7 | def __call__(self, *filters): 8 | """ 9 | Retrieves all of the Domains the acting user has access to. 10 | 11 | This is intended to be called off of the :any:`LinodeClient` 12 | class, like this:: 13 | 14 | domains = client.domains() 15 | 16 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-domains 17 | 18 | :param filters: Any number of filters to apply to this query. 19 | See :doc:`Filtering Collections` 20 | for more details on filtering. 21 | 22 | :returns: A list of Domains the acting user can access. 23 | :rtype: PaginatedList of Domain 24 | """ 25 | return self.client._get_and_filter(Domain, *filters) 26 | 27 | def create(self, domain, master=True, **kwargs): 28 | """ 29 | Registers a new Domain on the acting user's account. Make sure to point 30 | your registrar to Linode's nameservers so that Linode's DNS manager will 31 | correctly serve your domain. 32 | 33 | API Documentation: https://techdocs.akamai.com/linode-api/reference/post-domain 34 | 35 | :param domain: The domain to register to Linode's DNS manager. 36 | :type domain: str 37 | :param master: Whether this is a master (defaults to true) 38 | :type master: bool 39 | :param tags: A list of tags to apply to the new domain. If any of the 40 | tags included do not exist, they will be created as part of 41 | this operation. 42 | :type tags: list[str] 43 | 44 | :returns: The new Domain object. 45 | :rtype: Domain 46 | """ 47 | params = { 48 | "domain": domain, 49 | "type": "master" if master else "slave", 50 | } 51 | params.update(kwargs) 52 | 53 | result = self.client.post("/domains", data=params) 54 | 55 | if not "id" in result: 56 | raise UnexpectedResponseError( 57 | "Unexpected response when creating Domain!", json=result 58 | ) 59 | 60 | d = Domain(self.client, result["id"], result) 61 | return d 62 | -------------------------------------------------------------------------------- /linode_api4/groups/group.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import TYPE_CHECKING 4 | 5 | if TYPE_CHECKING: 6 | from linode_api4 import LinodeClient 7 | 8 | 9 | class Group: 10 | def __init__(self, client: LinodeClient): 11 | self.client = client 12 | -------------------------------------------------------------------------------- /linode_api4/groups/lke_tier.py: -------------------------------------------------------------------------------- 1 | from linode_api4.groups import Group 2 | from linode_api4.objects import TieredKubeVersion 3 | 4 | 5 | class LKETierGroup(Group): 6 | """ 7 | Encapsulates methods related to a specific LKE tier. This 8 | should not be instantiated on its own, but should instead be used through 9 | an instance of :any:`LinodeClient`:: 10 | 11 | client = LinodeClient(token) 12 | instances = client.lke.tier("standard") # use the LKETierGroup 13 | 14 | This group contains all features beneath the `/lke/tiers/{tier}` group in the API v4. 15 | """ 16 | 17 | def __init__(self, client: "LinodeClient", tier: str): 18 | super().__init__(client) 19 | self.tier = tier 20 | 21 | def versions(self, *filters): 22 | """ 23 | Returns a paginated list of versions for this tier matching the given filters. 24 | 25 | API Documentation: Not Yet Available 26 | 27 | :param filters: Any number of filters to apply to this query. 28 | See :doc:`Filtering Collections` 29 | for more details on filtering. 30 | 31 | :returns: A paginated list of kube versions that match the query. 32 | :rtype: PaginatedList of TieredKubeVersion 33 | """ 34 | 35 | return self.client._get_and_filter( 36 | TieredKubeVersion, 37 | endpoint=f"/lke/tiers/{self.tier}/versions", 38 | parent_id=self.tier, 39 | *filters, 40 | ) 41 | -------------------------------------------------------------------------------- /linode_api4/groups/nodebalancer.py: -------------------------------------------------------------------------------- 1 | from linode_api4.errors import UnexpectedResponseError 2 | from linode_api4.groups import Group 3 | from linode_api4.objects import Base, NodeBalancer, NodeBalancerType 4 | 5 | 6 | class NodeBalancerGroup(Group): 7 | def __call__(self, *filters): 8 | """ 9 | Retrieves all of the NodeBalancers the acting user has access to. 10 | 11 | This is intended to be called off of the :any:`LinodeClient` 12 | class, like this:: 13 | 14 | nodebalancers = client.nodebalancers() 15 | 16 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-node-balancers 17 | 18 | :param filters: Any number of filters to apply to this query. 19 | See :doc:`Filtering Collections` 20 | for more details on filtering. 21 | 22 | :returns: A list of NodeBalancers the acting user can access. 23 | :rtype: PaginatedList of NodeBalancers 24 | """ 25 | return self.client._get_and_filter(NodeBalancer, *filters) 26 | 27 | def create(self, region, **kwargs): 28 | """ 29 | Creates a new NodeBalancer in the given Region. 30 | 31 | API Documentation: https://techdocs.akamai.com/linode-api/reference/post-node-balancer 32 | 33 | :param region: The Region in which to create the NodeBalancer. 34 | :type region: Region or str 35 | 36 | :returns: The new NodeBalancer 37 | :rtype: NodeBalancer 38 | """ 39 | params = { 40 | "region": region.id if isinstance(region, Base) else region, 41 | } 42 | params.update(kwargs) 43 | 44 | result = self.client.post("/nodebalancers", data=params) 45 | 46 | if not "id" in result: 47 | raise UnexpectedResponseError( 48 | "Unexpected response when creating Nodebalaner!", json=result 49 | ) 50 | 51 | n = NodeBalancer(self.client, result["id"], result) 52 | return n 53 | 54 | def types(self, *filters): 55 | """ 56 | Returns a :any:`PaginatedList` of :any:`NodeBalancerType` objects that represents a valid NodeBalancer type. 57 | 58 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-node-balancer-types 59 | 60 | :param filters: Any number of filters to apply to this query. 61 | See :doc:`Filtering Collections` 62 | for more details on filtering. 63 | 64 | :returns: A Paginated List of NodeBalancer types that match the query. 65 | :rtype: PaginatedList of NodeBalancerType 66 | """ 67 | 68 | return self.client._get_and_filter( 69 | NodeBalancerType, *filters, endpoint="/nodebalancers/types" 70 | ) 71 | -------------------------------------------------------------------------------- /linode_api4/groups/region.py: -------------------------------------------------------------------------------- 1 | from linode_api4.groups import Group 2 | from linode_api4.objects import Region 3 | from linode_api4.objects.region import RegionAvailabilityEntry 4 | 5 | 6 | class RegionGroup(Group): 7 | def __call__(self, *filters): 8 | """ 9 | Returns the available Regions for Linode products. 10 | 11 | This is intended to be called off of the :any:`LinodeClient` 12 | class, like this:: 13 | 14 | region = client.regions() 15 | 16 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-regions 17 | 18 | :param filters: Any number of filters to apply to this query. 19 | See :doc:`Filtering Collections` 20 | for more details on filtering. 21 | 22 | :returns: A list of available Regions. 23 | :rtype: PaginatedList of Region 24 | """ 25 | 26 | return self.client._get_and_filter(Region, *filters) 27 | 28 | def availability(self, *filters): 29 | """ 30 | Returns the availability of Linode plans within a Region. 31 | 32 | 33 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-regions-availability 34 | 35 | :param filters: Any number of filters to apply to this query. 36 | See :doc:`Filtering Collections` 37 | for more details on filtering. 38 | 39 | :returns: A list of entries describing the availability of a plan in a region. 40 | :rtype: PaginatedList of RegionAvailabilityEntry 41 | """ 42 | 43 | return self.client._get_and_filter( 44 | RegionAvailabilityEntry, *filters, endpoint="/regions/availability" 45 | ) 46 | -------------------------------------------------------------------------------- /linode_api4/objects/__init__.py: -------------------------------------------------------------------------------- 1 | # isort: skip_file 2 | from .base import Base, Property, MappedObject, DATE_FORMAT, ExplicitNullValue 3 | from .dbase import DerivedBase 4 | from .serializable import JSONObject 5 | from .filtering import and_, or_ 6 | from .region import Region 7 | from .image import Image 8 | from .linode import * 9 | from .volume import * 10 | from .domain import * 11 | from .account import * 12 | from .networking import * 13 | from .nodebalancer import * 14 | from .support import * 15 | from .profile import * 16 | from .longview import * 17 | from .tag import Tag 18 | from .object_storage import * 19 | from .lke import * 20 | from .database import * 21 | from .vpc import * 22 | from .beta import * 23 | from .placement import * 24 | from .monitor import * 25 | -------------------------------------------------------------------------------- /linode_api4/objects/beta.py: -------------------------------------------------------------------------------- 1 | from linode_api4.objects import Base, Property 2 | 3 | 4 | class BetaProgram(Base): 5 | """ 6 | Beta program is a new product or service that's not generally available to all customers. 7 | User with permissions can enroll into a beta program and access the functionalities. 8 | 9 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-beta-program 10 | """ 11 | 12 | api_endpoint = "/betas/{id}" 13 | 14 | properties = { 15 | "id": Property(identifier=True), 16 | "label": Property(), 17 | "description": Property(), 18 | "started": Property(is_datetime=True), 19 | "ended": Property(is_datetime=True), 20 | "greenlight_only": Property(), 21 | "more_info": Property(), 22 | } 23 | -------------------------------------------------------------------------------- /linode_api4/objects/dbase.py: -------------------------------------------------------------------------------- 1 | from linode_api4.objects import Base 2 | 3 | 4 | class DerivedBase(Base): 5 | """ 6 | The DerivedBase class holds information about an object who belongs to another object 7 | (for example, a disk belongs to a linode). These objects have their own endpoints, 8 | but they are below another object in the hierarchy (i.e. /linodes/lnde_123/disks/disk_123) 9 | """ 10 | 11 | derived_url_path = "" # override in child classes 12 | parent_id_name = "parent_id" # override in child classes 13 | 14 | def __init__(self, client, id, parent_id, json={}): 15 | self._set(type(self).parent_id_name, parent_id) 16 | 17 | Base.__init__(self, client, id, json=json) 18 | 19 | @classmethod 20 | def _api_get_derived(cls, parent, client): 21 | base_url = "{}/{}".format( 22 | type(parent).api_endpoint, cls.derived_url_path 23 | ) 24 | 25 | return client._get_objects( 26 | base_url, cls, model=parent, parent_id=parent.id 27 | ) 28 | -------------------------------------------------------------------------------- /linode_api4/objects/longview.py: -------------------------------------------------------------------------------- 1 | from linode_api4.objects import Base, Property 2 | 3 | 4 | class LongviewClient(Base): 5 | """ 6 | A Longview Client that is accessible for use. Longview is Linode’s system data graphing service. 7 | 8 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-longview-client 9 | """ 10 | 11 | api_endpoint = "/longview/clients/{id}" 12 | 13 | properties = { 14 | "id": Property(identifier=True), 15 | "created": Property(is_datetime=True), 16 | "updated": Property(is_datetime=True), 17 | "label": Property(mutable=True), 18 | "install_code": Property(), 19 | "apps": Property(), 20 | "api_key": Property(), 21 | } 22 | 23 | 24 | class LongviewSubscription(Base): 25 | """ 26 | Contains the Longview Plan details for a specific subscription id. 27 | 28 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-longview-subscription 29 | """ 30 | 31 | api_endpoint = "/longview/subscriptions/{id}" 32 | 33 | properties = { 34 | "id": Property(identifier=True), 35 | "label": Property(), 36 | "clients_included": Property(), 37 | "price": Property(), 38 | } 39 | 40 | 41 | class LongviewPlan(Base): 42 | """ 43 | The current Longview Plan an account is using. 44 | 45 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-longview-plan 46 | """ 47 | 48 | api_endpoint = "/longview/plan" 49 | 50 | properties = { 51 | "id": Property(identifier=True), 52 | "label": Property(), 53 | "clients_included": Property(), 54 | "price": Property(), 55 | } 56 | -------------------------------------------------------------------------------- /linode_api4/objects/region.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import List, Optional 3 | 4 | from linode_api4.errors import UnexpectedResponseError 5 | from linode_api4.objects.base import Base, JSONObject, Property 6 | 7 | 8 | @dataclass 9 | class RegionPlacementGroupLimits(JSONObject): 10 | """ 11 | Represents the Placement Group limits for the current account 12 | in a specific region. 13 | """ 14 | 15 | maximum_pgs_per_customer: int = 0 16 | maximum_linodes_per_pg: int = 0 17 | 18 | 19 | class Region(Base): 20 | """ 21 | A Region. Regions correspond to individual data centers, each located in a different geographical area. 22 | 23 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-region 24 | """ 25 | 26 | api_endpoint = "/regions/{id}" 27 | properties = { 28 | "id": Property(identifier=True), 29 | "country": Property(), 30 | "capabilities": Property(unordered=True), 31 | "status": Property(), 32 | "resolvers": Property(), 33 | "label": Property(), 34 | "site_type": Property(), 35 | "placement_group_limits": Property( 36 | json_object=RegionPlacementGroupLimits 37 | ), 38 | } 39 | 40 | @property 41 | def availability(self) -> List["RegionAvailabilityEntry"]: 42 | result = self._client.get( 43 | f"{self.api_endpoint}/availability", model=self 44 | ) 45 | 46 | if result is None: 47 | raise UnexpectedResponseError( 48 | "Expected availability data, got None." 49 | ) 50 | 51 | return [RegionAvailabilityEntry.from_json(v) for v in result] 52 | 53 | 54 | @dataclass 55 | class RegionAvailabilityEntry(JSONObject): 56 | """ 57 | Represents the availability of a Linode type within a region. 58 | 59 | API Documentation: https://techdocs.akamai.com/linode-api/reference/get-region-availability 60 | """ 61 | 62 | region: Optional[str] = None 63 | plan: Optional[str] = None 64 | available: bool = False 65 | -------------------------------------------------------------------------------- /linode_api4/util.py: -------------------------------------------------------------------------------- 1 | """ 2 | Contains various utility functions. 3 | """ 4 | 5 | from typing import Any, Dict 6 | 7 | 8 | def drop_null_keys(data: Dict[Any, Any], recursive=True) -> Dict[Any, Any]: 9 | """ 10 | Traverses a dict and drops any keys that map to None values. 11 | """ 12 | 13 | if not recursive: 14 | return {k: v for k, v in data.items() if v is not None} 15 | 16 | def recursive_helper(value: Any) -> Any: 17 | if isinstance(value, dict): 18 | return { 19 | k: recursive_helper(v) 20 | for k, v in value.items() 21 | if v is not None 22 | } 23 | 24 | if isinstance(value, list): 25 | return [recursive_helper(v) for v in value] 26 | 27 | return value 28 | 29 | return recursive_helper(data) 30 | -------------------------------------------------------------------------------- /linode_api4/version.py: -------------------------------------------------------------------------------- 1 | """ 2 | The version of this linode_api4 package. 3 | """ 4 | 5 | __version__ = "0.0.0.dev" 6 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools", "wheel"] 3 | build-backend = "setuptools.build_meta" 4 | 5 | 6 | [project] 7 | name = "linode_api4" 8 | authors = [{ name = "Linode", email = "devs@linode.com" }] 9 | description = "The official Python SDK for Linode API v4" 10 | readme = "README.rst" 11 | requires-python = ">=3.9" 12 | keywords = [ 13 | "akamai", 14 | "Akamai Connected Cloud", 15 | "linode", 16 | "cloud", 17 | "SDK", 18 | "Linode APIv4", 19 | ] 20 | license = { text = "BSD-3-Clause" } 21 | classifiers = [ 22 | "Development Status :: 5 - Production/Stable", 23 | "Intended Audience :: Developers", 24 | "Topic :: Software Development :: Libraries", 25 | "License :: OSI Approved :: BSD License", 26 | "Programming Language :: Python", 27 | "Programming Language :: Python :: 3", 28 | "Programming Language :: Python :: 3.9", 29 | "Programming Language :: Python :: 3.10", 30 | "Programming Language :: Python :: 3.11", 31 | "Programming Language :: Python :: 3.12", 32 | ] 33 | dependencies = ["requests", "polling", "deprecated"] 34 | dynamic = ["version"] 35 | 36 | [project.optional-dependencies] 37 | test = ["tox>=4.4.0"] 38 | 39 | dev = [ 40 | "tox>=4.4.0", 41 | "mock>=5.0.0", 42 | "pytest>=7.3.1", 43 | "httpretty>=1.1.4", 44 | "black>=23.1.0", 45 | "isort>=5.12.0", 46 | "autoflake>=2.0.1", 47 | "pylint", 48 | "twine>=4.0.2", 49 | "build>=0.10.0", 50 | "Sphinx>=6.0.0", 51 | "sphinx-autobuild>=2021.3.14", 52 | "sphinxcontrib-fulltoc>=1.2.0", 53 | "build>=0.10.0", 54 | "twine>=4.0.2", 55 | "pytest-rerunfailures", 56 | ] 57 | 58 | doc = [ 59 | "Sphinx>=6.0.0", 60 | "sphinx-autobuild>=2021.3.14", 61 | "sphinxcontrib-fulltoc>=1.2.0", 62 | ] 63 | 64 | [project.urls] 65 | Homepage = "https://github.com/linode/linode_api4-python" 66 | Documentation = "https://linode-api4.readthedocs.io/" 67 | Repository = "https://github.com/linode/linode_api4-python.git" 68 | 69 | [tool.setuptools.dynamic] 70 | version = { attr = "linode_api4.version.__version__" } 71 | 72 | [tool.setuptools.packages.find] 73 | exclude = ['contrib', 'docs', 'test', 'test.*'] 74 | 75 | [tool.isort] 76 | profile = "black" 77 | line_length = 80 78 | 79 | [tool.black] 80 | line-length = 80 81 | target-version = ["py38", "py39", "py310", "py311", "py312"] 82 | 83 | [tool.autoflake] 84 | expand-star-imports = true 85 | ignore-init-module-imports = true 86 | ignore-pass-after-docstring = true 87 | in-place = true 88 | recursive = true 89 | remove-all-unused-imports = true 90 | remove-duplicate-keys = false 91 | 92 | [tool.pytest.ini_options] 93 | markers = [ 94 | "smoke: mark a test as a smoke test", 95 | "flaky: mark a test as a flaky test for rerun" 96 | ] -------------------------------------------------------------------------------- /scripts/lke-policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: projectcalico.org/v3 2 | kind: GlobalNetworkPolicy 3 | metadata: 4 | name: lke-rules 5 | spec: 6 | preDNAT: true 7 | applyOnForward: true 8 | order: 100 9 | # Remember to run calicoctl patch command for this to work 10 | selector: "" 11 | ingress: 12 | # Allow ICMP 13 | - action: Allow 14 | protocol: ICMP 15 | - action: Allow 16 | protocol: ICMPv6 17 | 18 | # Allow LKE-required ports 19 | - action: Allow 20 | protocol: TCP 21 | destination: 22 | nets: 23 | - 192.168.128.0/17 24 | - 10.0.0.0/8 25 | ports: 26 | - 10250 27 | - 10256 28 | - 179 29 | - action: Allow 30 | protocol: UDP 31 | destination: 32 | nets: 33 | - 192.168.128.0/17 34 | - 10.2.0.0/16 35 | ports: 36 | - 51820 37 | 38 | # Allow NodeBalancer ingress to the Node Ports & Allow DNS 39 | - action: Allow 40 | protocol: TCP 41 | source: 42 | nets: 43 | - 192.168.255.0/24 44 | - 10.0.0.0/8 45 | destination: 46 | ports: 47 | - 53 48 | - 30000:32767 49 | - action: Allow 50 | protocol: UDP 51 | source: 52 | nets: 53 | - 192.168.255.0/24 54 | - 10.0.0.0/8 55 | destination: 56 | ports: 57 | - 53 58 | - 30000:32767 59 | 60 | # Allow cluster internal communication 61 | - action: Allow 62 | destination: 63 | nets: 64 | - 10.0.0.0/8 65 | - action: Allow 66 | source: 67 | nets: 68 | - 10.0.0.0/8 69 | 70 | # 127.0.0.1/32 is needed for kubectl exec and node-shell 71 | - action: Allow 72 | destination: 73 | nets: 74 | - 127.0.0.1/32 75 | 76 | # Block everything else 77 | - action: Deny 78 | - action: Log 79 | -------------------------------------------------------------------------------- /scripts/lke_calico_rules_e2e.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | RETRIES=3 4 | DELAY=30 5 | 6 | # Function to retry a command with exponential backoff 7 | retry_command() { 8 | local retries=$1 9 | local wait_time=60 10 | shift 11 | until "$@"; do 12 | if ((retries == 0)); then 13 | echo "Command failed after multiple retries. Exiting." 14 | exit 1 15 | fi 16 | echo "Command failed. Retrying in $wait_time seconds..." 17 | sleep $wait_time 18 | ((retries--)) 19 | wait_time=$((wait_time * 2)) 20 | done 21 | } 22 | 23 | # Fetch the list of LKE cluster IDs 24 | CLUSTER_IDS=$(curl -s -H "Authorization: Bearer $LINODE_TOKEN" \ 25 | -H "Content-Type: application/json" \ 26 | "https://api.linode.com/v4/lke/clusters" | jq -r '.data[].id') 27 | 28 | # Check if CLUSTER_IDS is empty 29 | if [ -z "$CLUSTER_IDS" ]; then 30 | echo "All clusters have been cleaned and properly destroyed. No need to apply inbound or outbound rules" 31 | exit 0 32 | fi 33 | 34 | for ID in $CLUSTER_IDS; do 35 | echo "Applying Calico rules to nodes in Cluster ID: $ID" 36 | 37 | # Download cluster configuration file with retry 38 | for ((i=1; i<=RETRIES; i++)); do 39 | config_response=$(curl -sH "Authorization: Bearer $LINODE_TOKEN" "https://api.linode.com/v4/lke/clusters/$ID/kubeconfig") 40 | if [[ $config_response != *"kubeconfig is not yet available"* ]]; then 41 | echo $config_response | jq -r '.[] | @base64d' > "/tmp/${ID}_config.yaml" 42 | break 43 | fi 44 | echo "Attempt $i to download kubeconfig for cluster $ID failed. Retrying in $DELAY seconds..." 45 | sleep $DELAY 46 | done 47 | 48 | if [[ $config_response == *"kubeconfig is not yet available"* ]]; then 49 | echo "kubeconfig for cluster id:$ID not available after $RETRIES attempts, mostly likely it is an empty cluster. Skipping..." 50 | else 51 | # Export downloaded config file 52 | export KUBECONFIG="/tmp/${ID}_config.yaml" 53 | 54 | retry_command $RETRIES kubectl get nodes 55 | 56 | retry_command $RETRIES calicoctl patch kubecontrollersconfiguration default --allow-version-mismatch --patch='{"spec": {"controllers": {"node": {"hostEndpoint": {"autoCreate": "Enabled"}}}}}' 57 | 58 | retry_command $RETRIES calicoctl apply --allow-version-mismatch -f "$(pwd)/lke-policy.yaml" 59 | fi 60 | done 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linode/linode_api4-python/7b7f6470c8d61f46f9561b1cdaa8fee7a090d607/test/__init__.py -------------------------------------------------------------------------------- /test/fixtures/account.json: -------------------------------------------------------------------------------- 1 | { 2 | "state": "PA", 3 | "city": "Philadelphia", 4 | "phone": "123-456-7890", 5 | "tax_id": "", 6 | "balance": 0, 7 | "company": "Linode", 8 | "address_2": "", 9 | "email": "support@linode.com", 10 | "address_1": "3rd & Arch St", 11 | "zip": "19106", 12 | "first_name": "Test", 13 | "last_name": "Guy", 14 | "country": "US", 15 | "capabilities": [ 16 | "Linodes", 17 | "NodeBalancers", 18 | "Block Storage", 19 | "Object Storage" 20 | ], 21 | "active_promotions": [ 22 | { 23 | "credit_monthly_cap": "10.00", 24 | "credit_remaining": "50.00", 25 | "description": "Receive up to $10 off your services every month for 6 months! Unused credits will expire once this promotion period ends.", 26 | "expire_dt": "2018-01-31T23:59:59", 27 | "image_url": "https://linode.com/10_a_month_promotion.svg", 28 | "service_type": "all", 29 | "summary": "$10 off your Linode a month!", 30 | "this_month_credit_remaining": "10.00" 31 | } 32 | ], 33 | "active_since": "2018-01-01T00:01:01", 34 | "balance_uninvoiced": 145, 35 | "billing_source": "akamai", 36 | "euuid": "E1AF5EEC-526F-487D-B317EBEB34C87D71" 37 | } 38 | -------------------------------------------------------------------------------- /test/fixtures/account_availability.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "region": "ap-west", 5 | "unavailable": [], 6 | "available": ["Linodes", "NodeBalancers"] 7 | }, 8 | { 9 | "region": "ca-central", 10 | "unavailable": [], 11 | "available": ["Linodes", "NodeBalancers"] 12 | }, 13 | { 14 | "region": "ap-southeast", 15 | "unavailable": [], 16 | "available": ["Linodes", "NodeBalancers"] 17 | }, 18 | { 19 | "region": "us-central", 20 | "unavailable": [], 21 | "available": ["Linodes", "NodeBalancers"] 22 | }, 23 | { 24 | "region": "us-west", 25 | "unavailable": [], 26 | "available": ["Linodes", "NodeBalancers"] 27 | }, 28 | { 29 | "region": "us-southeast", 30 | "unavailable": [], 31 | "available": ["Linodes", "NodeBalancers"] 32 | }, 33 | { 34 | "region": "us-east", 35 | "unavailable": [], 36 | "available": ["Linodes", "Kubernetes"] 37 | }, 38 | { 39 | "region": "eu-west", 40 | "unavailable": [], 41 | "available": ["Linodes", "Cloud Firewall"] 42 | }, 43 | { 44 | "region": "ap-south", 45 | "unavailable": [], 46 | "available": ["Linodes", "NodeBalancers"] 47 | }, 48 | { 49 | "region": "eu-central", 50 | "unavailable": [], 51 | "available": ["Linodes", "NodeBalancers"] 52 | }, 53 | { 54 | "region": "ap-northeast", 55 | "unavailable": [], 56 | "available": ["Linodes"] 57 | } 58 | ], 59 | "page": 1, 60 | "pages": 1, 61 | "results": 11 62 | } 63 | -------------------------------------------------------------------------------- /test/fixtures/account_availability_us-east.json: -------------------------------------------------------------------------------- 1 | { 2 | "region": "us-east", 3 | "unavailable": [], 4 | "available": ["Linodes", "Kubernetes"] 5 | } -------------------------------------------------------------------------------- /test/fixtures/account_betas.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "cool", 5 | "label": "\r\n\r\nRepellat consequatur sunt qui.", 6 | "enrolled": "2018-01-02T03:04:05", 7 | "description": "Repellat consequatur sunt qui. Fugit eligendi ipsa et assumenda ea aspernatur esse. A itaque iste distinctio qui voluptas eum enim ipsa.", 8 | "started": "2018-01-02T03:04:05", 9 | "ended": "2018-01-02T03:04:05" 10 | } 11 | ], 12 | "page": 1, 13 | "pages": 1, 14 | "results": 1 15 | } -------------------------------------------------------------------------------- /test/fixtures/account_betas_cool.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "cool", 3 | "label": "\r\n\r\nRepellat consequatur sunt qui.", 4 | "enrolled": "2018-01-02T03:04:05", 5 | "description": "Repellat consequatur sunt qui. Fugit eligendi ipsa et assumenda ea aspernatur esse. A itaque iste distinctio qui voluptas eum enim ipsa.", 6 | "started": "2018-01-02T03:04:05", 7 | "ended": "2018-01-02T03:04:05" 8 | } -------------------------------------------------------------------------------- /test/fixtures/account_child-accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "active_since": "2018-01-01T00:01:01", 5 | "address_1": "123 Main Street", 6 | "address_2": "Suite A", 7 | "balance": 200, 8 | "balance_uninvoiced": 145, 9 | "billing_source": "external", 10 | "capabilities": [ 11 | "Linodes", 12 | "NodeBalancers", 13 | "Block Storage", 14 | "Object Storage" 15 | ], 16 | "city": "Philadelphia", 17 | "company": "Linode LLC", 18 | "country": "US", 19 | "credit_card": { 20 | "expiry": "11/2022", 21 | "last_four": 1111 22 | }, 23 | "email": "john.smith@linode.com", 24 | "euuid": "E1AF5EEC-526F-487D-B317EBEB34C87D71", 25 | "first_name": "John", 26 | "last_name": "Smith", 27 | "phone": "215-555-1212", 28 | "state": "PA", 29 | "tax_id": "ATU99999999", 30 | "zip": "19102-1234" 31 | } 32 | ], 33 | "page": 1, 34 | "pages": 1, 35 | "results": 1 36 | } 37 | -------------------------------------------------------------------------------- /test/fixtures/account_child-accounts_123456.json: -------------------------------------------------------------------------------- 1 | { 2 | "active_since": "2018-01-01T00:01:01", 3 | "address_1": "123 Main Street", 4 | "address_2": "Suite A", 5 | "balance": 200, 6 | "balance_uninvoiced": 145, 7 | "billing_source": "external", 8 | "capabilities": [ 9 | "Linodes", 10 | "NodeBalancers", 11 | "Block Storage", 12 | "Object Storage" 13 | ], 14 | "city": "Philadelphia", 15 | "company": "Linode LLC", 16 | "country": "US", 17 | "credit_card": { 18 | "expiry": "11/2022", 19 | "last_four": 1111 20 | }, 21 | "email": "john.smith@linode.com", 22 | "euuid": "E1AF5EEC-526F-487D-B317EBEB34C87D71", 23 | "first_name": "John", 24 | "last_name": "Smith", 25 | "phone": "215-555-1212", 26 | "state": "PA", 27 | "tax_id": "ATU99999999", 28 | "zip": "19102-1234" 29 | } -------------------------------------------------------------------------------- /test/fixtures/account_child-accounts_123456_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2024-01-01T00:01:01", 3 | "expiry": "2024-01-01T13:46:32", 4 | "id": 123, 5 | "label": "cool_customer_proxy", 6 | "scopes": "*", 7 | "token": "abcdefghijklmnop" 8 | } -------------------------------------------------------------------------------- /test/fixtures/account_events_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "ticket_create", 3 | "created": "2018-01-01T00:01:01", 4 | "duration": 300.56, 5 | "entity": { 6 | "id": 11111, 7 | "label": "Problem booting my Linode", 8 | "type": "ticket", 9 | "url": "/v4/support/tickets/11111" 10 | }, 11 | "id": 123, 12 | "message": "None", 13 | "percent_complete": null, 14 | "rate": null, 15 | "read": true, 16 | "secondary_entity": { 17 | "id": "linode/debian9", 18 | "label": "linode1234", 19 | "type": "linode", 20 | "url": "/v4/linode/instances/1234" 21 | }, 22 | "seen": true, 23 | "status": null, 24 | "time_remaining": null, 25 | "username": "exampleUser" 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/account_invoices.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 123456, 5 | "date": "2015-01-01T05:01:02", 6 | "label": "Invoice #123456", 7 | "total": 9.51 8 | } 9 | ], 10 | "page": 1, 11 | "pages": 1, 12 | "results": 1 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/account_invoices_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "date": "2018-01-01T00:01:01", 3 | "id": 123, 4 | "label": "Invoice", 5 | "subtotal": 120.25, 6 | "tax": 12.25, 7 | "tax_summary": [ 8 | { 9 | "name": "PA STATE TAX", 10 | "tax": 12.25 11 | } 12 | ], 13 | "total": 132.5 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/account_invoices_123456_items.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "from": "2014-12-19T00:27:02", 5 | "label": "Linode 2048 - Example", 6 | "type": "hourly", 7 | "amount": 9.51, 8 | "to": "2015-01-01T04:59:59", 9 | "quantity": 317, 10 | "unit_price": "0.03" 11 | } 12 | ], 13 | "page": 1, 14 | "pages": 1, 15 | "results": 1 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/account_logins.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "datetime": "2018-01-01T00:01:01", 5 | "id": 1234, 6 | "ip": "192.0.2.0", 7 | "restricted": true, 8 | "status": "successful", 9 | "username": "test-user" 10 | } 11 | ], 12 | "page": 1, 13 | "pages": 1, 14 | "results": 1 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/account_logins_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "datetime": "2018-01-01T00:01:01", 3 | "id": 123, 4 | "ip": "192.0.2.0", 5 | "restricted": true, 6 | "status": "successful", 7 | "username": "test-user" 8 | } -------------------------------------------------------------------------------- /test/fixtures/account_maintenance.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "entity": { 5 | "id": 123, 6 | "label": "demo-linode", 7 | "type": "Linode", 8 | "url": "https://api.linode.com/v4/linode/instances/{linodeId}" 9 | }, 10 | "reason": "This maintenance will allow us to update the BIOS on the host's motherboard.", 11 | "status": "started", 12 | "type": "reboot", 13 | "when": "2020-07-09T00:01:01" 14 | } 15 | ], 16 | "page": 1, 17 | "pages": 1, 18 | "results": 1 19 | } -------------------------------------------------------------------------------- /test/fixtures/account_notifications.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "body": null, 5 | "entity": { 6 | "id": 3456, 7 | "label": "Linode not booting.", 8 | "type": "ticket", 9 | "url": "/support/tickets/3456" 10 | }, 11 | "label": "You have an important ticket open!", 12 | "message": "You have an important ticket open!", 13 | "severity": "major", 14 | "type": "ticket_important", 15 | "until": null, 16 | "when": null 17 | } 18 | ], 19 | "page": 1, 20 | "pages": 1, 21 | "results": 1 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/account_oauth-clients_2737bf16b39ab5d7b4a1.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "2737bf16b39ab5d7b4a1", 3 | "label": "Test_Client_1", 4 | "public": false, 5 | "redirect_uri": "https://example.org/oauth/callback", 6 | "secret": "", 7 | "status": "active", 8 | "thumbnail_url": "https://api.linode.com/v4/account/clients/2737bf16b39ab5d7b4a1/thumbnail" 9 | } -------------------------------------------------------------------------------- /test/fixtures/account_payment-method_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2018-01-15T00:01:01", 3 | "data": { 4 | "card_type": "Discover", 5 | "expiry": "06/2022", 6 | "last_four": "1234" 7 | }, 8 | "id": 123, 9 | "is_default": true, 10 | "type": "credit_card" 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/account_payment-methods.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-15T00:01:01", 5 | "data": { 6 | "card_type": "Discover", 7 | "expiry": "06/2022", 8 | "last_four": "1234" 9 | }, 10 | "id": 123, 11 | "is_default": true, 12 | "type": "credit_card" 13 | } 14 | ], 15 | "page": 1, 16 | "pages": 1, 17 | "results": 1 18 | } -------------------------------------------------------------------------------- /test/fixtures/account_payments.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 123456, 5 | "date": "2015-01-01T05:01:02", 6 | "usd": 1000 7 | } 8 | ], 9 | "page": 1, 10 | "pages": 1, 11 | "results": 1 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/account_promo-codes.json: -------------------------------------------------------------------------------- 1 | { 2 | "credit_monthly_cap": "10.00", 3 | "credit_remaining": "50.00", 4 | "description": "Receive up to $10 off your services every month for 6 months! Unused credits will expire once this promotion period ends.", 5 | "expire_dt": "2018-01-31T23:59:59", 6 | "image_url": "https://linode.com/10_a_month_promotion.svg", 7 | "service_type": "all", 8 | "summary": "$10 off your Linode a month!", 9 | "this_month_credit_remaining": "10.00" 10 | } -------------------------------------------------------------------------------- /test/fixtures/account_service-transfers.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2021-02-11T16:37:03", 5 | "entities": { 6 | "linodes": [ 7 | 111, 8 | 222 9 | ] 10 | }, 11 | "expiry": "2021-02-12T16:37:03", 12 | "is_sender": true, 13 | "status": "pending", 14 | "token": "123E4567-E89B-12D3-A456-426614174000", 15 | "updated": "2021-02-11T16:37:03" 16 | } 17 | ], 18 | "page": 1, 19 | "pages": 1, 20 | "results": 1 21 | } -------------------------------------------------------------------------------- /test/fixtures/account_service-transfers_12345.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2021-02-11T16:37:03", 3 | "entities": { 4 | "linodes": [ 5 | 111, 6 | 222 7 | ] 8 | }, 9 | "expiry": "2021-02-12T16:37:03", 10 | "is_sender": true, 11 | "status": "pending", 12 | "token": "12345", 13 | "updated": "2021-02-11T16:37:03" 14 | } -------------------------------------------------------------------------------- /test/fixtures/account_settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "longview_subscription": "longview-100", 3 | "managed": false, 4 | "network_helper": false, 5 | "object_storage": "active", 6 | "backups_enabled": true 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/account_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "quota": 471, 3 | "used": 737373, 4 | "billable": 0, 5 | 6 | "region_transfers": [ 7 | { 8 | "id": "ap-west", 9 | "used": 1, 10 | "quota": 5010, 11 | "billable": 0 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /test/fixtures/account_users_test-user.json: -------------------------------------------------------------------------------- 1 | { 2 | "email": "test-user@linode.com", 3 | "restricted": true, 4 | "ssh_keys": [ 5 | "home-pc", 6 | "laptop" 7 | ], 8 | "tfa_enabled": true, 9 | "username": "test-user" 10 | } -------------------------------------------------------------------------------- /test/fixtures/betas.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "active_closed", 5 | "label": "active closed beta", 6 | "description": "An active closed beta", 7 | "started": "2023-07-19T15:23:43", 8 | "ended": null, 9 | "greenlight_only": true, 10 | "more_info": "a link with even more info" 11 | }, 12 | { 13 | "id": "limited", 14 | "label": "limited beta", 15 | "description": "An active limited beta", 16 | "started": "2023-07-19T15:23:43", 17 | "ended": null, "greenlight_only": false, 18 | "more_info": "a link with even more info" 19 | } 20 | ], 21 | "page": 1, 22 | "pages": 1, 23 | "results": 2 24 | } -------------------------------------------------------------------------------- /test/fixtures/betas_active.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "active", 3 | "label": "active closed beta", 4 | "description": "An active closed beta", 5 | "started": "2018-01-02T03:04:05", 6 | "ended": null, 7 | "greenlight_only": true, 8 | "more_info": "a link with even more info" 9 | } -------------------------------------------------------------------------------- /test/fixtures/databases_engines.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "engine": "mysql", 5 | "id": "mysql/8.0.26", 6 | "version": "8.0.26" 7 | }, 8 | { 9 | "engine": "postgresql", 10 | "id": "postgresql/10.14", 11 | "version": "10.14" 12 | } 13 | ], 14 | "page": 1, 15 | "pages": 1, 16 | "results": 2 17 | } -------------------------------------------------------------------------------- /test/fixtures/databases_instances.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "allow_list": [ 5 | "203.0.113.1/32", 6 | "192.0.1.0/24" 7 | ], 8 | "cluster_size": 3, 9 | "created": "2022-01-01T00:01:01", 10 | "encrypted": false, 11 | "engine": "mysql", 12 | "hosts": { 13 | "primary": "lin-123-456-mysql-mysql-primary.servers.linodedb.net", 14 | "secondary": "lin-123-456-mysql-primary-private.servers.linodedb.net" 15 | }, 16 | "id": 123, 17 | "instance_uri": "/v4/databases/mysql/instances/123", 18 | "label": "example-db", 19 | "region": "us-east", 20 | "status": "active", 21 | "type": "g6-dedicated-2", 22 | "updated": "2022-01-01T00:01:01", 23 | "updates": { 24 | "day_of_week": 1, 25 | "duration": 3, 26 | "frequency": "weekly", 27 | "hour_of_day": 0, 28 | "week_of_month": null 29 | }, 30 | "version": "8.0.26" 31 | } 32 | ], 33 | "page": 1, 34 | "pages": 1, 35 | "results": 1 36 | } -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "allow_list": [ 5 | "203.0.113.1/32", 6 | "192.0.1.0/24" 7 | ], 8 | "cluster_size": 3, 9 | "created": "2022-01-01T00:01:01", 10 | "encrypted": false, 11 | "engine": "mysql", 12 | "hosts": { 13 | "primary": "lin-123-456-mysql-mysql-primary.servers.linodedb.net", 14 | "secondary": "lin-123-456-mysql-primary-private.servers.linodedb.net" 15 | }, 16 | "id": 123, 17 | "label": "example-db", 18 | "port": 3306, 19 | "region": "us-east", 20 | "replication_type": "semi_synch", 21 | "ssl_connection": true, 22 | "status": "active", 23 | "type": "g6-dedicated-2", 24 | "updated": "2022-01-01T00:01:01", 25 | "updates": { 26 | "day_of_week": 1, 27 | "duration": 3, 28 | "frequency": "weekly", 29 | "hour_of_day": 0, 30 | "week_of_month": null 31 | }, 32 | "version": "8.0.26", 33 | "engine_config": { 34 | "binlog_retention_period": 600, 35 | "mysql": { 36 | "connect_timeout": 10, 37 | "default_time_zone": "+03:00", 38 | "group_concat_max_len": 1024, 39 | "information_schema_stats_expiry": 86400, 40 | "innodb_change_buffer_max_size": 30, 41 | "innodb_flush_neighbors": 0, 42 | "innodb_ft_min_token_size": 3, 43 | "innodb_ft_server_stopword_table": "db_name/table_name", 44 | "innodb_lock_wait_timeout": 50, 45 | "innodb_log_buffer_size": 16777216, 46 | "innodb_online_alter_log_max_size": 134217728, 47 | "innodb_read_io_threads": 10, 48 | "innodb_rollback_on_timeout": true, 49 | "innodb_thread_concurrency": 10, 50 | "innodb_write_io_threads": 10, 51 | "interactive_timeout": 3600, 52 | "internal_tmp_mem_storage_engine": "TempTable", 53 | "max_allowed_packet": 67108864, 54 | "max_heap_table_size": 16777216, 55 | "net_buffer_length": 16384, 56 | "net_read_timeout": 30, 57 | "net_write_timeout": 30, 58 | "sort_buffer_size": 262144, 59 | "sql_mode": "ANSI,TRADITIONAL", 60 | "sql_require_primary_key": true, 61 | "tmp_table_size": 16777216, 62 | "wait_timeout": 28800 63 | } 64 | } 65 | } 66 | ], 67 | "page": 1, 68 | "pages": 1, 69 | "results": 1 70 | } -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_backups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2022-01-01T00:01:01", 5 | "id": 456, 6 | "label": "Scheduled - 02/04/22 11:11 UTC-XcCRmI", 7 | "type": "auto" 8 | } 9 | ], 10 | "page": 1, 11 | "pages": 1, 12 | "results": 1 13 | } -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_backups_456_restore.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "password": "s3cur3P@ssw0rd", 3 | "username": "linroot" 4 | } -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_credentials_reset.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_patch.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_resume.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_ssl.json: -------------------------------------------------------------------------------- 1 | { 2 | "ca_certificate": "LS0tLS1CRUdJ...==" 3 | } -------------------------------------------------------------------------------- /test/fixtures/databases_mysql_instances_123_suspend.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_backups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2022-01-01T00:01:01", 5 | "id": 456, 6 | "label": "Scheduled - 02/04/22 11:11 UTC-XcCRmI", 7 | "type": "auto" 8 | } 9 | ], 10 | "page": 1, 11 | "pages": 1, 12 | "results": 1 13 | } -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_backups_456_restore.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_credentials.json: -------------------------------------------------------------------------------- 1 | { 2 | "password": "s3cur3P@ssw0rd", 3 | "username": "linroot" 4 | } -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_credentials_reset.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_patch.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_resume.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_ssl.json: -------------------------------------------------------------------------------- 1 | { 2 | "ca_certificate": "LS0tLS1CRUdJ...==" 3 | } -------------------------------------------------------------------------------- /test/fixtures/databases_postgresql_instances_123_suspend.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /test/fixtures/databases_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "class": "nanode", 5 | "deprecated": false, 6 | "disk": 25600, 7 | "engines": { 8 | "mysql": [ 9 | { 10 | "price": { 11 | "hourly": 0.03, 12 | "monthly": 20 13 | }, 14 | "quantity": 1 15 | } 16 | ], 17 | "postgresql": [ 18 | { 19 | "price": { 20 | "hourly": 0.03, 21 | "monthly": 20 22 | }, 23 | "quantity": 1 24 | } 25 | ] 26 | }, 27 | "id": "g6-nanode-1", 28 | "label": "DBaaS - Nanode 1GB", 29 | "memory": 1024, 30 | "vcpus": 1 31 | } 32 | ], 33 | "page": 1, 34 | "pages": 1, 35 | "results": 1 36 | } -------------------------------------------------------------------------------- /test/fixtures/domains.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 1, 4 | "results": 1, 5 | "data": [ 6 | { 7 | "domain": "example.org", 8 | "type": "master", 9 | "id": 12345, 10 | "axfr_ips": [], 11 | "retry_sec": 0, 12 | "ttl_sec": 300, 13 | "status": "active", 14 | "master_ips": [], 15 | "description": "", 16 | "group": "", 17 | "expire_sec": 0, 18 | "soa_email": "test@example.org", 19 | "refresh_sec": 0, 20 | "tags": ["something"] 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /test/fixtures/domains_12345_clone.json: -------------------------------------------------------------------------------- 1 | { 2 | "axfr_ips": [], 3 | "description": null, 4 | "domain": "example.org", 5 | "expire_sec": 300, 6 | "group": null, 7 | "id": 12345, 8 | "master_ips": [], 9 | "refresh_sec": 300, 10 | "retry_sec": 300, 11 | "soa_email": "admin@example.org", 12 | "status": "active", 13 | "tags": [ 14 | "example tag", 15 | "another example" 16 | ], 17 | "ttl_sec": 300, 18 | "type": "master" 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/domains_12345_records.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2018-01-01T00:01:01", 3 | "id": 123456, 4 | "name": "test", 5 | "port": 80, 6 | "priority": 50, 7 | "protocol": null, 8 | "service": null, 9 | "tag": null, 10 | "target": "192.0.2.0", 11 | "ttl_sec": 604800, 12 | "type": "A", 13 | "updated": "2018-01-01T00:01:01", 14 | "weight": 50 15 | } -------------------------------------------------------------------------------- /test/fixtures/domains_12345_zone-file.json: -------------------------------------------------------------------------------- 1 | { 2 | "zone_file": [ 3 | "; example.com [123]", 4 | "$TTL 864000", 5 | "@ IN SOA ns1.linode.com. user.example.com. 2021000066 14400 14400 1209600 86400", 6 | "@ NS ns1.linode.com.", 7 | "@ NS ns2.linode.com.", 8 | "@ NS ns3.linode.com.", 9 | "@ NS ns4.linode.com.", 10 | "@ NS ns5.linode.com." 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/domains_import.json: -------------------------------------------------------------------------------- 1 | { 2 | "axfr_ips": [], 3 | "description": null, 4 | "domain": "example.org", 5 | "expire_sec": 300, 6 | "group": null, 7 | "id": 1234, 8 | "master_ips": [], 9 | "refresh_sec": 300, 10 | "retry_sec": 300, 11 | "soa_email": "admin@example.org", 12 | "status": "active", 13 | "tags": [ 14 | "example tag", 15 | "another example" 16 | ], 17 | "ttl_sec": 300, 18 | "type": "master" 19 | } -------------------------------------------------------------------------------- /test/fixtures/images_private_123_regions.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2017-08-20T14:01:01", 3 | "description": null, 4 | "deprecated": false, 5 | "status": "available", 6 | "created_by": "testguy", 7 | "id": "private/123", 8 | "label": "Gold Master", 9 | "size": 650, 10 | "is_public": false, 11 | "type": "manual", 12 | "vendor": null, 13 | "eol": "2026-07-01T04:00:00", 14 | "expiry": "2026-08-01T04:00:00", 15 | "updated": "2020-07-01T04:00:00", 16 | "capabilities": ["cloud-init"], 17 | "tags": ["tests"], 18 | "total_size": 1300, 19 | "regions": [ 20 | { 21 | "region": "us-east", 22 | "status": "available" 23 | }, 24 | { 25 | "region": "us-west", 26 | "status": "pending replication" 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /test/fixtures/images_private_1337.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2021-08-14T22:44:02", 3 | "created_by": "someone", 4 | "deprecated": false, 5 | "description": "very real image upload.", 6 | "eol": "2026-07-01T04:00:00", 7 | "expiry": null, 8 | "id": "private/1337", 9 | "is_public": false, 10 | "label": "Realest Image Upload", 11 | "size": 2500, 12 | "status": "available", 13 | "type": "manual", 14 | "updated": "2021-08-14T22:44:02", 15 | "vendor": "Debian", 16 | "capabilities": ["cloud-init"] 17 | } -------------------------------------------------------------------------------- /test/fixtures/images_upload.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": { 3 | "created": "2021-08-14T22:44:02", 4 | "created_by": "someone", 5 | "deprecated": false, 6 | "description": "very real image upload.", 7 | "eol": "2026-07-01T04:00:00", 8 | "expiry": null, 9 | "id": "private/1337", 10 | "is_public": false, 11 | "label": "Realest Image Upload", 12 | "size": 2500, 13 | "status": "available", 14 | "type": "manual", 15 | "updated": "2021-08-14T22:44:02", 16 | "vendor": "Debian", 17 | "capabilities": ["cloud-init"], 18 | "tags": ["test_tag", "test2"] 19 | }, 20 | "upload_to": "https://linode.com/" 21 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 1, 4 | "results": 2, 5 | "data": [ 6 | { 7 | "group": "test", 8 | "hypervisor": "kvm", 9 | "id": 123, 10 | "status": "running", 11 | "type": "g6-standard-1", 12 | "alerts": { 13 | "network_in": 5, 14 | "network_out": 5, 15 | "cpu": 90, 16 | "transfer_quota": 80, 17 | "io": 5000 18 | }, 19 | "label": "linode123", 20 | "backups": { 21 | "enabled": true, 22 | "schedule": { 23 | "window": "W02", 24 | "day": "Scheduling" 25 | } 26 | }, 27 | "specs": { 28 | "memory": 2048, 29 | "disk": 30720, 30 | "vcpus": 1, 31 | "transfer": 2000 32 | }, 33 | "ipv6": "1234:abcd::1234:abcd:89ef:67cd/64", 34 | "created": "2017-01-01T00:00:00", 35 | "region": "us-east-1a", 36 | "ipv4": [ 37 | "123.45.67.89" 38 | ], 39 | "updated": "2017-01-01T00:00:00", 40 | "image": "linode/ubuntu17.04", 41 | "tags": ["something"], 42 | "host_uuid": "3a3ddd59d9a78bb8de041391075df44de62bfec8", 43 | "watchdog_enabled": true, 44 | "disk_encryption": "disabled", 45 | "lke_cluster_id": null, 46 | "placement_group": { 47 | "id": 123, 48 | "label": "test", 49 | "placement_group_type": "anti_affinity:local", 50 | "placement_group_policy": "strict" 51 | } 52 | }, 53 | { 54 | "group": "test", 55 | "hypervisor": "kvm", 56 | "id": 456, 57 | "status": "running", 58 | "type": "g5-standard-1", 59 | "alerts": { 60 | "network_in": 5, 61 | "network_out": 5, 62 | "cpu": 90, 63 | "transfer_quota": 80, 64 | "io": 5000 65 | }, 66 | "label": "linode456", 67 | "backups": { 68 | "enabled": false, 69 | "schedule": { 70 | "window": null, 71 | "day": null 72 | } 73 | }, 74 | "specs": { 75 | "memory": 2048, 76 | "disk": 30720, 77 | "vcpus": 1, 78 | "transfer": 2000 79 | }, 80 | "ipv6": "1234:abcd::1234:abcd:89ef:67cd/64", 81 | "created": "2017-01-01T00:00:00", 82 | "region": "us-east-1a", 83 | "ipv4": [ 84 | "123.45.67.89" 85 | ], 86 | "updated": "2017-01-01T00:00:00", 87 | "image": "linode/debian9", 88 | "tags": [], 89 | "host_uuid": "3a3ddd59d9a78bb8de041391075df44de62bfec8", 90 | "watchdog_enabled": false, 91 | "disk_encryption": "enabled", 92 | "lke_cluster_id": 18881, 93 | "placement_group": null 94 | } 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_backups.json: -------------------------------------------------------------------------------- 1 | { 2 | "automatic": [ 3 | { 4 | "region": "us-east-1a", 5 | "finished": "2018-01-09T00:01:01", 6 | "updated": "2018-01-09T00:01:01", 7 | "disks": [ 8 | { 9 | "size": 1024, 10 | "label": "Debian 8.1 Disk", 11 | "filesystem": "ext4" 12 | }, 13 | { 14 | "size": 0, 15 | "label": "256MB Swap Image", 16 | "filesystem": "swap" 17 | } 18 | ], 19 | "label": null, 20 | "configs": [ 21 | "My Debian 8.1 Profile" 22 | ], 23 | "id": 12345, 24 | "status": "successful", 25 | "created": "2018-01-09T00:01:01", 26 | "type": "auto", 27 | "available": true 28 | }, 29 | { 30 | "region": "us-east-1a", 31 | "finished": "2018-01-01T00:01:01", 32 | "updated": "2018-01-01T00:01:01", 33 | "disks": [ 34 | { 35 | "size": 1024, 36 | "label": "Debian 8.1 Disk", 37 | "filesystem": "ext4" 38 | }, 39 | { 40 | "size": 0, 41 | "label": "256MB Swap Image", 42 | "filesystem": "swap" 43 | } 44 | ], 45 | "label": null, 46 | "configs": [ 47 | "My Debian 8.1 Profile" 48 | ], 49 | "id": 12456, 50 | "status": "successful", 51 | "created": "2018-01-01T00:01:01", 52 | "type": "auto", 53 | "available": true 54 | }, 55 | { 56 | "region": "us-east-1a", 57 | "finished": "2018-01-07T00:01:01", 58 | "updated": "2018-01-07T00:01:01", 59 | "disks": [ 60 | { 61 | "size": 1024, 62 | "label": "Debian 8.1 Disk", 63 | "filesystem": "ext4" 64 | }, 65 | { 66 | "size": 0, 67 | "label": "256MB Swap Image", 68 | "filesystem": "swap" 69 | } 70 | ], 71 | "label": null, 72 | "configs": [ 73 | "My Debian 8.1 Profile" 74 | ], 75 | "id": 12567, 76 | "status": "successful", 77 | "created": "2018-01-07T00:01:01", 78 | "type": "auto", 79 | "available": false 80 | } 81 | ], 82 | "snapshot": { 83 | "in_progress": null, 84 | "current": null 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "root_device": "/dev/sda", 5 | "comments": "", 6 | "helpers": { 7 | "updatedb_disabled": true, 8 | "modules_dep": true, 9 | "devtmpfs_automount": true, 10 | "distro": true, 11 | "network": false 12 | }, 13 | "label": "My Ubuntu 17.04 LTS Profile", 14 | "created": "2014-10-07T20:04:00", 15 | "memory_limit": 0, 16 | "id": 456789, 17 | "interfaces": [ 18 | { 19 | "id": 456, 20 | "purpose": "public", 21 | "primary": true 22 | }, 23 | { 24 | "id": 123, 25 | "purpose": "vpc", 26 | "primary": true, 27 | "active": true, 28 | "vpc_id": 123456, 29 | "subnet_id": 789, 30 | "ipv4": { 31 | "vpc": "10.0.0.2", 32 | "nat_1_1": "any" 33 | }, 34 | "ip_ranges": [ 35 | "10.0.0.0/24" 36 | ] 37 | }, 38 | { 39 | "id": 321, 40 | "primary": false, 41 | "ipam_address":"10.0.0.2", 42 | "label":"test-interface", 43 | "purpose":"vlan" 44 | } 45 | ], 46 | "run_level": "default", 47 | "initrd": null, 48 | "virt_mode": "paravirt", 49 | "kernel": "linode/latest-64bit", 50 | "updated": "2014-10-07T20:04:00", 51 | "devices": { 52 | "sda": { 53 | "disk_id": 12345, 54 | "volume_id": null 55 | }, 56 | "sdc": null, 57 | "sde": null, 58 | "sdh": null, 59 | "sdg": null, 60 | "sdb": { 61 | "disk_id": 12346, 62 | "volume_id": null 63 | }, 64 | "sdf": null, 65 | "sdd": null 66 | } 67 | } 68 | ], 69 | "pages": 1, 70 | "page": 1, 71 | "results": 1 72 | } 73 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs_456789.json: -------------------------------------------------------------------------------- 1 | { 2 | "root_device":"/dev/sda", 3 | "comments":"", 4 | "helpers":{ 5 | "updatedb_disabled":true, 6 | "modules_dep":true, 7 | "devtmpfs_automount":true, 8 | "distro":true, 9 | "network":false 10 | }, 11 | "label":"My Ubuntu 17.04 LTS Profile", 12 | "created":"2014-10-07T20:04:00", 13 | "memory_limit":0, 14 | "id":456789, 15 | "interfaces": [ 16 | { 17 | "id": 456, 18 | "purpose": "public", 19 | "primary": true 20 | }, 21 | { 22 | "id": 123, 23 | "purpose": "vpc", 24 | "primary": true, 25 | "active": true, 26 | "vpc_id": 123456, 27 | "subnet_id": 789, 28 | "ipv4": { 29 | "vpc": "10.0.0.2", 30 | "nat_1_1": "any" 31 | }, 32 | "ip_ranges": [ 33 | "10.0.0.0/24" 34 | ] 35 | }, 36 | { 37 | "id": 321, 38 | "primary": false, 39 | "ipam_address":"10.0.0.2", 40 | "label":"test-interface", 41 | "purpose":"vlan" 42 | } 43 | ], 44 | "run_level":"default", 45 | "initrd":null, 46 | "virt_mode":"paravirt", 47 | "kernel":"linode/latest-64bit", 48 | "updated":"2014-10-07T20:04:00", 49 | "devices":{ 50 | "sda":{ 51 | "disk_id":12345, 52 | "volume_id":null 53 | }, 54 | "sdc":null, 55 | "sde":null, 56 | "sdh":null, 57 | "sdg":null, 58 | "sdb":{ 59 | "disk_id":12346, 60 | "volume_id":null 61 | }, 62 | "sdf":null, 63 | "sdd":null 64 | } 65 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs_456789_interfaces.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 456, 5 | "purpose": "public", 6 | "primary": true 7 | }, 8 | { 9 | "id": 123, 10 | "purpose": "vpc", 11 | "primary": true, 12 | "active": true, 13 | "vpc_id": 123456, 14 | "subnet_id": 789, 15 | "ipv4": { 16 | "vpc": "10.0.0.2", 17 | "nat_1_1": "any" 18 | }, 19 | "ip_ranges": [ 20 | "10.0.0.0/24" 21 | ] 22 | }, 23 | { 24 | "id": 321, 25 | "primary": false, 26 | "ipam_address":"10.0.0.2", 27 | "label":"test-interface", 28 | "purpose":"vlan" 29 | } 30 | ], 31 | "page": 1, 32 | "pages": 1, 33 | "results": 1 34 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs_456789_interfaces_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 123, 3 | "purpose": "vpc", 4 | "primary": true, 5 | "active": true, 6 | "vpc_id": 123456, 7 | "subnet_id": 789, 8 | "ipv4": { 9 | "vpc": "10.0.0.2", 10 | "nat_1_1": "any" 11 | }, 12 | "ip_ranges": [ 13 | "10.0.0.0/24" 14 | ] 15 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs_456789_interfaces_123_put.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 123, 3 | "purpose": "vpc", 4 | "primary": false, 5 | "vpc_id": 123456, 6 | "subnet_id": 789, 7 | "ipv4": { 8 | "vpc": "10.0.0.3", 9 | "nat_1_1": "any" 10 | }, 11 | "ip_ranges": [ 12 | "10.0.0.0/24" 13 | ] 14 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs_456789_interfaces_321.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 321, 3 | "primary": false, 4 | "ipam_address":"10.0.0.2", 5 | "label":"test-interface", 6 | "purpose":"vlan" 7 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_configs_456789_interfaces_456.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 456, 3 | "purpose": "public", 4 | "primary": true 5 | } -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_disks.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "results": 2, 4 | "pages": 1, 5 | "data": [ 6 | { 7 | "size": 25088, 8 | "status": "ready", 9 | "filesystem": "ext4", 10 | "id": 12345, 11 | "updated": "2017-01-01T00:00:00", 12 | "label": "Ubuntu 17.04 Disk", 13 | "created": "2017-01-01T00:00:00", 14 | "disk_encryption": "disabled" 15 | }, 16 | { 17 | "size": 512, 18 | "status": "ready", 19 | "filesystem": "swap", 20 | "id": 12346, 21 | "updated": "2017-01-01T00:00:00", 22 | "label": "512 MB Swap Image", 23 | "created": "2017-01-01T00:00:00", 24 | "disk_encryption": "disabled" 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_disks_12345_clone.json: -------------------------------------------------------------------------------- 1 | { 2 | "size": 25088, 3 | "status": "ready", 4 | "filesystem": "ext4", 5 | "id": 12345, 6 | "updated": "2017-01-01T00:00:00", 7 | "label": "Ubuntu 17.04 Disk", 8 | "created": "2017-01-01T00:00:00", 9 | "disk_encryption": "disabled" 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_firewalls.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-01T00:01:01", 5 | "id": 123, 6 | "label": "firewall123", 7 | "rules": { 8 | "inbound": [ 9 | { 10 | "action": "ACCEPT", 11 | "addresses": { 12 | "ipv4": [ 13 | "192.0.2.0/24" 14 | ], 15 | "ipv6": [ 16 | "2001:DB8::/32" 17 | ] 18 | }, 19 | "description": "An example firewall rule description.", 20 | "label": "firewallrule123", 21 | "ports": "22-24, 80, 443", 22 | "protocol": "TCP" 23 | } 24 | ], 25 | "inbound_policy": "DROP", 26 | "outbound": [ 27 | { 28 | "action": "ACCEPT", 29 | "addresses": { 30 | "ipv4": [ 31 | "192.0.2.0/24" 32 | ], 33 | "ipv6": [ 34 | "2001:DB8::/32" 35 | ] 36 | }, 37 | "description": "An example firewall rule description.", 38 | "label": "firewallrule123", 39 | "ports": "22-24, 80, 443", 40 | "protocol": "TCP" 41 | } 42 | ], 43 | "outbound_policy": "DROP" 44 | }, 45 | "status": "enabled", 46 | "tags": [ 47 | "example tag", 48 | "another example" 49 | ], 50 | "updated": "2018-01-02T00:01:01" 51 | } 52 | ], 53 | "page": 1, 54 | "pages": 1, 55 | "results": 1 56 | } 57 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_ips.json: -------------------------------------------------------------------------------- 1 | { 2 | "ipv4": { 3 | "private": [ 4 | { 5 | "address": "192.168.133.234", 6 | "gateway": null, 7 | "linode_id": 123, 8 | "prefix": 17, 9 | "public": false, 10 | "rdns": null, 11 | "region": "us-east", 12 | "subnet_mask": "255.255.128.0", 13 | "type": "ipv4" 14 | } 15 | ], 16 | "public": [ 17 | { 18 | "address": "97.107.143.141", 19 | "gateway": "97.107.143.1", 20 | "linode_id": 123, 21 | "prefix": 24, 22 | "public": true, 23 | "rdns": "test.example.org", 24 | "region": "us-east", 25 | "subnet_mask": "255.255.255.0", 26 | "type": "ipv4" 27 | } 28 | ], 29 | "reserved": [ 30 | { 31 | "address": "97.107.143.141", 32 | "gateway": "97.107.143.1", 33 | "linode_id": 123, 34 | "prefix": 24, 35 | "public": true, 36 | "rdns": "test.example.org", 37 | "region": "us-east", 38 | "subnet_mask": "255.255.255.0", 39 | "type": "ipv4" 40 | } 41 | ], 42 | "vpc": [ 43 | { 44 | "address": "10.0.0.2", 45 | "address_range": null, 46 | "vpc_id": 39246, 47 | "subnet_id": 39388, 48 | "region": "us-mia", 49 | "linode_id": 55904908, 50 | "config_id": 59036295, 51 | "interface_id": 1186165, 52 | "active": true, 53 | "nat_1_1": "172.233.179.133", 54 | "gateway": "10.0.0.1", 55 | "prefix": 24, 56 | "subnet_mask": "255.255.255.0" 57 | } 58 | ], 59 | "shared": [ 60 | { 61 | "address": "97.107.143.141", 62 | "gateway": "97.107.143.1", 63 | "linode_id": 123, 64 | "prefix": 24, 65 | "public": true, 66 | "rdns": "test.example.org", 67 | "region": "us-east", 68 | "subnet_mask": "255.255.255.0", 69 | "type": "ipv4" 70 | } 71 | ] 72 | }, 73 | "ipv6": { 74 | "global": [ 75 | { 76 | "prefix": 124, 77 | "range": "2600:3c01::2:5000:0", 78 | "region": "us-east", 79 | "route_target": "2600:3c01::2:5000:f" 80 | } 81 | ], 82 | "link_local": { 83 | "address": "fe80::f03c:91ff:fe24:3a2f", 84 | "gateway": "fe80::1", 85 | "linode_id": 123, 86 | "prefix": 64, 87 | "public": false, 88 | "rdns": null, 89 | "region": "us-east", 90 | "subnet_mask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 91 | "type": "ipv6" 92 | }, 93 | "slaac": { 94 | "address": "2600:3c03::f03c:91ff:fe24:3a2f", 95 | "gateway": "fe80::1", 96 | "linode_id": 123, 97 | "prefix": 64, 98 | "public": true, 99 | "rdns": null, 100 | "region": "us-east", 101 | "subnet_mask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 102 | "type": "ipv6" 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_nodebalancers.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "client_conn_throttle": 0, 5 | "created": "2018-01-01T00:01:01", 6 | "hostname": "192.0.2.1.ip.linodeusercontent.com", 7 | "id": 12345, 8 | "ipv4": "203.0.113.1", 9 | "ipv6": null, 10 | "label": "balancer12345", 11 | "region": "us-east", 12 | "tags": [ 13 | "example tag", 14 | "another example" 15 | ], 16 | "transfer": { 17 | "in": 28.91200828552246, 18 | "out": 3.5487728118896484, 19 | "total": 32.46078109741211 20 | }, 21 | "updated": "2018-03-01T00:01:01" 22 | } 23 | ], 24 | "page": 1, 25 | "pages": 1, 26 | "results": 1 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "quota": 471, 3 | "used": 10369075, 4 | "billable": 0 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_transfer_2023_4.json: -------------------------------------------------------------------------------- 1 | { 2 | "bytes_in": 30471077120, 3 | "bytes_out": 22956600198, 4 | "bytes_total": 53427677318 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/linode_instances_123_volumes.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-01T00:01:01", 5 | "filesystem_path": "/dev/disk/by-id/scsi-0Linode_Volume_my-volume", 6 | "hardware_type": "nvme", 7 | "id": 12345, 8 | "label": "my-volume", 9 | "linode_id": 12346, 10 | "linode_label": "linode123", 11 | "region": "us-east", 12 | "size": 30, 13 | "status": "active", 14 | "tags": [ 15 | "example tag", 16 | "another example" 17 | ], 18 | "updated": "2018-01-01T00:01:01" 19 | } 20 | ], 21 | "page": 1, 22 | "pages": 1, 23 | "results": 1 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/linode_stackscripts_10079.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2018-01-01T00:01:01", 3 | "deployments_active": 1, 4 | "deployments_total": 12, 5 | "description": "This StackScript installs and configures MySQL\n", 6 | "id": 10079, 7 | "images": [ 8 | "linode/debian9", 9 | "linode/debian8" 10 | ], 11 | "is_public": true, 12 | "label": "a-stackscript", 13 | "mine": true, 14 | "rev_note": "Set up MySQL", 15 | "script": "\"#!/bin/bash\"\n", 16 | "updated": "2018-01-01T00:01:01", 17 | "user_defined_fields": [ 18 | { 19 | "default": null, 20 | "example": "hunter2", 21 | "label": "Enter the password", 22 | "manyOf": "avalue,anothervalue,thirdvalue", 23 | "name": "DB_PASSWORD", 24 | "oneOf": "avalue,anothervalue,thirdvalue" 25 | } 26 | ], 27 | "user_gravatar_id": "a445b305abda30ebc766bc7fda037c37", 28 | "username": "myuser" 29 | } -------------------------------------------------------------------------------- /test/fixtures/linode_types_g6-nanode-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "disk": 20480, 3 | "memory": 1024, 4 | "transfer": 1000, 5 | "addons": { 6 | "backups": { 7 | "price": { 8 | "hourly": 0.003, 9 | "monthly": 2 10 | }, 11 | "region_prices": [ 12 | { 13 | "id": "ap-west", 14 | "hourly": 0.02, 15 | "monthly": 20 16 | }, 17 | { 18 | "id": "ap-northeast", 19 | "hourly": 0.02, 20 | "monthly": 20 21 | } 22 | ] 23 | } 24 | }, 25 | "class": "nanode", 26 | "network_out": 1000, 27 | "vcpus": 1, 28 | "gpus": 0, 29 | "id": "g5-nanode-1", 30 | "label": "Linode 1024", 31 | "price": { 32 | "hourly": 0.0075, 33 | "monthly": 5 34 | }, 35 | "region_prices": [ 36 | { 37 | "id": "us-east", 38 | "hourly": 0.02, 39 | "monthly": 20 40 | }, 41 | { 42 | "id": "ap-northeast", 43 | "hourly": 0.02, 44 | "monthly": 20 45 | } 46 | ], 47 | "successor": null 48 | } 49 | -------------------------------------------------------------------------------- /test/fixtures/lke_clusters.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 18881, 3 | "status": "ready", 4 | "created": "2021-02-10T23:54:21", 5 | "updated": "2021-02-10T23:54:21", 6 | "label": "example-cluster", 7 | "region": "ap-west", 8 | "k8s_version": "1.19", 9 | "tags": [], 10 | "apl_enabled": true 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18881.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 18881, 3 | "status": "ready", 4 | "created": "2021-02-10T23:54:21", 5 | "updated": "2021-02-10T23:54:21", 6 | "label": "example-cluster", 7 | "region": "ap-west", 8 | "k8s_version": "1.19", 9 | "tier": "standard", 10 | "tags": [], 11 | "control_plane": { 12 | "high_availability": true 13 | }, 14 | "apl_enabled": true 15 | } -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18881_control__plane__acl.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl": { 3 | "enabled": true, 4 | "addresses": { 5 | "ipv4": [ 6 | "10.0.0.1/32" 7 | ], 8 | "ipv6": [ 9 | "1234::5678" 10 | ] 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18881_dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://example.dashboard.linodelke.net" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18881_nodes_123456.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "123456", 3 | "instance_id": 456, 4 | "status": "ready" 5 | } -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18881_pools_456.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoscaler": { 3 | "enabled": true, 4 | "max": 12, 5 | "min": 3 6 | }, 7 | "count": 6, 8 | "disks": [ 9 | { 10 | "size": 1024, 11 | "type": "ext-4" 12 | } 13 | ], 14 | "id": 456, 15 | "nodes": [ 16 | { 17 | "id": "123456", 18 | "instance_id": 123458, 19 | "status": "ready" 20 | } 21 | ], 22 | "tags": [ 23 | "example tag", 24 | "another example" 25 | ], 26 | "taints": [ 27 | { 28 | "key": "foo", 29 | "value": "bar", 30 | "effect": "NoSchedule" 31 | } 32 | ], 33 | "labels": { 34 | "foo": "bar", 35 | "bar": "foo" 36 | }, 37 | "type": "g6-standard-4", 38 | "disk_encryption": "enabled" 39 | } -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18882.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 18881, 3 | "status": "ready", 4 | "created": "2021-02-10T23:54:21", 5 | "updated": "2021-02-10T23:54:21", 6 | "label": "example-cluster-2", 7 | "region": "ap-west", 8 | "k8s_version": "1.31.1+lke1", 9 | "tier": "enterprise", 10 | "tags": [], 11 | "control_plane": { 12 | "high_availability": true 13 | } 14 | } -------------------------------------------------------------------------------- /test/fixtures/lke_clusters_18882_pools_789.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 789, 3 | "type": "g6-standard-2", 4 | "count": 3, 5 | "nodes": [], 6 | "disks": [], 7 | "autoscaler": { 8 | "enabled": false, 9 | "min": 3, 10 | "max": 3 11 | }, 12 | "labels": {}, 13 | "taints": [], 14 | "tags": [], 15 | "disk_encryption": "enabled", 16 | "k8s_version": "1.31.1+lke1", 17 | "update_strategy": "rolling_update" 18 | } -------------------------------------------------------------------------------- /test/fixtures/lke_tiers_standard_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "1.32", 5 | "tier": "standard" 6 | }, 7 | { 8 | "id": "1.31", 9 | "tier": "standard" 10 | }, 11 | { 12 | "id": "1.30", 13 | "tier": "standard" 14 | } 15 | ], 16 | "page": 1, 17 | "pages": 1, 18 | "results": 3 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/lke_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "lke-sa", 5 | "label": "LKE Standard Availability", 6 | "price": { 7 | "hourly": 0, 8 | "monthly": 0 9 | }, 10 | "region_prices": [], 11 | "transfer": 0 12 | }, 13 | { 14 | "id": "lke-ha", 15 | "label": "LKE High Availability", 16 | "price": { 17 | "hourly": 0.09, 18 | "monthly": 60 19 | }, 20 | "region_prices": [ 21 | { 22 | "id": "id-cgk", 23 | "hourly": 0.108, 24 | "monthly": 72 25 | }, 26 | { 27 | "id": "br-gru", 28 | "hourly": 0.126, 29 | "monthly": 84 30 | } 31 | ], 32 | "transfer": 0 33 | } 34 | ], 35 | "page": 1, 36 | "pages": 1, 37 | "results": 2 38 | } -------------------------------------------------------------------------------- /test/fixtures/lke_versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | {"id": "1.19"}, 4 | {"id": "1.18"}, 5 | {"id": "1.17"} 6 | ], 7 | "page": 1, 8 | "pages": 1, 9 | "results": 3 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/longview_clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2014-12-01T00:01:01", 5 | "updated": "2016-06-02T00:01:01", 6 | "id": 1234, 7 | "label": "test_client_1", 8 | "install_code": "12345678-ABCD-EF01-23456789ABCDEF12", 9 | "apps": { 10 | "nginx": false, 11 | "mysql": false, 12 | "apache": false 13 | }, 14 | "api_key": "12345678-ABCD-EF01-23456789ABCDEF12" 15 | }, 16 | { 17 | "created": "2017-01-01T06:00:00", 18 | "updated": "2017-01-01T06:00:00", 19 | "id": 5678, 20 | "label": "longview5678", 21 | "install_code": "12345678-ABCD-EF01-23456789ABCDEF12", 22 | "apps": { 23 | "nginx": true, 24 | "mysql": true, 25 | "apache": true 26 | }, 27 | "api_key": "12345678-ABCD-EF01-23456789ABCDEF12" 28 | } 29 | ], 30 | "results": 2, 31 | "pages": 1, 32 | "page": 1 33 | } 34 | -------------------------------------------------------------------------------- /test/fixtures/longview_plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "clients_included": 10, 3 | "id": "longview-10", 4 | "label": "Longview Pro 10 pack", 5 | "price": { 6 | "hourly": 0.06, 7 | "monthly": 40 8 | } 9 | } -------------------------------------------------------------------------------- /test/fixtures/longview_subscriptions.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "results": 4, 4 | "data": [ 5 | { 6 | "id": "longview-10", 7 | "label": "Longview Pro 10 pack", 8 | "clients_included": 10, 9 | "price": { 10 | "hourly": 0.06, 11 | "monthly": 40 12 | } 13 | }, 14 | { 15 | "id": "longview-100", 16 | "label": "Longview Pro 100 pack", 17 | "clients_included": 100, 18 | "price": { 19 | "hourly": 0.3, 20 | "monthly": 200 21 | } 22 | }, 23 | { 24 | "id": "longview-3", 25 | "label": "Longview Pro 3 pack", 26 | "clients_included": 3, 27 | "price": { 28 | "hourly": 0.03, 29 | "monthly": 20 30 | } 31 | }, 32 | { 33 | "id": "longview-40", 34 | "label": "Longview Pro 40 pack", 35 | "clients_included": 40, 36 | "price": { 37 | "hourly": 0.15, 38 | "monthly": 100 39 | } 40 | } 41 | ], 42 | "pages": 1 43 | } 44 | -------------------------------------------------------------------------------- /test/fixtures/mongodb.json: -------------------------------------------------------------------------------- 1 | { 2 | "ca_certificate": "LS0tLS1CRUdJ...==" 3 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_dashboards.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2024-10-10T05:01:58", 5 | "id": 1, 6 | "label": "Resource Usage", 7 | "service_type": "dbaas", 8 | "type": "standard", 9 | "updated": "2024-10-10T05:01:58", 10 | "widgets": [ 11 | { 12 | "aggregate_function": "sum", 13 | "chart_type": "area", 14 | "color": "default", 15 | "label": "CPU Usage", 16 | "metric": "cpu_usage", 17 | "size": 12, 18 | "unit": "%", 19 | "y_label": "cpu_usage" 20 | }, 21 | { 22 | "aggregate_function": "sum", 23 | "chart_type": "area", 24 | "color": "default", 25 | "label": "Disk I/O Write", 26 | "metric": "write_iops", 27 | "size": 6, 28 | "unit": "IOPS", 29 | "y_label": "write_iops" 30 | } 31 | ] 32 | } 33 | ], 34 | "page": 1, 35 | "pages": 1, 36 | "results": 1 37 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_dashboards_1.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2024-10-10T05:01:58", 3 | "id": 1, 4 | "label": "Resource Usage", 5 | "service_type": "dbaas", 6 | "type": "standard", 7 | "updated": "2024-10-10T05:01:58", 8 | "widgets": [ 9 | { 10 | "aggregate_function": "sum", 11 | "chart_type": "area", 12 | "color": "default", 13 | "label": "CPU Usage", 14 | "metric": "cpu_usage", 15 | "size": 12, 16 | "unit": "%", 17 | "y_label": "cpu_usage" 18 | }, 19 | { 20 | "aggregate_function": "sum", 21 | "chart_type": "area", 22 | "color": "default", 23 | "label": "Available Memory", 24 | "metric": "available_memory", 25 | "size": 6, 26 | "unit": "GB", 27 | "y_label": "available_memory" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_services.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "label": "Databases", 5 | "service_type": "dbaas" 6 | } 7 | ], 8 | "page": 1, 9 | "pages": 1, 10 | "results": 1 11 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_services_dbaas.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "label": "Databases", 5 | "service_type": "dbaas" 6 | } 7 | ], 8 | "page": 1, 9 | "pages": 1, 10 | "results": 1 11 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_services_dbaas_dashboards.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2024-10-10T05:01:58", 5 | "id": 1, 6 | "label": "Resource Usage", 7 | "service_type": "dbaas", 8 | "type": "standard", 9 | "updated": "2024-10-10T05:01:58", 10 | "widgets": [ 11 | { 12 | "aggregate_function": "sum", 13 | "chart_type": "area", 14 | "color": "default", 15 | "label": "CPU Usage", 16 | "metric": "cpu_usage", 17 | "size": 12, 18 | "unit": "%", 19 | "y_label": "cpu_usage" 20 | }, 21 | { 22 | "aggregate_function": "sum", 23 | "chart_type": "area", 24 | "color": "default", 25 | "label": "Memory Usage", 26 | "metric": "memory_usage", 27 | "size": 6, 28 | "unit": "%", 29 | "y_label": "memory_usage" 30 | } 31 | ] 32 | } 33 | ], 34 | "page": 1, 35 | "pages": 1, 36 | "results": 1 37 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_services_dbaas_metric-definitions.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "available_aggregate_functions": [ 5 | "max", 6 | "avg", 7 | "min", 8 | "sum" 9 | ], 10 | "dimensions": [ 11 | { 12 | "dimension_label": "node_type", 13 | "label": "Node Type", 14 | "values": [ 15 | "primary", 16 | "secondary" 17 | ] 18 | } 19 | ], 20 | "is_alertable": true, 21 | "label": "CPU Usage", 22 | "metric": "cpu_usage", 23 | "metric_type": "gauge", 24 | "scrape_interval": "60s", 25 | "unit": "percent" 26 | }, 27 | { 28 | "available_aggregate_functions": [ 29 | "max", 30 | "avg", 31 | "min", 32 | "sum" 33 | ], 34 | "dimensions": [ 35 | { 36 | "dimension_label": "node_type", 37 | "label": "Node Type", 38 | "values": [ 39 | "primary", 40 | "secondary" 41 | ] 42 | } 43 | ], 44 | "is_alertable": true, 45 | "label": "Disk I/O Read", 46 | "metric": "read_iops", 47 | "metric_type": "gauge", 48 | "scrape_interval": "60s", 49 | "unit": "iops" 50 | } 51 | ], 52 | "page": 1, 53 | "pages": 1, 54 | "results": 2 55 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_services_dbaas_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "abcdefhjigkfghh" 3 | } -------------------------------------------------------------------------------- /test/fixtures/monitor_services_linode_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "abcdefhjigkfghh" 3 | } -------------------------------------------------------------------------------- /test/fixtures/network-transfer_prices.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "distributed_network_transfer", 5 | "label": "Distributed Network Transfer", 6 | "price": { 7 | "hourly": 0.01, 8 | "monthly": null 9 | }, 10 | "region_prices": [], 11 | "transfer": 0 12 | }, 13 | { 14 | "id": "network_transfer", 15 | "label": "Network Transfer", 16 | "price": { 17 | "hourly": 0.005, 18 | "monthly": null 19 | }, 20 | "region_prices": [ 21 | { 22 | "id": "id-cgk", 23 | "hourly": 0.015, 24 | "monthly": null 25 | }, 26 | { 27 | "id": "br-gru", 28 | "hourly": 0.007, 29 | "monthly": null 30 | } 31 | ], 32 | "transfer": 0 33 | } 34 | ], 35 | "page": 1, 36 | "pages": 1, 37 | "results": 2 38 | } -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":[ 3 | { 4 | "id":123, 5 | "label":"test-firewall-1", 6 | "created":"2018-01-01T00:01:01", 7 | "updated":"2018-01-01T00:01:01", 8 | "status":"enabled", 9 | "rules":{ 10 | "outbound":[], 11 | "outbound_policy":"DROP", 12 | "inbound":[], 13 | "inbound_policy":"DROP" 14 | }, 15 | "tags":[] 16 | } 17 | ], 18 | "page":1, 19 | "pages":1, 20 | "results":1 21 | } -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "id":123, 3 | "label":"test-firewall-1", 4 | "created":"2018-01-01T00:01:01", 5 | "updated":"2018-01-01T00:01:01", 6 | "status":"enabled", 7 | "rules":{ 8 | "outbound":[], 9 | "outbound_policy":"DROP", 10 | "inbound":[], 11 | "inbound_policy":"DROP" 12 | }, 13 | "tags":[] 14 | } -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls_123_devices.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-01T00:01:01", 5 | "entity": { 6 | "id": 123, 7 | "label": "my-linode", 8 | "type": "linode", 9 | "url": "/v4/linode/instances/123" 10 | }, 11 | "id": 123, 12 | "updated": "2018-01-02T00:01:01" 13 | } 14 | ], 15 | "page": 1, 16 | "pages": 1, 17 | "results": 1 18 | } -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls_123_devices_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "created":"2018-01-01T00:01:01", 3 | "entity":{ 4 | "id":123, 5 | "label":"my-linode", 6 | "type":"linode", 7 | "url":"/v4/linode/instances/123" 8 | }, 9 | "id":123, 10 | "updated":"2018-01-02T00:01:01" 11 | } -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls_123_history.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "updated": "2025-03-07T17:06:36", 5 | "status": "enabled", 6 | "rules": { 7 | "version": 1 8 | } 9 | }, 10 | { 11 | "updated": "2025-03-07T17:06:36", 12 | "status": "enabled", 13 | "rules": { 14 | "version": 2 15 | } 16 | } 17 | ], 18 | "page": 1, 19 | "pages": 1, 20 | "results": 2 21 | } 22 | -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls_123_history_rules_2.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbound": [ 3 | { 4 | "action": "ACCEPT", 5 | "addresses": { 6 | "ipv4": [ 7 | "0.0.0.0/0" 8 | ], 9 | "ipv6": [ 10 | "ff00::/8" 11 | ] 12 | }, 13 | "description": "A really cool firewall rule.", 14 | "label": "really-cool-firewall-rule", 15 | "ports": "80", 16 | "protocol": "TCP" 17 | } 18 | ], 19 | "inbound_policy": "ACCEPT", 20 | "outbound": [], 21 | "outbound_policy": "DROP", 22 | "version": 2, 23 | "fingerprint": "96c9568c" 24 | } 25 | -------------------------------------------------------------------------------- /test/fixtures/networking_firewalls_123_rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbound": [], 3 | "inbound_policy": "DROP", 4 | "outbound": [], 5 | "outbound_policy": "DROP" 6 | } -------------------------------------------------------------------------------- /test/fixtures/networking_ips_127.0.0.1.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "127.0.0.1", 3 | "gateway": "127.0.0.1", 4 | "linode_id": 123, 5 | "prefix": 24, 6 | "public": true, 7 | "rdns": "test.example.org", 8 | "region": "us-east", 9 | "subnet_mask": "255.255.255.0", 10 | "type": "ipv4", 11 | "vpc_nat_1_1": { 12 | "vpc_id": 242, 13 | "subnet_id": 194, 14 | "address": "139.144.244.36" 15 | } 16 | } -------------------------------------------------------------------------------- /test/fixtures/networking_ipv6_pools.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "prefix": 124, 5 | "range": "2600:3c01::2:5000:0", 6 | "region": "us-east", 7 | "route_target": "2600:3c01::2:5000:f" 8 | } 9 | ], 10 | "page": 1, 11 | "pages": 1, 12 | "results": 1 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/networking_ipv6_ranges.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "prefix": 64, 5 | "range": "2600:3c01::", 6 | "region": "us-east", 7 | "route_target": "2600:3c01::ffff:ffff:ffff:ffff" 8 | } 9 | ], 10 | "page": 1, 11 | "pages": 1, 12 | "results": 1 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/networking_ipv6_ranges_2600%3A3c01%3A%3A.json: -------------------------------------------------------------------------------- 1 | { 2 | "is_bgp": false, 3 | "linodes": [ 4 | 123 5 | ], 6 | "prefix": 64, 7 | "range": "2600:3c01::", 8 | "region": "us-east" 9 | } -------------------------------------------------------------------------------- /test/fixtures/networking_vlans.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2020-01-01T00:01:01", 5 | "label": "vlan-test", 6 | "linodes": [ 7 | 111, 8 | 222 9 | ], 10 | "region": "us-southeast" 11 | } 12 | ], 13 | "page": 1, 14 | "pages": 1, 15 | "results": 1 16 | } -------------------------------------------------------------------------------- /test/fixtures/nodebalancers.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-01T00:01:01", 5 | "ipv6": "c001:d00d:b01::1:abcd:1234", 6 | "region": "us-east-1a", 7 | "ipv4": "12.34.56.789", 8 | "hostname": "nb-12-34-56-789.newark.nodebalancer.linode.com", 9 | "id": 123456, 10 | "updated": "2018-01-01T00:01:01", 11 | "label": "balancer123456", 12 | "client_conn_throttle": 0, 13 | "tags": ["something"] 14 | }, 15 | { 16 | "created": "2018-01-01T00:01:01", 17 | "ipv6": "c001:d00d:b01::1:abcd:1256", 18 | "region": "us-east-1a", 19 | "ipv4": "12.34.56.890", 20 | "hostname": "nb-12-34-56-890.newark.nodebalancer.linode.com", 21 | "id": 123457, 22 | "updated": "2018-01-01T00:01:01", 23 | "label": "balancer123457", 24 | "client_conn_throttle": 0, 25 | "tags": [] 26 | } 27 | ], 28 | "results": 2, 29 | "page": 1, 30 | "pages": 1 31 | } 32 | -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_123456.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2018-01-01T00:01:01", 3 | "ipv6": "c001:d00d:b01::1:abcd:1234", 4 | "region": "us-east-1a", 5 | "ipv4": "12.34.56.789", 6 | "hostname": "nb-12-34-56-789.newark.nodebalancer.linode.com", 7 | "id": 123456, 8 | "updated": "2018-01-01T00:01:01", 9 | "label": "balancer123456", 10 | "client_conn_throttle": 0, 11 | "tags": [ 12 | "something" 13 | ] 14 | } -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_123456_configs.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "check": "connection", 5 | "check_attempts": 2, 6 | "stickiness": "table", 7 | "check_interval": 5, 8 | "check_body": "", 9 | "id": 65432, 10 | "check_passive": true, 11 | "algorithm": "roundrobin", 12 | "check_timeout": 3, 13 | "check_path": "/", 14 | "ssl_cert": null, 15 | "ssl_commonname": "", 16 | "port": 80, 17 | "nodebalancer_id": 123456, 18 | "cipher_suite": "recommended", 19 | "ssl_key": null, 20 | "nodes_status": { 21 | "up": 0, 22 | "down": 0 23 | }, 24 | "protocol": "http", 25 | "ssl_fingerprint": "", 26 | "proxy_protocol": "none" 27 | }, 28 | { 29 | "check": "connection", 30 | "check_attempts": 2, 31 | "stickiness": "table", 32 | "check_interval": 5, 33 | "check_body": "", 34 | "id": 65431, 35 | "check_passive": true, 36 | "algorithm": "roundrobin", 37 | "check_timeout": 3, 38 | "check_path": "/", 39 | "ssl_cert": null, 40 | "ssl_commonname": "", 41 | "port": 80, 42 | "nodebalancer_id": 123456, 43 | "cipher_suite": "none", 44 | "ssl_key": null, 45 | "nodes_status": { 46 | "up": 0, 47 | "down": 0 48 | }, 49 | "protocol": "udp", 50 | "ssl_fingerprint": "", 51 | "proxy_protocol": "none", 52 | "udp_check_port": 12345 53 | } 54 | ], 55 | "results": 2, 56 | "page": 1, 57 | "pages": 1 58 | } 59 | -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_123456_configs_65432_nodes.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 54321, 5 | "address": "192.168.210.120", 6 | "label": "node54321", 7 | "status": "UP", 8 | "weight": 50, 9 | "mode": "accept", 10 | "config_id": 54321, 11 | "nodebalancer_id": 123456 12 | }, 13 | { 14 | "id": 12345, 15 | "address": "192.168.210.120", 16 | "label": "node12345", 17 | "status": "UP", 18 | "weight": 50, 19 | "mode": "none", 20 | "config_id": 123456, 21 | "nodebalancer_id": 123456 22 | } 23 | ], 24 | "pages": 1, 25 | "page": 1, 26 | "results": 2 27 | } 28 | -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_12345_configs_4567_rebuild.json: -------------------------------------------------------------------------------- 1 | { 2 | "algorithm": "roundrobin", 3 | "check": "http_body", 4 | "check_attempts": 3, 5 | "check_body": "it works", 6 | "check_interval": 90, 7 | "check_passive": true, 8 | "check_path": "/test", 9 | "check_timeout": 10, 10 | "cipher_suite": "recommended", 11 | "id": 4567, 12 | "nodebalancer_id": 12345, 13 | "nodes_status": { 14 | "down": 0, 15 | "up": 4 16 | }, 17 | "port": 80, 18 | "protocol": "http", 19 | "proxy_protocol": "none", 20 | "ssl_cert": "", 21 | "ssl_commonname": "www.example.com", 22 | "ssl_fingerprint": "00:01:02:03:04:05:06:07:08:09:0A:0B:0C:0D:0E:0F:10:11:12:13", 23 | "ssl_key": "", 24 | "stickiness": "http_cookie" 25 | } -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_12345_firewalls.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-01T00:01:01", 5 | "id": 123, 6 | "label": "firewall123", 7 | "rules": { 8 | "inbound": [ 9 | { 10 | "action": "ACCEPT", 11 | "addresses": { 12 | "ipv4": [ 13 | "192.0.2.0/24" 14 | ], 15 | "ipv6": [ 16 | "2001:DB8::/32" 17 | ] 18 | }, 19 | "description": "An example firewall rule description.", 20 | "label": "firewallrule123", 21 | "ports": "22-24, 80, 443", 22 | "protocol": "TCP" 23 | } 24 | ], 25 | "inbound_policy": "DROP", 26 | "outbound": [ 27 | { 28 | "action": "ACCEPT", 29 | "addresses": { 30 | "ipv4": [ 31 | "192.0.2.0/24" 32 | ], 33 | "ipv6": [ 34 | "2001:DB8::/32" 35 | ] 36 | }, 37 | "description": "An example firewall rule description.", 38 | "label": "firewallrule123", 39 | "ports": "22-24, 80, 443", 40 | "protocol": "TCP" 41 | } 42 | ], 43 | "outbound_policy": "DROP" 44 | }, 45 | "status": "enabled", 46 | "tags": [ 47 | "example tag", 48 | "another example" 49 | ], 50 | "updated": "2018-01-02T00:01:01" 51 | } 52 | ], 53 | "page": 1, 54 | "pages": 1, 55 | "results": 1 56 | } 57 | -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_12345_stats.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "connections": [ 4 | null 5 | ], 6 | "traffic": { 7 | "in": [ 8 | null 9 | ], 10 | "out": [ 11 | null 12 | ] 13 | } 14 | }, 15 | "title": "linode.com - balancer12345 (12345) - day (5 min avg)" 16 | } -------------------------------------------------------------------------------- /test/fixtures/nodebalancers_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "nodebalancer", 5 | "label": "NodeBalancer", 6 | "price": { 7 | "hourly": 0.015, 8 | "monthly": 10 9 | }, 10 | "region_prices": [ 11 | { 12 | "id": "id-cgk", 13 | "hourly": 0.018, 14 | "monthly": 12 15 | }, 16 | { 17 | "id": "br-gru", 18 | "hourly": 0.021, 19 | "monthly": 14 20 | } 21 | ], 22 | "transfer": 0 23 | } 24 | ], 25 | "page": 1, 26 | "pages": 1, 27 | "results": 1 28 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "cluster": "us-east-1", 5 | "created": "2019-01-01T01:23:45", 6 | "hostname": "example-bucket.us-east-1.linodeobjects.com", 7 | "label": "example-bucket", 8 | "objects": 4, 9 | "size": 188318981 10 | } 11 | ], 12 | "page": 1, 13 | "pages": 1, 14 | "results": 1 15 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "cluster": "us-east-1", 5 | "created": "2019-01-01T01:23:45", 6 | "hostname": "example-bucket.us-east-1.linodeobjects.com", 7 | "label": "example-bucket", 8 | "objects": 4, 9 | "size": 188318981, 10 | "endpoint_type": "E1", 11 | "s3_endpoint": "us-east-12.linodeobjects.com" 12 | } 13 | ], 14 | "page": 1, 15 | "pages": 1, 16 | "results": 1 17 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east-1_example-bucket.json: -------------------------------------------------------------------------------- 1 | { 2 | "cluster": "us-east-1", 3 | "region": "us-east", 4 | "created": "2019-01-01T01:23:45", 5 | "hostname": "example-bucket.us-east-1.linodeobjects.com", 6 | "label": "example-bucket", 7 | "objects": 4, 8 | "size": 188318981, 9 | "endpoint_type": "E1", 10 | "s3_endpoint": "us-east-12.linodeobjects.com" 11 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east-1_example-bucket_object-acl.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl": "public-read", 3 | "acl_xml": "..." 4 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east-1_example-bucket_object-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "etag": "9f254c71e28e033bf9e0e5262e3e72ab", 5 | "is_truncated": true, 6 | "last_modified": "2019-01-01T01:23:45", 7 | "name": "example", 8 | "next_marker": "bd021c21-e734-4823-97a4-58b41c2cd4c8.892602.184", 9 | "owner": "bfc70ab2-e3d4-42a4-ad55-83921822270c", 10 | "size": 123 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east-1_example-bucket_object-url.json: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://us-east-1.linodeobjects.com/example-bucket/example?Signature=qr98TEucCntPgEG%2BsZQGDsJg93c%3D&Expires=1567609905&AWSAccessKeyId=G4YAF81XWY61DQM94SE0" 3 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east-1_example-bucket_ssl.json: -------------------------------------------------------------------------------- 1 | { 2 | "ssl": true 3 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_buckets_us-east_example-bucket_access.json: -------------------------------------------------------------------------------- 1 | { 2 | "acl": "authenticated-read", 3 | "acl_xml": "..." 6 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_clusters.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": 1, 3 | "page": 1, 4 | "data": [ 5 | { 6 | "id": "us-east-1", 7 | "status": "available", 8 | "static_site_domain": "website-us-east-1.linodeobjects.com", 9 | "region": "us-east", 10 | "domain": "us-east-1.linodeobjects.com" 11 | } 12 | ], 13 | "results": 1 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/object-storage_keys.json: -------------------------------------------------------------------------------- 1 | { 2 | "results": 2, 3 | "pages": 1, 4 | "data": [ 5 | { 6 | "id": 1, 7 | "label": "object-storage-key-1", 8 | "secret_key": "[REDACTED]", 9 | "access_key": "testAccessKeyHere123", 10 | "limited": false, 11 | "regions": [ 12 | { 13 | "id": "us-east", 14 | "s3_endpoint": "us-east-1.linodeobjects.com" 15 | }, 16 | { 17 | "id": "us-west", 18 | "s3_endpoint": "us-west-123.linodeobjects.com" 19 | } 20 | ] 21 | }, 22 | { 23 | "id": 2, 24 | "label": "object-storage-key-2", 25 | "secret_key": "[REDACTED]", 26 | "access_key": "testAccessKeyHere456", 27 | "limited": true, 28 | "bucket_access": [ 29 | { 30 | "cluster": "us-mia-1", 31 | "bucket_name": "example-bucket", 32 | "permissions": "read_only", 33 | "region": "us-mia" 34 | } 35 | ], 36 | "regions": [ 37 | { 38 | "id": "us-mia", 39 | "s3_endpoint": "us-mia-1.linodeobjects.com" 40 | } 41 | ] 42 | } 43 | ], 44 | "page": 1 45 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_quotas.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "quota_id": "obj-objects-us-ord-1", 5 | "quota_name": "Object Storage Maximum Objects", 6 | "description": "Maximum number of Objects this customer is allowed to have on this endpoint.", 7 | "endpoint_type": "E1", 8 | "s3_endpoint": "us-iad-1.linodeobjects.com", 9 | "quota_limit": 50, 10 | "resource_metric": "object" 11 | }, 12 | { 13 | "quota_id": "obj-bucket-us-ord-1", 14 | "quota_name": "Object Storage Maximum Buckets", 15 | "description": "Maximum number of buckets this customer is allowed to have on this endpoint.", 16 | "endpoint_type": "E1", 17 | "s3_endpoint": "us-iad-1.linodeobjects.com", 18 | "quota_limit": 50, 19 | "resource_metric": "bucket" 20 | } 21 | ], 22 | "page": 1, 23 | "pages": 1, 24 | "results": 2 25 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_quotas_obj-objects-us-ord-1.json: -------------------------------------------------------------------------------- 1 | { 2 | "quota_id": "obj-objects-us-ord-1", 3 | "quota_name": "Object Storage Maximum Objects", 4 | "description": "Maximum number of Objects this customer is allowed to have on this endpoint.", 5 | "endpoint_type": "E1", 6 | "s3_endpoint": "us-iad-1.linodeobjects.com", 7 | "quota_limit": 50, 8 | "resource_metric": "object" 9 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_quotas_obj-objects-us-ord-1_usage.json: -------------------------------------------------------------------------------- 1 | { 2 | "quota_limit": 100, 3 | "usage": 10 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/object-storage_transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "used": 12956600198 3 | } -------------------------------------------------------------------------------- /test/fixtures/object-storage_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "objectstorage", 5 | "label": "Object Storage", 6 | "price": { 7 | "hourly": 0.0015, 8 | "monthly": 0.1 9 | }, 10 | "region_prices": [ 11 | { 12 | "hourly": 0.00018, 13 | "id": "us-east", 14 | "monthly": 0.12 15 | } 16 | ], 17 | "transfer": 0 18 | } 19 | ], 20 | "page": 1, 21 | "pages": 1, 22 | "results": 1 23 | } -------------------------------------------------------------------------------- /test/fixtures/placement_groups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 123, 5 | "label": "test", 6 | "region": "eu-west", 7 | "placement_group_type": "anti_affinity:local", 8 | "placement_group_policy": "strict", 9 | "is_compliant": true, 10 | "members": [ 11 | { 12 | "linode_id": 123, 13 | "is_compliant": true 14 | } 15 | ], 16 | "migrations": { 17 | "inbound": [ 18 | { 19 | "linode_id": 123 20 | } 21 | ], 22 | "outbound": [ 23 | { 24 | "linode_id": 456 25 | } 26 | ] 27 | } 28 | } 29 | ], 30 | "page": 1, 31 | "pages": 1, 32 | "results": 1 33 | } -------------------------------------------------------------------------------- /test/fixtures/placement_groups_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 123, 3 | "label": "test", 4 | "region": "eu-west", 5 | "placement_group_type": "anti_affinity:local", 6 | "placement_group_policy": "strict", 7 | "is_compliant": true, 8 | "members": [ 9 | { 10 | "linode_id": 123, 11 | "is_compliant": true 12 | } 13 | ], 14 | "migrations": { 15 | "inbound": [ 16 | { 17 | "linode_id": 123 18 | } 19 | ], 20 | "outbound": [ 21 | { 22 | "linode_id": 456 23 | } 24 | ] 25 | } 26 | } -------------------------------------------------------------------------------- /test/fixtures/profile.json: -------------------------------------------------------------------------------- 1 | { 2 | "authentication_type": "password", 3 | "authorized_keys": [ 4 | null 5 | ], 6 | "email": "example-user@gmail.com", 7 | "email_notifications": true, 8 | "ip_whitelist_enabled": false, 9 | "lish_auth_method": "keys_only", 10 | "referrals": { 11 | "code": "871be32f49c1411b14f29f618aaf0c14637fb8d3", 12 | "completed": 0, 13 | "credit": 0, 14 | "pending": 0, 15 | "total": 0, 16 | "url": "https://www.linode.com/?r=871be32f49c1411b14f29f618aaf0c14637fb8d3" 17 | }, 18 | "restricted": false, 19 | "timezone": "US/Eastern", 20 | "two_factor_auth": true, 21 | "uid": 1234, 22 | "username": "exampleUser", 23 | "verified_phone_number": "+5555555555" 24 | } -------------------------------------------------------------------------------- /test/fixtures/profile_device_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2018-01-01T01:01:01", 3 | "expiry": "2018-01-31T01:01:01", 4 | "id": 123, 5 | "last_authenticated": "2018-01-05T12:57:12", 6 | "last_remote_addr": "203.0.113.1", 7 | "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 Vivaldi/2.1.1337.36\n" 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/profile_devices.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "created": "2018-01-01T01:01:01", 5 | "expiry": "2018-01-31T01:01:01", 6 | "id": 123, 7 | "last_authenticated": "2018-01-05T12:57:12", 8 | "last_remote_addr": "203.0.113.1", 9 | "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36 Vivaldi/2.1.1337.36\n" 10 | } 11 | ], 12 | "page": 1, 13 | "pages": 1, 14 | "results": 1 15 | } -------------------------------------------------------------------------------- /test/fixtures/profile_logins.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "datetime": "2018-01-01T00:01:01", 5 | "id": 123, 6 | "ip": "192.0.2.0", 7 | "restricted": true, 8 | "status": "successful", 9 | "username": "example_user" 10 | } 11 | ], 12 | "page": 1, 13 | "pages": 1, 14 | "results": 1 15 | } -------------------------------------------------------------------------------- /test/fixtures/profile_logins_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "datetime": "2018-01-01T00:01:01", 3 | "id": 123, 4 | "ip": "192.0.2.0", 5 | "restricted": true, 6 | "status": "successful", 7 | "username": "example_user" 8 | } -------------------------------------------------------------------------------- /test/fixtures/profile_preferences.json: -------------------------------------------------------------------------------- 1 | { 2 | "key1": "value1", 3 | "key2": "value2" 4 | } -------------------------------------------------------------------------------- /test/fixtures/profile_security-questions.json: -------------------------------------------------------------------------------- 1 | { 2 | "security_questions": [ 3 | { 4 | "id": 1, 5 | "question": "In what city were you born?", 6 | "response": "Gotham City" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /test/fixtures/profile_sshkeys.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 22, 5 | "label": "Home Ubuntu PC", 6 | "ssh_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDe9NlKepJsI/S98ISBJmG+cpEARtM0T1Qa5uTOUB/vQFlHmfQW07ZfA++ybPses0vRCDeWyYPIuXcV5yFrf8YAW/Am0+/60MivT3jFY0tDfcrlvjdJAf1NpWOTVlzv0gpsHFO+XIZcfEj3V0K5+pOMw9QGVf6Qbg8qzHVDPFdYKu3imuc9KHY8F/b4DN/Wh17k3xAJpspCZEFkn0bdaYafJj0tPs0k78JRoF2buc3e3M6dlvHaoON1votmrri9lut65OIpglOgPwE3QU8toGyyoCMGaT4R7kIRjXy3WSyTMAi0KTAdxRK+IlDVMXWoE5TdLovd0a9L7qynZungKhKZUgFma7r9aTFVHXKh29Tzb42neDTpQnZ/Et735sDC1vfz/YfgZNdgMUXFJ3+uA4M/36/Vy3Dpj2Larq3qY47RDFitmwSzwUlfztUoyiQ7e1WvXHT4N4Z8K2FPlTvNMg5CSjXHdlzcfiRFPwPn13w36vTvAUxPvTa84P1eOLDp/JzykFbhHNh8Cb02yrU28zDeoTTyjwQs0eHd1wtgIXJ8wuUgcaE4LgcgLYWwiKTq4/FnX/9lfvuAiPFl6KLnh23bcKwnNA7YCWlb1NNLb2y+mCe91D8r88FGvbnhnOuVjd/SxQWDHtxCICmhW7erNJNVxYjtzseGpBLmRRUTsT038w== dorthu@dorthu-command", 7 | "created": "2018-09-14T13:00:00" 8 | }, 9 | { 10 | "id": 72, 11 | "label": "Work Laptop", 12 | "ssh_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC29XlmxbDaEXd4O9eOfjVwHevLGnEocKwsgzQx62CD3FmZ6maZIKYy7J7Si3ct7l69Ic3NcWvl8enXLUUhmoho0whSKCizQFjotC8u+MJwJWFPd6ioYLRRIXH7l0ZQ/oYkxsK13nCx9CiirebM5OXRW4WJET+sYbnNa3cH/PkEDZHvr7vQ+kUxjlcQAMwQ/VF+VpeA7XRFVqvPJr73KEsdQGVc2ZGdaHp7xycgKOuFrTHJT5dVd4wk+3n3DVZgZgYQZim86MWB9TUDXgonDKG4VoQ2Pborxh5lLwElncHFn8digfdmVu5Pg7BtzsLLhfaQFl4EnuU072/WiNmS9I6bs1S4ExEIKOeUqfglT7ypDX2usulK69q4ZAfJHruqPG1+UobhbwwIS8zlFEhDmgWw2zYA9CYMn1WzcfPUKumG+qIOVzK1D+kV1V30WpnQi+BZtey8EKHU2pVdy3SrlZ6WPBnSRGJgNyP8Gq6L1vKFOXPg8RTrpnXMHJv7YwKy/boLwIPUJ/PBuIWo5oqWRYWsE1nAkAoVZE98gJFQGrvsUKsJkbJM4MP4KjFkFOMBkMPTyEubb96VypWzwGaEcpuXcULqvnzgA5Npg8ah/anrAHxUNk8Cc/QfnrKhjAqze4q1+mUR9OCursSs9rKfqd0g1Cbu7FNPw+OQevNI8zpmJw== wsmith@linode", 13 | "created": "2018-09-14T13:30:00" 14 | } 15 | ], 16 | "pages": 1, 17 | "page": 1, 18 | "results": 2 19 | } 20 | -------------------------------------------------------------------------------- /test/fixtures/regions_us-east_availability.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region": "us-east", 4 | "plan": "gpu-rtx6000-1.1", 5 | "available": false 6 | }, 7 | { 8 | "region": "us-east", 9 | "plan": "gpu-rtx6000-2.1", 10 | "available": false 11 | }, 12 | { 13 | "region": "us-east", 14 | "plan": "gpu-rtx6000-3.1", 15 | "available": false 16 | }, 17 | { 18 | "region": "us-east", 19 | "plan": "gpu-rtx6000-4.1", 20 | "available": false 21 | }, 22 | { 23 | "region": "us-east", 24 | "plan": "premium131072.7", 25 | "available": false 26 | }, 27 | { 28 | "region": "us-east", 29 | "plan": "premium16384.7", 30 | "available": false 31 | }, 32 | { 33 | "region": "us-east", 34 | "plan": "premium262144.7", 35 | "available": false 36 | }, 37 | { 38 | "region": "us-east", 39 | "plan": "premium32768.7", 40 | "available": false 41 | }, 42 | { 43 | "region": "us-east", 44 | "plan": "premium4096.7", 45 | "available": false 46 | }, 47 | { 48 | "region": "us-east", 49 | "plan": "premium524288.7", 50 | "available": false 51 | }, 52 | { 53 | "region": "us-east", 54 | "plan": "premium65536.7", 55 | "available": false 56 | }, 57 | { 58 | "region": "us-east", 59 | "plan": "premium8192.7", 60 | "available": false 61 | }, 62 | { 63 | "region": "us-east", 64 | "plan": "premium98304.7", 65 | "available": false 66 | } 67 | ] -------------------------------------------------------------------------------- /test/fixtures/support_tickets_123.json: -------------------------------------------------------------------------------- 1 | { 2 | "attachments": [ 3 | null 4 | ], 5 | "closable": false, 6 | "closed": "2015-06-04T16:07:03", 7 | "description": "I'm having trouble setting the root password on my Linode. I tried following the instructions but something is not working and I'm not sure what I'm doing wrong. Can you please help me figure out how I can reset it?\n", 8 | "entity": { 9 | "id": 10400, 10 | "label": "linode123456", 11 | "type": "linode", 12 | "url": "/v4/linode/instances/123456" 13 | }, 14 | "gravatar_id": "474a1b7373ae0be4132649e69c36ce30", 15 | "id": 123, 16 | "opened": "2015-06-04T14:16:44", 17 | "opened_by": "some_user", 18 | "status": "open", 19 | "summary": "Having trouble resetting root password on my Linode\n", 20 | "updated": "2015-06-04T16:07:03", 21 | "updated_by": "some_other_user" 22 | } 23 | -------------------------------------------------------------------------------- /test/fixtures/tags.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 1, 4 | "results": 2, 5 | "data": [ 6 | { 7 | "label": "nothing" 8 | }, 9 | { 10 | "label": "something" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/tags_nothing.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 1, 4 | "results": 0, 5 | "data": [] 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/tags_something.json: -------------------------------------------------------------------------------- 1 | { 2 | "page": 1, 3 | "pages": 1, 4 | "results": 1, 5 | "data": [ 6 | { 7 | "type": "linode", 8 | "data": { 9 | "group": "test", 10 | "hypervisor": "kvm", 11 | "id": 123, 12 | "status": "running", 13 | "type": "g6-standard-1", 14 | "alerts": { 15 | "network_in": 5, 16 | "network_out": 5, 17 | "cpu": 90, 18 | "transfer_quota": 80, 19 | "io": 5000 20 | }, 21 | "label": "linode123", 22 | "backups": { 23 | "enabled": true, 24 | "schedule": { 25 | "window": "W02", 26 | "day": "Scheduling" 27 | } 28 | }, 29 | "specs": { 30 | "memory": 2048, 31 | "disk": 30720, 32 | "vcpus": 1, 33 | "transfer": 2000 34 | }, 35 | "ipv6": "1234:abcd::1234:abcd:89ef:67cd/64", 36 | "created": "2017-01-01T00:00:00", 37 | "region": "us-east-1a", 38 | "ipv4": [ 39 | "123.45.67.89" 40 | ], 41 | "updated": "2017-01-01T00:00:00", 42 | "image": "linode/ubuntu17.04", 43 | "tags": ["something"] 44 | } 45 | }, 46 | { 47 | "type": "domain", 48 | "data": { 49 | "domain": "example.org", 50 | "type": "master", 51 | "id": 12345, 52 | "axfr_ips": [], 53 | "retry_sec": 0, 54 | "ttl_sec": 300, 55 | "status": "active", 56 | "master_ips": [], 57 | "description": "", 58 | "group": "", 59 | "expire_sec": 0, 60 | "soa_email": "test@example.org", 61 | "refresh_sec": 0, 62 | "tags": ["something"] 63 | } 64 | }, 65 | { 66 | "type": "nodebalancer", 67 | "data": { 68 | "created": "2018-01-01T00:01:01", 69 | "ipv6": "c001:d00d:b01::1:abcd:1234", 70 | "region": "us-east-1a", 71 | "ipv4": "12.34.56.789", 72 | "hostname": "nb-12-34-56-789.newark.nodebalancer.linode.com", 73 | "id": 123456, 74 | "updated": "2018-01-01T00:01:01", 75 | "label": "balancer123456", 76 | "client_conn_throttle": 0, 77 | "tags": ["something"] 78 | } 79 | }, 80 | { 81 | "type": "volume", 82 | "data": { 83 | "id": 1, 84 | "label": "block1", 85 | "created": "2017-08-04T03:00:00", 86 | "region": "us-east-1a", 87 | "linode_id": null, 88 | "size": 40, 89 | "updated": "2017-08-04T04:00:00", 90 | "status": "active", 91 | "tags": ["something"] 92 | } 93 | } 94 | ] 95 | } 96 | -------------------------------------------------------------------------------- /test/fixtures/testmappedobj1.json: -------------------------------------------------------------------------------- 1 | { 2 | "bar": "bar" 3 | } -------------------------------------------------------------------------------- /test/fixtures/volumes.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": 1, 5 | "label": "block1", 6 | "created": "2017-08-04T03:00:00", 7 | "region": "us-east-1a", 8 | "linode_id": null, 9 | "size": 40, 10 | "updated": "2017-08-04T04:00:00", 11 | "status": "active", 12 | "tags": ["something"], 13 | "filesystem_path": "this/is/a/file/path", 14 | "hardware_type": "hdd", 15 | "linode_label": null 16 | }, 17 | { 18 | "id": 2, 19 | "label": "block2", 20 | "created": "2017-08-06T17:00:00", 21 | "region": "ap-south-1a", 22 | "linode_id": null, 23 | "size": 100, 24 | "updated": "2017-08-07T04:00:00", 25 | "status": "active", 26 | "tags": [], 27 | "filesystem_path": "this/is/a/file/path", 28 | "hardware_type": "nvme", 29 | "linode_label": null 30 | }, 31 | { 32 | "id": 3, 33 | "label": "block3", 34 | "created": "2017-08-06T17:00:00", 35 | "region": "ap-south-1a", 36 | "linode_id": 1, 37 | "size": 200, 38 | "updated": "2017-08-07T04:00:00", 39 | "status": "active", 40 | "tags": ["attached"], 41 | "filesystem_path": "this/is/a/file/path", 42 | "hardware_type": "nvme", 43 | "linode_label": "some_label" 44 | }, 45 | { 46 | "id": 4, 47 | "label": "block4", 48 | "created": "2017-08-04T03:00:00", 49 | "region": "ap-west-1a", 50 | "linode_id": null, 51 | "size": 40, 52 | "updated": "2017-08-04T04:00:00", 53 | "status": "active", 54 | "tags": ["something"], 55 | "filesystem_path": "this/is/a/file/path", 56 | "hardware_type": "hdd", 57 | "linode_label": null, 58 | "encryption": "enabled" 59 | } 60 | ], 61 | "results": 4, 62 | "pages": 1, 63 | "page": 1 64 | } 65 | -------------------------------------------------------------------------------- /test/fixtures/volumes_types.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "id": "volume", 5 | "label": "Storage Volume", 6 | "price": { 7 | "hourly": 0.00015, 8 | "monthly": 0.1 9 | }, 10 | "region_prices": [ 11 | { 12 | "id": "id-cgk", 13 | "hourly": 0.00018, 14 | "monthly": 0.12 15 | }, 16 | { 17 | "id": "br-gru", 18 | "hourly": 0.00021, 19 | "monthly": 0.14 20 | } 21 | ], 22 | "transfer": 0 23 | } 24 | ], 25 | "page": 1, 26 | "pages": 1, 27 | "results": 1 28 | } -------------------------------------------------------------------------------- /test/fixtures/vpcs.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "label": "test-vpc", 5 | "id": 123456, 6 | "description": "A very real VPC.", 7 | "region": "us-southeast", 8 | "created": "2018-01-01T00:01:01", 9 | "updated": "2018-01-01T00:01:01" 10 | } 11 | ], 12 | "results": 1, 13 | "page": 1, 14 | "pages": 1 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/vpcs_123456.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "test-vpc", 3 | "id": 123456, 4 | "description": "A very real VPC.", 5 | "region": "us-southeast", 6 | "created": "2018-01-01T00:01:01", 7 | "updated": "2018-01-01T00:01:01" 8 | } -------------------------------------------------------------------------------- /test/fixtures/vpcs_123456_ips.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "address": "10.0.0.2", 5 | "address_range": null, 6 | "vpc_id": 123456, 7 | "subnet_id": 654321, 8 | "region": "us-ord", 9 | "linode_id": 111, 10 | "config_id": 222, 11 | "interface_id": 333, 12 | "active": true, 13 | "nat_1_1": null, 14 | "gateway": "10.0.0.1", 15 | "prefix": 8, 16 | "subnet_mask": "255.0.0.0" 17 | }, 18 | { 19 | "address": "10.0.0.3", 20 | "address_range": null, 21 | "vpc_id": 41220, 22 | "subnet_id": 41184, 23 | "region": "us-ord", 24 | "linode_id": 56323949, 25 | "config_id": 59467106, 26 | "interface_id": 1248358, 27 | "active": true, 28 | "nat_1_1": null, 29 | "gateway": "10.0.0.1", 30 | "prefix": 8, 31 | "subnet_mask": "255.0.0.0" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /test/fixtures/vpcs_123456_subnets.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "label": "test-subnet", 5 | "id": 789, 6 | "ipv4": "10.0.0.0/24", 7 | "linodes": [ 8 | { 9 | "id": 12345, 10 | "interfaces": [ 11 | { 12 | "id": 678, 13 | "active": true 14 | }, 15 | { 16 | "id": 543, 17 | "active": false 18 | } 19 | ] 20 | } 21 | ], 22 | "created": "2018-01-01T00:01:01", 23 | "updated": "2018-01-01T00:01:01" 24 | } 25 | ], 26 | "results": 1, 27 | "page": 1, 28 | "pages": 1 29 | } -------------------------------------------------------------------------------- /test/fixtures/vpcs_123456_subnets_789.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "test-subnet", 3 | "id": 789, 4 | "ipv4": "10.0.0.0/24", 5 | "linodes": [ 6 | { 7 | "id": 12345, 8 | "interfaces": [ 9 | { 10 | "id": 678, 11 | "active": true 12 | }, 13 | { 14 | "id": 543, 15 | "active": false 16 | } 17 | ] 18 | } 19 | ], 20 | "created": "2018-01-01T00:01:01", 21 | "updated": "2018-01-01T00:01:01" 22 | } -------------------------------------------------------------------------------- /test/fixtures/vpcs_ips.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "address": "10.0.0.2", 5 | "address_range": null, 6 | "vpc_id": 123, 7 | "subnet_id": 456, 8 | "region": "us-mia", 9 | "linode_id": 123, 10 | "config_id": 456, 11 | "interface_id": 789, 12 | "active": true, 13 | "nat_1_1": "172.233.179.133", 14 | "gateway": "10.0.0.1", 15 | "prefix": 24, 16 | "subnet_mask": "255.255.255.0" 17 | } 18 | ], 19 | "page": 1, 20 | "pages": 1, 21 | "results": 1 22 | } -------------------------------------------------------------------------------- /test/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linode/linode_api4-python/7b7f6470c8d61f46f9561b1cdaa8fee7a090d607/test/integration/__init__.py -------------------------------------------------------------------------------- /test/integration/helpers.py: -------------------------------------------------------------------------------- 1 | import random 2 | import time 3 | from string import ascii_lowercase 4 | from typing import Callable 5 | 6 | from linode_api4.errors import ApiError 7 | 8 | 9 | def get_test_label(length: int = 8): 10 | return "".join(random.choice(ascii_lowercase) for i in range(length)) 11 | 12 | 13 | def wait_for_condition( 14 | interval: int, timeout: int, condition: Callable, *args 15 | ) -> object: 16 | end_time = time.time() + timeout 17 | while time.time() < end_time: 18 | result = condition(*args) 19 | if result: 20 | return result 21 | time.sleep(interval) 22 | raise TimeoutError( 23 | f"Timeout Error: resource not available in {timeout} seconds" 24 | ) 25 | 26 | 27 | # Retry function to help in case of requests sending too quickly before instance is ready 28 | def retry_sending_request( 29 | retries: int, condition: Callable, *args, backoff: int = 5, **kwargs 30 | ) -> object: 31 | for attempt in range(1, retries + 1): 32 | try: 33 | return condition(*args, **kwargs) 34 | except ApiError: 35 | if attempt == retries: 36 | raise ApiError( 37 | "Api Error: Failed after all retry attempts" 38 | ) from None 39 | time.sleep(backoff) 40 | 41 | 42 | def send_request_when_resource_available( 43 | timeout: int, func: Callable, *args, **kwargs 44 | ) -> object: 45 | start_time = time.time() 46 | retry_statuses = {400, 500} 47 | 48 | while True: 49 | try: 50 | return func(*args, **kwargs) 51 | except ApiError as e: 52 | if e.status in retry_statuses or "Please try again later" in str(e): 53 | if time.time() - start_time > timeout: 54 | raise TimeoutError( 55 | f"Timeout Error: resource not available in {timeout} seconds" 56 | ) 57 | time.sleep(10) 58 | else: 59 | raise e 60 | -------------------------------------------------------------------------------- /test/integration/linode_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linode/linode_api4-python/7b7f6470c8d61f46f9561b1cdaa8fee7a090d607/test/integration/linode_client/__init__.py -------------------------------------------------------------------------------- /test/integration/linode_client/test_errors.py: -------------------------------------------------------------------------------- 1 | from linode_api4.errors import ApiError 2 | 3 | 4 | def test_error_404(test_linode_client): 5 | api_exc = None 6 | 7 | try: 8 | test_linode_client.get("/invalid/endpoint") 9 | except ApiError as exc: 10 | api_exc = exc 11 | 12 | assert str(api_exc) == "GET /v4beta/invalid/endpoint: [404] Not found" 13 | 14 | 15 | def test_error_400(test_linode_client): 16 | api_exc = None 17 | 18 | try: 19 | test_linode_client.linode.instance_create( 20 | "g6-fake-plan", "us-fakeregion" 21 | ) 22 | except ApiError as exc: 23 | api_exc = exc 24 | 25 | assert str(api_exc) == ( 26 | "POST /v4beta/linode/instances: [400] type: A valid plan type by that ID was not found; " 27 | "region: region is not valid" 28 | ) 29 | -------------------------------------------------------------------------------- /test/integration/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linode/linode_api4-python/7b7f6470c8d61f46f9561b1cdaa8fee7a090d607/test/integration/models/__init__.py -------------------------------------------------------------------------------- /test/integration/models/domain/test_domain.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from test.integration.helpers import wait_for_condition 4 | 5 | import pytest 6 | 7 | from linode_api4.objects import Domain, DomainRecord 8 | 9 | 10 | @pytest.mark.smoke 11 | def test_get_domain_record(test_linode_client, test_domain): 12 | dr = DomainRecord( 13 | test_linode_client, test_domain.records.first().id, test_domain.id 14 | ) 15 | 16 | assert dr.id == test_domain.records.first().id 17 | 18 | 19 | def test_save_null_values_excluded(test_linode_client, test_domain): 20 | domain = test_linode_client.load(Domain, test_domain.id) 21 | 22 | domain.type = "master" 23 | domain.master_ips = ["127.0.0.1"] 24 | res = domain.save() 25 | 26 | 27 | def test_zone_file_view(test_linode_client, test_domain): 28 | domain = test_linode_client.load(Domain, test_domain.id) 29 | 30 | def get_zone_file_view(): 31 | res = domain.zone_file_view() 32 | return res != [] 33 | 34 | wait_for_condition(10, 100, get_zone_file_view) 35 | 36 | assert domain.domain in str(domain.zone_file_view()) 37 | assert re.search("ns[0-9].linode.com", str(domain.zone_file_view())) 38 | 39 | 40 | def test_clone(test_linode_client, test_domain): 41 | domain = test_linode_client.load(Domain, test_domain.id) 42 | timestamp = str(time.time_ns()) 43 | dom = "example.clone-" + timestamp + "-inttestsdk.org" 44 | domain.clone(dom) 45 | 46 | ds = test_linode_client.domains() 47 | 48 | time.sleep(1) 49 | 50 | domains = [i.domain for i in ds] 51 | 52 | assert dom in domains 53 | 54 | 55 | def test_import(test_linode_client, test_domain): 56 | pytest.skip( 57 | 'Currently failing with message: linode_api4.errors.ApiError: 400: An unknown error occured. Please open a ticket for further assistance. Command: domain_import(domain, "google.ca")' 58 | ) 59 | domain = test_linode_client.load(Domain, test_domain.id) 60 | -------------------------------------------------------------------------------- /test/integration/models/firewall/test_firewall.py: -------------------------------------------------------------------------------- 1 | import time 2 | from test.integration.conftest import get_region 3 | from test.integration.helpers import get_test_label 4 | 5 | import pytest 6 | 7 | from linode_api4.objects import Firewall, FirewallDevice 8 | 9 | 10 | @pytest.fixture(scope="session") 11 | def linode_fw(test_linode_client): 12 | client = test_linode_client 13 | region = get_region(client, {"Linodes", "Cloud Firewall"}, site_type="core") 14 | label = get_test_label() 15 | 16 | linode_instance, password = client.linode.instance_create( 17 | "g6-nanode-1", region, image="linode/debian12", label=label 18 | ) 19 | 20 | yield linode_instance 21 | 22 | linode_instance.delete() 23 | 24 | 25 | @pytest.mark.smoke 26 | def test_get_firewall_rules(test_linode_client, test_firewall): 27 | firewall = test_linode_client.load(Firewall, test_firewall.id) 28 | rules = firewall.rules 29 | 30 | assert rules.inbound_policy in ["ACCEPT", "DROP"] 31 | assert rules.outbound_policy in ["ACCEPT", "DROP"] 32 | 33 | 34 | @pytest.mark.smoke 35 | def test_update_firewall_rules(test_linode_client, test_firewall): 36 | firewall = test_linode_client.load(Firewall, test_firewall.id) 37 | new_rules = { 38 | "inbound": [ 39 | { 40 | "action": "ACCEPT", 41 | "addresses": { 42 | "ipv4": ["0.0.0.0/0"], 43 | "ipv6": ["ff00::/8"], 44 | }, 45 | "description": "A really cool firewall rule.", 46 | "label": "really-cool-firewall-rule", 47 | "ports": "80", 48 | "protocol": "TCP", 49 | } 50 | ], 51 | "inbound_policy": "ACCEPT", 52 | "outbound": [], 53 | "outbound_policy": "DROP", 54 | } 55 | 56 | firewall.update_rules(new_rules) 57 | 58 | time.sleep(1) 59 | 60 | firewall = test_linode_client.load(Firewall, test_firewall.id) 61 | 62 | assert firewall.rules.inbound_policy == "ACCEPT" 63 | assert firewall.rules.outbound_policy == "DROP" 64 | 65 | 66 | def test_get_devices(test_linode_client, linode_fw, test_firewall): 67 | linode = linode_fw 68 | 69 | test_firewall.device_create(int(linode.id)) 70 | 71 | firewall = test_linode_client.load(Firewall, test_firewall.id) 72 | 73 | assert len(firewall.devices) > 0 74 | 75 | 76 | def test_get_device(test_linode_client, test_firewall, linode_fw): 77 | firewall = test_firewall 78 | 79 | firewall_device = test_linode_client.load( 80 | FirewallDevice, firewall.devices.first().id, firewall.id 81 | ) 82 | 83 | assert firewall_device.entity.type == "linode" 84 | assert "/v4/linode/instances/" in firewall_device.entity.url 85 | -------------------------------------------------------------------------------- /test/integration/models/longview/test_longview.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from test.integration.helpers import get_test_label 4 | 5 | import pytest 6 | 7 | from linode_api4.objects import ( 8 | ApiError, 9 | LongviewClient, 10 | LongviewPlan, 11 | LongviewSubscription, 12 | ) 13 | 14 | 15 | @pytest.mark.smoke 16 | def test_get_longview_client(test_linode_client, test_longview_client): 17 | longview = test_linode_client.load(LongviewClient, test_longview_client.id) 18 | 19 | assert longview.id == test_longview_client.id 20 | 21 | 22 | def test_update_longview_label(test_linode_client, test_longview_client): 23 | longview = test_linode_client.load(LongviewClient, test_longview_client.id) 24 | old_label = longview.label 25 | 26 | label = get_test_label(10) 27 | 28 | longview.label = label 29 | 30 | longview.save() 31 | 32 | assert longview.label != old_label 33 | 34 | 35 | def test_delete_client(test_linode_client, test_longview_client): 36 | client = test_linode_client 37 | label = get_test_label(length=8) 38 | longview_client = client.longview.client_create(label=label) 39 | 40 | time.sleep(5) 41 | 42 | res = longview_client.delete() 43 | 44 | assert res 45 | 46 | 47 | def test_get_longview_subscription(test_linode_client, test_longview_client): 48 | subs = test_linode_client.longview.subscriptions() 49 | sub = test_linode_client.load(LongviewSubscription, subs[0].id) 50 | 51 | assert "clients_included" in str(subs.first().__dict__) 52 | 53 | assert re.search("[0-9]+", str(sub.price.hourly)) 54 | assert re.search("[0-9]+", str(sub.price.monthly)) 55 | 56 | assert "longview-3" in str(subs.lists) 57 | assert "longview-10" in str(subs.lists) 58 | assert "longview-40" in str(subs.lists) 59 | assert "longview-100" in str(subs.lists) 60 | 61 | 62 | def test_longview_plan_update_method_not_allowed(test_linode_client): 63 | try: 64 | test_linode_client.longview.longview_plan_update("longview-100") 65 | except ApiError as e: 66 | assert e.status == 405 67 | assert "Method Not Allowed" in str(e) 68 | 69 | 70 | def test_get_current_longview_plan(test_linode_client): 71 | lv_plan = test_linode_client.load(LongviewPlan, "") 72 | 73 | if lv_plan.label is not None: 74 | assert "Longview" in lv_plan.label 75 | assert "hourly" in lv_plan.price.dict 76 | assert "monthly" in lv_plan.price.dict 77 | -------------------------------------------------------------------------------- /test/integration/models/object_storage/test_obj_quotas.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from linode_api4.objects.object_storage import ( 4 | ObjectStorageQuota, 5 | ObjectStorageQuotaUsage, 6 | ) 7 | 8 | 9 | def test_list_and_get_obj_storage_quotas(test_linode_client): 10 | quotas = test_linode_client.object_storage.quotas() 11 | 12 | if len(quotas) < 1: 13 | pytest.skip("No available quota for testing. Skipping now...") 14 | 15 | found_quota = quotas[0] 16 | 17 | get_quota = test_linode_client.load( 18 | ObjectStorageQuota, found_quota.quota_id 19 | ) 20 | 21 | assert found_quota.quota_id == get_quota.quota_id 22 | assert found_quota.quota_name == get_quota.quota_name 23 | assert found_quota.endpoint_type == get_quota.endpoint_type 24 | assert found_quota.s3_endpoint == get_quota.s3_endpoint 25 | assert found_quota.description == get_quota.description 26 | assert found_quota.quota_limit == get_quota.quota_limit 27 | assert found_quota.resource_metric == get_quota.resource_metric 28 | 29 | 30 | def test_get_obj_storage_quota_usage(test_linode_client): 31 | quotas = test_linode_client.object_storage.quotas() 32 | 33 | if len(quotas) < 1: 34 | pytest.skip("No available quota for testing. Skipping now...") 35 | 36 | quota_id = quotas[0].quota_id 37 | quota = test_linode_client.load(ObjectStorageQuota, quota_id) 38 | 39 | quota_usage = quota.usage() 40 | 41 | assert isinstance(quota_usage, ObjectStorageQuotaUsage) 42 | assert quota_usage.quota_limit >= 0 43 | 44 | if quota_usage.usage is not None: 45 | assert quota_usage.usage >= 0 46 | -------------------------------------------------------------------------------- /test/integration/models/profile/test_profile.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from linode_api4.objects import PersonalAccessToken, Profile, SSHKey 4 | 5 | 6 | @pytest.mark.smoke 7 | def test_user_profile(test_linode_client): 8 | client = test_linode_client 9 | 10 | profile = client.profile() 11 | 12 | assert isinstance(profile, Profile) 13 | 14 | 15 | def test_get_personal_access_token_objects(test_linode_client): 16 | client = test_linode_client 17 | 18 | personal_access_tokens = client.profile.tokens() 19 | 20 | if len(personal_access_tokens) > 0: 21 | assert isinstance(personal_access_tokens[0], PersonalAccessToken) 22 | 23 | 24 | @pytest.mark.smoke 25 | @pytest.mark.flaky(reruns=3, reruns_delay=2) 26 | def test_get_sshkeys(test_linode_client, test_sshkey): 27 | client = test_linode_client 28 | 29 | ssh_keys = client.profile.ssh_keys() 30 | 31 | ssh_labels = [i.label for i in ssh_keys] 32 | 33 | assert isinstance(test_sshkey, SSHKey) 34 | assert test_sshkey.label in ssh_labels 35 | 36 | 37 | @pytest.mark.flaky(reruns=3, reruns_delay=2) 38 | def test_ssh_key_create(test_sshkey, ssh_key_gen): 39 | pub_key = ssh_key_gen[0] 40 | key = test_sshkey 41 | 42 | assert pub_key == key._raw_json["ssh_key"] 43 | -------------------------------------------------------------------------------- /test/integration/models/tag/test_tag.py: -------------------------------------------------------------------------------- 1 | from test.integration.helpers import get_test_label 2 | 3 | import pytest 4 | 5 | from linode_api4.objects import Tag 6 | 7 | 8 | @pytest.fixture 9 | def test_tag(test_linode_client): 10 | unique_tag = get_test_label() + "_tag" 11 | tag = test_linode_client.tag_create(unique_tag) 12 | 13 | yield tag 14 | 15 | tag.delete() 16 | 17 | 18 | @pytest.mark.smoke 19 | def test_get_tag(test_linode_client, test_tag): 20 | tag = test_linode_client.load(Tag, test_tag.id) 21 | 22 | assert tag.id == test_tag.id 23 | -------------------------------------------------------------------------------- /test/unit/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/linode/linode_api4-python/7b7f6470c8d61f46f9561b1cdaa8fee7a090d607/test/unit/__init__.py -------------------------------------------------------------------------------- /test/unit/fixtures.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import re 4 | import sys 5 | 6 | FIXTURES_DIR = sys.path[0] + "/test/fixtures" 7 | 8 | # This regex is useful for finding individual underscore characters, 9 | # which is necessary to allow us to use underscores in URL paths. 10 | PATH_REPLACEMENT_REGEX = re.compile(r"(? 0 24 | 25 | for entry in avail_entries: 26 | assert entry.region is not None 27 | assert len(entry.region) > 0 28 | 29 | assert entry.plan is not None 30 | assert len(entry.plan) > 0 31 | 32 | assert entry.available is not None 33 | 34 | # Ensure all three pages are read 35 | assert m.call_count == 3 36 | assert m.mock.call_args_list[0].args[0] == "//regions/availability" 37 | 38 | assert ( 39 | m.mock.call_args_list[1].args[0] 40 | == "//regions/availability?page=2&page_size=100" 41 | ) 42 | assert ( 43 | m.mock.call_args_list[2].args[0] 44 | == "//regions/availability?page=3&page_size=100" 45 | ) 46 | 47 | # Ensure the filter headers are correct 48 | for k, call in m.mock.call_args_list: 49 | assert json.loads(call.get("headers").get("X-Filter")) == { 50 | "+and": [{"region": "us-east"}, {"plan": "premium4096.7"}] 51 | } 52 | -------------------------------------------------------------------------------- /test/unit/login_client_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from linode_api4 import OAuthScopes 4 | 5 | 6 | class OAuthScopesTest(TestCase): 7 | def test_parse_scopes_none(self): 8 | """ 9 | Tests parsing no scopes 10 | """ 11 | scopes = OAuthScopes.parse("") 12 | self.assertEqual(scopes, []) 13 | 14 | def test_parse_scopes_single(self): 15 | """ 16 | Tests parsing a single scope 17 | """ 18 | scopes = OAuthScopes.parse("linodes:read_only") 19 | self.assertEqual(scopes, [OAuthScopes.Linodes.read_only]) 20 | 21 | def test_parse_scopes_many(self): 22 | """ 23 | Tests parsing many scopes 24 | """ 25 | scopes = OAuthScopes.parse("linodes:read_only domains:read_write") 26 | self.assertEqual( 27 | scopes, 28 | [OAuthScopes.Linodes.read_only, OAuthScopes.Domains.read_write], 29 | ) 30 | 31 | def test_parse_scopes_many_comma_delimited(self): 32 | """ 33 | Tests parsing many scopes that are comma-delimited (which preserves old behavior) 34 | """ 35 | scopes = OAuthScopes.parse( 36 | "nodebalancers:read_write,stackscripts:*,events:read_only" 37 | ) 38 | self.assertEqual( 39 | scopes, 40 | [ 41 | OAuthScopes.NodeBalancers.read_write, 42 | OAuthScopes.StackScripts.all, 43 | OAuthScopes.Events.read_only, 44 | ], 45 | ) 46 | 47 | def test_parse_scopes_all(self): 48 | """ 49 | Tests parsing * scopes 50 | """ 51 | scopes = OAuthScopes.parse("*") 52 | self.assertEqual( 53 | scopes, 54 | [getattr(c, "all") for c in OAuthScopes._scope_families.values()], 55 | ) 56 | -------------------------------------------------------------------------------- /test/unit/objects/beta_test.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from test.unit.base import ClientBaseCase 3 | 4 | from linode_api4.objects import BetaProgram 5 | 6 | 7 | class BetaProgramTest(ClientBaseCase): 8 | """ 9 | Test the methods of the Beta Program. 10 | """ 11 | 12 | def test_beta_program_api_get(self): 13 | beta_id = "active" 14 | beta_program_api_get_url = "/betas/{}".format(beta_id) 15 | 16 | with self.mock_get(beta_program_api_get_url) as m: 17 | beta_program = BetaProgram(self.client, beta_id) 18 | self.assertEqual(beta_program.id, beta_id) 19 | self.assertEqual(beta_program.label, "active closed beta") 20 | self.assertEqual(beta_program.description, "An active closed beta") 21 | self.assertEqual( 22 | beta_program.started, datetime(2018, 1, 2, 3, 4, 5) 23 | ) 24 | self.assertEqual(beta_program.ended, None) 25 | self.assertEqual(beta_program.greenlight_only, True) 26 | self.assertEqual( 27 | beta_program.more_info, "a link with even more info" 28 | ) 29 | 30 | self.assertEqual(m.call_url, beta_program_api_get_url) 31 | -------------------------------------------------------------------------------- /test/unit/objects/domain_test.py: -------------------------------------------------------------------------------- 1 | from test.unit.base import ClientBaseCase 2 | 3 | from linode_api4.objects import Domain, DomainRecord 4 | 5 | 6 | class DomainGeneralTest(ClientBaseCase): 7 | """ 8 | Tests methods of the Domain class. 9 | """ 10 | 11 | def test_domain_get(self): 12 | domain_record = DomainRecord(self.client, 123456, 12345) 13 | 14 | self.assertEqual(domain_record.id, 123456) 15 | 16 | def test_save_null_values_excluded(self): 17 | with self.mock_put("domains/12345") as m: 18 | domain = self.client.load(Domain, 12345) 19 | 20 | domain.type = "slave" 21 | domain.master_ips = ["127.0.0.1"] 22 | domain.save() 23 | self.assertTrue("group" not in m.call_data.keys()) 24 | 25 | def test_zone_file_view(self): 26 | domain = Domain(self.client, 12345) 27 | 28 | with self.mock_get("/domains/12345/zone-file") as m: 29 | result = domain.zone_file_view() 30 | self.assertEqual(m.call_url, "/domains/12345/zone-file") 31 | self.assertIsNotNone(result) 32 | 33 | def test_clone(self): 34 | domain = Domain(self.client, 12345) 35 | 36 | with self.mock_post("/domains/12345/clone") as m: 37 | clone = domain.clone("example.org") 38 | self.assertEqual(m.call_url, "/domains/12345/clone") 39 | self.assertEqual(m.call_data["domain"], "example.org") 40 | self.assertEqual(clone.id, 12345) 41 | 42 | def test_import(self): 43 | domain = Domain(self.client, 12345) 44 | 45 | with self.mock_post("/domains/import") as m: 46 | domain.domain_import("example.org", "examplenameserver.com") 47 | self.assertEqual(m.call_url, "/domains/import") 48 | self.assertEqual(m.call_data["domain"], "example.org") 49 | self.assertEqual( 50 | m.call_data["remote_nameserver"], "examplenameserver.com" 51 | ) 52 | 53 | with self.mock_post("/domains/import") as m: 54 | domain.domain_import(domain, "examplenameserver.com") 55 | self.assertEqual(m.call_url, "/domains/import") 56 | self.assertEqual(m.call_data["domain"], "example.org") 57 | self.assertEqual( 58 | m.call_data["remote_nameserver"], "examplenameserver.com" 59 | ) 60 | -------------------------------------------------------------------------------- /test/unit/objects/firewall_test.py: -------------------------------------------------------------------------------- 1 | from test.unit.base import ClientBaseCase 2 | 3 | from linode_api4.objects import Firewall, FirewallDevice 4 | 5 | 6 | class FirewallTest(ClientBaseCase): 7 | """ 8 | Tests methods of the Firewall class 9 | """ 10 | 11 | def test_get_rules(self): 12 | """ 13 | Test that the rules can be retrieved from a Firewall 14 | """ 15 | firewall = Firewall(self.client, 123) 16 | rules = firewall.rules 17 | 18 | self.assertEqual(len(rules.inbound), 0) 19 | self.assertEqual(rules.inbound_policy, "DROP") 20 | self.assertEqual(len(rules.outbound), 0) 21 | self.assertEqual(rules.outbound_policy, "DROP") 22 | 23 | def test_update_rules(self): 24 | """ 25 | Test that the rules can be updated for a Firewall 26 | """ 27 | 28 | firewall = Firewall(self.client, 123) 29 | 30 | with self.mock_put("networking/firewalls/123/rules") as m: 31 | new_rules = { 32 | "inbound": [ 33 | { 34 | "action": "ACCEPT", 35 | "addresses": { 36 | "ipv4": ["0.0.0.0/0"], 37 | "ipv6": ["ff00::/8"], 38 | }, 39 | "description": "A really cool firewall rule.", 40 | "label": "really-cool-firewall-rule", 41 | "ports": "80", 42 | "protocol": "TCP", 43 | } 44 | ], 45 | "inbound_policy": "ALLOW", 46 | "outbound": [], 47 | "outbound_policy": "ALLOW", 48 | } 49 | 50 | firewall.update_rules(new_rules) 51 | 52 | self.assertEqual(m.method, "put") 53 | self.assertEqual(m.call_url, "/networking/firewalls/123/rules") 54 | 55 | self.assertEqual(m.call_data, new_rules) 56 | 57 | 58 | class FirewallDevicesTest(ClientBaseCase): 59 | """ 60 | Tests methods of Firewall devices 61 | """ 62 | 63 | def test_get_devices(self): 64 | """ 65 | Tests that devices can be pulled from a firewall 66 | """ 67 | firewall = Firewall(self.client, 123) 68 | self.assertEqual(len(firewall.devices), 1) 69 | 70 | def test_get_device(self): 71 | """ 72 | Tests that a device is loaded correctly by ID 73 | """ 74 | device = FirewallDevice(self.client, 123, 123) 75 | self.assertEqual(device._populated, False) 76 | 77 | self.assertEqual(device.id, 123) 78 | self.assertEqual(device.entity.id, 123) 79 | self.assertEqual(device.entity.label, "my-linode") 80 | self.assertEqual(device.entity.type, "linode") 81 | self.assertEqual(device.entity.url, "/v4/linode/instances/123") 82 | 83 | self.assertEqual(device._populated, True) 84 | -------------------------------------------------------------------------------- /test/unit/objects/mapped_object_test.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from test.unit.base import ClientBaseCase 3 | 4 | from linode_api4.objects import Base, JSONObject, MappedObject, Property 5 | 6 | 7 | class MappedObjectCase(ClientBaseCase): 8 | def test_mapped_object_dict(self): 9 | test_dict = { 10 | "key1": 1, 11 | "key2": "2", 12 | "key3": 3.3, 13 | "key4": [41, "42", {"key4-3": "43"}], 14 | "key5": { 15 | "key5-1": 1, 16 | "key5-2": {"key5-2-1": {"key5-2-1-1": 1}}, 17 | "key5-3": [{"key5-3-1": 531}, {"key5-3-2": 532}], 18 | }, 19 | } 20 | 21 | mapped_obj = MappedObject(**test_dict) 22 | self.assertEqual(mapped_obj.dict, test_dict) 23 | 24 | def test_serialize_base_objects(self): 25 | test_property_name = "bar" 26 | test_property_value = "bar" 27 | 28 | class Foo(Base): 29 | api_endpoint = "/testmappedobj1" 30 | id_attribute = test_property_name 31 | properties = { 32 | test_property_name: Property(mutable=True), 33 | } 34 | 35 | foo = Foo(self.client, test_property_value) 36 | foo._api_get() 37 | 38 | expected_dict = { 39 | "foo": { 40 | test_property_name: test_property_value, 41 | } 42 | } 43 | 44 | mapped_obj = MappedObject(foo=foo) 45 | self.assertEqual(mapped_obj.dict, expected_dict) 46 | 47 | def test_serialize_json_objects(self): 48 | test_property_name = "bar" 49 | test_property_value = "bar" 50 | 51 | @dataclass 52 | class Foo(JSONObject): 53 | bar: str = "" 54 | 55 | foo = Foo.from_json({test_property_name: test_property_value}) 56 | 57 | expected_dict = { 58 | "foo": { 59 | test_property_name: test_property_value, 60 | } 61 | } 62 | 63 | mapped_obj = MappedObject(foo=foo) 64 | self.assertEqual(mapped_obj.dict, expected_dict) 65 | -------------------------------------------------------------------------------- /test/unit/objects/placement_test.py: -------------------------------------------------------------------------------- 1 | from test.unit.base import ClientBaseCase 2 | 3 | from linode_api4.objects import ( 4 | MigratedInstance, 5 | PlacementGroup, 6 | PlacementGroupMember, 7 | ) 8 | 9 | 10 | class PlacementTest(ClientBaseCase): 11 | """ 12 | Tests methods of the Placement Group 13 | """ 14 | 15 | def test_get_placement_group(self): 16 | """ 17 | Tests that a Placement Group is loaded correctly by ID 18 | """ 19 | 20 | pg = PlacementGroup(self.client, 123) 21 | assert not pg._populated 22 | 23 | self.validate_pg_123(pg) 24 | assert pg._populated 25 | 26 | def test_pg_assign(self): 27 | """ 28 | Tests that you can assign to a PG. 29 | """ 30 | 31 | pg = PlacementGroup(self.client, 123) 32 | assert not pg._populated 33 | 34 | with self.mock_post("/placement/groups/123") as m: 35 | pg.assign([123], compliant_only=True) 36 | 37 | assert m.call_url == "/placement/groups/123/assign" 38 | 39 | # Ensure the PG state was populated 40 | assert pg._populated 41 | 42 | self.assertEqual( 43 | m.call_data, 44 | {"linodes": [123], "compliant_only": True}, 45 | ) 46 | 47 | def test_pg_unassign(self): 48 | """ 49 | Tests that you can unassign from a PG. 50 | """ 51 | 52 | pg = PlacementGroup(self.client, 123) 53 | assert not pg._populated 54 | 55 | with self.mock_post("/placement/groups/123") as m: 56 | pg.unassign([123]) 57 | 58 | assert m.call_url == "/placement/groups/123/unassign" 59 | 60 | # Ensure the PG state was populated 61 | assert pg._populated 62 | 63 | self.assertEqual( 64 | m.call_data, 65 | {"linodes": [123]}, 66 | ) 67 | 68 | def validate_pg_123(self, pg: PlacementGroup): 69 | assert pg.id == 123 70 | assert pg.label == "test" 71 | assert pg.region.id == "eu-west" 72 | assert pg.placement_group_type == "anti_affinity:local" 73 | assert pg.placement_group_policy == "strict" 74 | assert pg.is_compliant 75 | assert pg.members[0] == PlacementGroupMember( 76 | linode_id=123, is_compliant=True 77 | ) 78 | assert pg.migrations.inbound[0] == MigratedInstance(linode_id=123) 79 | assert pg.migrations.outbound[0] == MigratedInstance(linode_id=456) 80 | -------------------------------------------------------------------------------- /test/unit/objects/region_test.py: -------------------------------------------------------------------------------- 1 | from test.unit.base import ClientBaseCase 2 | 3 | from linode_api4.objects import Region 4 | 5 | 6 | class RegionTest(ClientBaseCase): 7 | """ 8 | Tests methods of the Region class 9 | """ 10 | 11 | def test_get_region(self): 12 | """ 13 | Tests that a Region is loaded correctly by ID 14 | """ 15 | region = Region(self.client, "us-east") 16 | 17 | self.assertEqual(region.id, "us-east") 18 | self.assertIsNotNone(region.capabilities) 19 | self.assertEqual(region.country, "us") 20 | self.assertEqual(region.label, "label7") 21 | self.assertEqual(region.status, "ok") 22 | self.assertIsNotNone(region.resolvers) 23 | self.assertEqual(region.site_type, "core") 24 | self.assertEqual( 25 | region.placement_group_limits.maximum_pgs_per_customer, 5 26 | ) 27 | self.assertEqual( 28 | region.placement_group_limits.maximum_linodes_per_pg, 5 29 | ) 30 | 31 | def test_region_availability(self): 32 | """ 33 | Tests that availability for a specific region can be listed and filtered on. 34 | """ 35 | avail_entries = Region(self.client, "us-east").availability 36 | 37 | for entry in avail_entries: 38 | assert entry.region is not None 39 | assert len(entry.region) > 0 40 | 41 | assert entry.plan is not None 42 | assert len(entry.plan) > 0 43 | 44 | assert entry.available is not None 45 | -------------------------------------------------------------------------------- /test/unit/objects/support_test.py: -------------------------------------------------------------------------------- 1 | from test.unit.base import ClientBaseCase 2 | 3 | from linode_api4.objects import SupportTicket 4 | 5 | 6 | class SupportTest(ClientBaseCase): 7 | """ 8 | Tests methods of the SupportTicket class 9 | """ 10 | 11 | def test_get_support_ticket(self): 12 | ticket = SupportTicket(self.client, 123) 13 | 14 | self.assertIsNotNone(ticket.attachments) 15 | self.assertFalse(ticket.closable) 16 | self.assertIsNotNone(ticket.entity) 17 | self.assertEqual(ticket.gravatar_id, "474a1b7373ae0be4132649e69c36ce30") 18 | self.assertEqual(ticket.id, 123) 19 | self.assertEqual(ticket.opened_by, "some_user") 20 | self.assertEqual(ticket.status, "open") 21 | self.assertEqual(ticket.updated_by, "some_other_user") 22 | 23 | def test_support_ticket_close(self): 24 | ticket = SupportTicket(self.client, 123) 25 | 26 | with self.mock_post({}) as m: 27 | ticket.support_ticket_close() 28 | self.assertEqual(m.call_url, "/support/tickets/123/close") 29 | -------------------------------------------------------------------------------- /test/unit/objects/tag_test.py: -------------------------------------------------------------------------------- 1 | from test.unit.base import ClientBaseCase 2 | 3 | from linode_api4.objects import Tag 4 | 5 | 6 | class TagTest(ClientBaseCase): 7 | """ 8 | Tests methods of the Tag class 9 | """ 10 | 11 | def test_get_tag(self): 12 | """ 13 | Tests that Tag is loaded correctly by label 14 | """ 15 | tag = Tag(self.client, "something") 16 | 17 | self.assertEqual(tag.label, "something") 18 | self.assertFalse(hasattr(tag, "_raw_objects")) 19 | 20 | def test_load_tag(self): 21 | """ 22 | Tests that the LinodeClient can load a tag 23 | """ 24 | tag = self.client.load(Tag, "something") 25 | 26 | self.assertEqual(tag.label, "something") 27 | self.assertTrue(hasattr(tag, "_raw_objects")) # got the raw objects 28 | print(tag._raw_objects) 29 | 30 | # objects loaded up right 31 | self.assertEqual(len(tag.objects), 1) 32 | self.assertEqual(tag.objects[0].id, 123) 33 | self.assertEqual(tag.objects[0].label, "linode123") 34 | self.assertEqual(tag.objects[0].tags, ["something"]) 35 | 36 | def test_delete_tag(self): 37 | """ 38 | Tests that you can delete a tag 39 | """ 40 | with self.mock_delete() as m: 41 | tag = Tag(self.client, "nothing") 42 | result = tag.delete() 43 | 44 | self.assertEqual(result, True) 45 | 46 | self.assertEqual(m.call_url, "/tags/nothing") 47 | -------------------------------------------------------------------------------- /test/unit/util_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from linode_api4.util import drop_null_keys 4 | 5 | 6 | class UtilTest(unittest.TestCase): 7 | """ 8 | Tests for utility functions. 9 | """ 10 | 11 | def test_drop_null_keys_nonrecursive(self): 12 | """ 13 | Tests whether a non-recursive drop_null_keys call works as expected. 14 | """ 15 | value = { 16 | "foo": "bar", 17 | "test": None, 18 | "cool": { 19 | "test": "bar", 20 | "cool": None, 21 | }, 22 | } 23 | 24 | expected_output = {"foo": "bar", "cool": {"test": "bar", "cool": None}} 25 | 26 | assert drop_null_keys(value, recursive=False) == expected_output 27 | 28 | def test_drop_null_keys_recursive(self): 29 | """ 30 | Tests whether a recursive drop_null_keys call works as expected. 31 | """ 32 | 33 | value = { 34 | "foo": "bar", 35 | "test": None, 36 | "cool": { 37 | "test": "bar", 38 | "cool": None, 39 | "list": [{"foo": "bar", "test": None}], 40 | }, 41 | } 42 | 43 | expected_output = { 44 | "foo": "bar", 45 | "cool": { 46 | "test": "bar", 47 | "list": [ 48 | { 49 | "foo": "bar", 50 | } 51 | ], 52 | }, 53 | } 54 | 55 | assert drop_null_keys(value) == expected_output 56 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py38,py39,py310,py311,py312 3 | skip_missing_interpreters = true 4 | 5 | [testenv] 6 | deps = 7 | pytest 8 | coverage 9 | mock 10 | pylint 11 | httpretty 12 | pytest-rerunfailures 13 | commands = 14 | python -m pip install . 15 | coverage run --source linode_api4 -m pytest test/unit 16 | coverage report 17 | pylint linode_api4 18 | --------------------------------------------------------------------------------