Merge "Adds ironic-k8s charm" into main
This commit is contained in:
1
charms/ironic-k8s/.gitignore
vendored
Normal file
1
charms/ironic-k8s/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
!lib/charms/ironic_k8s
|
21
charms/ironic-k8s/.sunbeam-build.yaml
Normal file
21
charms/ironic-k8s/.sunbeam-build.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
external-libraries:
|
||||
- charms.data_platform_libs.v0.data_interfaces
|
||||
- charms.rabbitmq_k8s.v0.rabbitmq
|
||||
- charms.traefik_k8s.v2.ingress
|
||||
- charms.traefik_route_k8s.v0.traefik_route
|
||||
- charms.certificate_transfer_interface.v0.certificate_transfer
|
||||
- charms.loki_k8s.v1.loki_push_api
|
||||
- charms.tempo_k8s.v2.tracing
|
||||
- charms.tempo_k8s.v1.charm_tracing
|
||||
internal-libraries:
|
||||
- charms.keystone_k8s.v1.identity_service
|
||||
- charms.sunbeam_libs.v0.service_readiness
|
||||
templates:
|
||||
- parts/section-database
|
||||
- parts/database-connection
|
||||
- parts/database-connection-settings
|
||||
- parts/section-identity
|
||||
- parts/identity-data
|
||||
- parts/section-oslo-messaging-rabbit
|
||||
- parts/section-service-user
|
||||
- ca-bundle.pem.j2
|
63
charms/ironic-k8s/CONTRIBUTING.md
Normal file
63
charms/ironic-k8s/CONTRIBUTING.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# ironic-k8s
|
||||
|
||||
## Developing
|
||||
|
||||
Create and activate a virtualenv with the development requirements:
|
||||
|
||||
virtualenv -p python3 venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
|
||||
## Code overview
|
||||
|
||||
Get familiarised with [Charmed Operator Framework](https://juju.is/docs/sdk)
|
||||
and [Sunbeam documentation](sunbeam-docs).
|
||||
|
||||
ironic-k8s charm uses the ops\_sunbeam library and extends
|
||||
OSBaseOperatorAPICharm from the library.
|
||||
|
||||
ironic-k8s charm consumes database relation to connect to database,
|
||||
identity-service to register the service endpoints to keystone
|
||||
and ingress-internal/ingress-public relation to get exposed over
|
||||
internal and public networks.
|
||||
|
||||
The charms starts the ironic-api and ironic-novncproxy services.
|
||||
|
||||
## Intended use case
|
||||
|
||||
ironic-k8s charm deploys and configures OpenStack Ironic - a bare metal
|
||||
provisioning service - on a kubernetes based environment.
|
||||
|
||||
## Roadmap
|
||||
|
||||
TODO
|
||||
|
||||
## Testing
|
||||
|
||||
The Python operator framework includes a very nice harness for testing
|
||||
operator behaviour without full deployment. Run tests using command:
|
||||
|
||||
tox --root ../../ -e py3 -- ironic-k8s
|
||||
|
||||
## Deployment
|
||||
|
||||
This project uses tox for building and managing. To build the charm
|
||||
run:
|
||||
|
||||
tox --root ../../ -e build -- ironic-k8s
|
||||
|
||||
To deploy the local test instance:
|
||||
|
||||
juju deploy ./ironic-k8s.charm ironic --trust \
|
||||
--resource ironic-api-image=ghcr.io/canonical/ironic-api:2025.1 \
|
||||
--resource ironic-novncproxy-image=ghcr.io/canonical/ironic-novncproxy:2025.1
|
||||
|
||||
To upgrade / refresh the ironic-k8s charm with a locally-built charm, use the
|
||||
following command:
|
||||
|
||||
juju refresh ironic --path ./ironic-k8s.charm
|
||||
|
||||
|
||||
<!-- LINKS -->
|
||||
|
||||
[sunbeam-docs]: https://opendev.org/openstack/sunbeam-charms/src/branch/main/README.md
|
202
charms/ironic-k8s/LICENSE
Normal file
202
charms/ironic-k8s/LICENSE
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
85
charms/ironic-k8s/README.md
Normal file
85
charms/ironic-k8s/README.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# ironic-k8s
|
||||
|
||||
## Description
|
||||
|
||||
ironic-k8s is an operator to manage the Ironic API and noVNC proxy
|
||||
services on a Kubernetes based environment.
|
||||
|
||||
## Usage
|
||||
|
||||
### Deployment
|
||||
|
||||
ironic-k8s is deployed using command below:
|
||||
|
||||
juju deploy ironic-k8s ironic --trust
|
||||
|
||||
For instructions on how to build the charm and deploy / refresh it, check out
|
||||
the [CONTRIBUTING.md][contributors-guide].
|
||||
|
||||
Now connect the ironic operator to existing database, messaging,
|
||||
keystone identity, and traefik operators:
|
||||
|
||||
juju deploy mysql-router-k8s ironic-mysql-router --trust
|
||||
juju relate ironic-mysql-router:backend-database mysql:database
|
||||
juju relate ironic-mysql-router:database ironic:database
|
||||
juju relate rabbitmq:amqp ironic:amqp
|
||||
juju relate keystone:identity-service ironic:identity-service
|
||||
juju relate traefik:ingress ironic:ingress-internal
|
||||
juju relate traefik:traefik-route ironic:traefik-route-internal
|
||||
juju relate traefik-public:traefik-route ironic:traefik-route-public
|
||||
|
||||
### Configuration
|
||||
|
||||
This section covers common and/or important configuration options. See file
|
||||
`config.yaml` for the full list of options, along with their descriptions and
|
||||
default values. See the [Juju documentation][juju-docs-config-apps] for details
|
||||
on configuring applications.
|
||||
|
||||
### Actions
|
||||
|
||||
This section covers Juju [actions][juju-docs-actions] supported by the charm.
|
||||
Actions allow specific operations to be performed on a per-unit basis. To
|
||||
display action descriptions run `juju actions ironic`. If the charm is not
|
||||
deployed then see file `actions.yaml`.
|
||||
|
||||
## Relations
|
||||
|
||||
ironic-k8s requires the following relations:
|
||||
|
||||
- `amqp`: To connect to RabbitMQ.
|
||||
- `database`: To connect to MySQL.
|
||||
- `identity-service`: To register endpoints in Keystone.
|
||||
- `ingress-internal`: To expose service on underlying internal network.
|
||||
- `traefik-route-internal`: To create an internal Traefik route for `ironic-novncproxy`.
|
||||
|
||||
The following relations are optional:
|
||||
|
||||
- `ingress-public`: To expose service on public network.
|
||||
- `logging`: To send logs to Loki.
|
||||
- `receive-ca-cert`: To enable TLS on the service endpoints.
|
||||
- `tracing`: To connect to a tracing backend.
|
||||
- `traefik-route-public`: To create a internal Traefik route for `ironic-novncproxy`.
|
||||
|
||||
## OCI Images
|
||||
|
||||
The charm by default uses following images:
|
||||
|
||||
- `ghcr.io/canonical/ironic-api:2025.1`
|
||||
- `ghcr.io/canonical/ironic-novncproxy:2025.1`
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see the [Juju SDK docs](https://juju.is/docs/sdk) for guidelines
|
||||
on enhancements to this charm following best practice guidelines, and
|
||||
[CONTRIBUTING.md][contributors-guide] for developer guidance.
|
||||
|
||||
## Bugs
|
||||
|
||||
Please report bugs on [Launchpad][lp-bugs-charm-ironic-k8s].
|
||||
|
||||
<!-- LINKS -->
|
||||
|
||||
[contributors-guide]: https://opendev.org/openstack/sunbeam-charms/src/branch/main/charms/ironic-k8s/CONTRIBUTING.md
|
||||
[juju-docs-actions]: https://jaas.ai/docs/actions
|
||||
[juju-docs-config-apps]: https://documentation.ubuntu.com/juju/3.6/reference/configuration/#application-configuration
|
||||
[lp-bugs-charm-ironic-k8s]: https://bugs.launchpad.net/sunbeam-charms/+filebug
|
119
charms/ironic-k8s/charmcraft.yaml
Normal file
119
charms/ironic-k8s/charmcraft.yaml
Normal file
@@ -0,0 +1,119 @@
|
||||
type: charm
|
||||
title: OpenStack Ironic service
|
||||
name: ironic-k8s
|
||||
summary: OpenStack bare metal provisioning service
|
||||
description: |
|
||||
ironic-k8s is a charm for the OpenStack Ironic service, a bare metal
|
||||
provisioning service.
|
||||
assumes:
|
||||
- k8s-api
|
||||
- juju >= 3.1
|
||||
links:
|
||||
source: https://opendev.org/openstack/sunbeam-charms
|
||||
issues: https://bugs.launchpad.net/sunbeam-charms
|
||||
|
||||
base: ubuntu@24.04
|
||||
platforms:
|
||||
amd64:
|
||||
|
||||
config:
|
||||
options:
|
||||
debug:
|
||||
default: false
|
||||
description: Enable debug logging
|
||||
type: boolean
|
||||
region:
|
||||
default: RegionOne
|
||||
description: Name of the OpenStack region
|
||||
type: string
|
||||
|
||||
containers:
|
||||
ironic-api:
|
||||
resource: ironic-api-image
|
||||
ironic-novncproxy:
|
||||
resource: ironic-novncproxy-image
|
||||
|
||||
resources:
|
||||
ironic-api-image:
|
||||
type: oci-image
|
||||
description: OCI image for OpenStack Ironic API
|
||||
upstream-source: ghcr.io/canonical/ironic-api:2025.1
|
||||
ironic-novncproxy-image:
|
||||
type: oci-image
|
||||
description: OCI image for OpenStack Ironic noVNC proxy
|
||||
upstream-source: ghcr.io/canonical/ironic-novncproxy:2025.1
|
||||
|
||||
requires:
|
||||
amqp:
|
||||
interface: rabbitmq
|
||||
limit: 1
|
||||
database:
|
||||
interface: mysql_client
|
||||
limit: 1
|
||||
identity-service:
|
||||
interface: keystone
|
||||
limit: 1
|
||||
ingress-internal:
|
||||
interface: ingress
|
||||
limit: 1
|
||||
ingress-public:
|
||||
interface: ingress
|
||||
optional: true
|
||||
limit: 1
|
||||
logging:
|
||||
interface: loki_push_api
|
||||
optional: true
|
||||
limit: 1
|
||||
receive-ca-cert:
|
||||
interface: certificate_transfer
|
||||
optional: true
|
||||
limit: 1
|
||||
tracing:
|
||||
interface: tracing
|
||||
optional: true
|
||||
limit: 1
|
||||
# For ironic-novncproxy
|
||||
traefik-route-internal:
|
||||
interface: traefik_route
|
||||
limit: 1
|
||||
# For ironic-novncproxy
|
||||
traefik-route-public:
|
||||
interface: traefik_route
|
||||
optional: true
|
||||
limit: 1
|
||||
|
||||
provides:
|
||||
ironic-api:
|
||||
interface: baremetal
|
||||
|
||||
peers:
|
||||
peers:
|
||||
interface: ironic-peer
|
||||
|
||||
parts:
|
||||
update-certificates:
|
||||
plugin: nil
|
||||
override-build: |
|
||||
apt update
|
||||
apt install -y ca-certificates
|
||||
update-ca-certificates
|
||||
charm:
|
||||
after:
|
||||
- update-certificates
|
||||
build-packages:
|
||||
- git
|
||||
- libffi-dev
|
||||
- libssl-dev
|
||||
- rustc-1.80
|
||||
- cargo-1.80
|
||||
- pkg-config
|
||||
charm-binary-python-packages:
|
||||
- cryptography
|
||||
- jsonschema
|
||||
- pydantic
|
||||
- jinja2
|
||||
build-snaps: [astral-uv]
|
||||
override-build: |
|
||||
uv export --frozen --no-hashes --format=requirements-txt -o requirements.txt
|
||||
craftctl default
|
||||
charm-requirements: [requirements.txt]
|
17
charms/ironic-k8s/pyproject.toml
Normal file
17
charms/ironic-k8s/pyproject.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
# Copyright 2025 Canonical Ltd.
|
||||
# See LICENSE file for licensing details.
|
||||
|
||||
[project]
|
||||
name = "ironic-k8s"
|
||||
version = "2025.1"
|
||||
requires-python = "~=3.12.0"
|
||||
|
||||
dependencies = [
|
||||
"jinja2",
|
||||
"jsonschema",
|
||||
"pydantic",
|
||||
"ops",
|
||||
"interface_tls_certificates@git+https://opendev.org/openstack/charm-ops-interface-tls-certificates",
|
||||
"tenacity", # From ops_sunbeam
|
||||
"opentelemetry-api~=1.21.0", # charm_tracing library -> opentelemetry-sdk requires 1.21.0
|
||||
]
|
3
charms/ironic-k8s/rebuild
Normal file
3
charms/ironic-k8s/rebuild
Normal file
@@ -0,0 +1,3 @@
|
||||
# This file is used to trigger a build.
|
||||
# Change uuid to trigger a new build.
|
||||
842dcec3-813e-41d1-8d6e-c0ea6b558a2b
|
411
charms/ironic-k8s/src/charm.py
Executable file
411
charms/ironic-k8s/src/charm.py
Executable file
@@ -0,0 +1,411 @@
|
||||
#!/usr/bin/env python3
|
||||
#
|
||||
# Copyright 2025 Canonical Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Ironic Operator Charm.
|
||||
|
||||
This charm provides Ironic API and noVNC Proxy services as part of an OpenStack
|
||||
deployment.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import socket
|
||||
from typing import (
|
||||
Dict,
|
||||
List,
|
||||
Mapping,
|
||||
)
|
||||
|
||||
import ops
|
||||
import ops_sunbeam.charm as sunbeam_charm
|
||||
import ops_sunbeam.container_handlers as sunbeam_chandlers
|
||||
import ops_sunbeam.core as sunbeam_core
|
||||
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
|
||||
import ops_sunbeam.tracing as sunbeam_tracing
|
||||
from ops.charm import (
|
||||
RelationEvent,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
IRONIC_API_PORT = 6385
|
||||
IRONIC_API_CONTAINER = "ironic-api"
|
||||
IRONIC_NOVNCPROXY_CONTAINER = "ironic-novncproxy"
|
||||
IRONIC_NOVNCPROXY_INGRESS_NAME = "ironic-novncproxy"
|
||||
IRONIC_API_PROVIDES = "ironic-api"
|
||||
|
||||
|
||||
@sunbeam_tracing.trace_type
|
||||
class IronicNoVNCProxyPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
|
||||
"""Pebble handler for Ironic noVNC Proxy."""
|
||||
|
||||
def get_layer(self) -> dict:
|
||||
"""Ironic noVNC Proxy service layer.
|
||||
|
||||
:returns: pebble layer configuration for the ironic-novncproxy service
|
||||
:rtype: dict
|
||||
"""
|
||||
return {
|
||||
"summary": "ironic novncproxy layer",
|
||||
"description": "pebble configuration for ironic-novncproxy service",
|
||||
"services": {
|
||||
"ironic-novncproxy": {
|
||||
"override": "replace",
|
||||
"summary": "Ironic noVNC Proxy",
|
||||
"command": "ironic-novncproxy",
|
||||
"user": "ironic",
|
||||
"group": "ironic",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
def default_container_configs(
|
||||
self,
|
||||
) -> List[sunbeam_core.ContainerConfigFile]:
|
||||
"""Container configuration files for handler."""
|
||||
_cconfigs = [
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/ironic/ironic.conf",
|
||||
"root",
|
||||
"ironic",
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/ironic/rootwrap.conf",
|
||||
"root",
|
||||
"ironic",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
return _cconfigs
|
||||
|
||||
|
||||
@sunbeam_tracing.trace_sunbeam_charm
|
||||
class IronicOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
|
||||
"""Charm the service."""
|
||||
|
||||
service_name = "ironic-api"
|
||||
wsgi_admin_script = "/usr/bin/ironic-api-wsgi"
|
||||
wsgi_public_script = "/usr/bin/ironic-api-wsgi"
|
||||
|
||||
db_sync_cmds = [
|
||||
[
|
||||
"sudo",
|
||||
"-u",
|
||||
"ironic",
|
||||
"ironic-dbsync",
|
||||
],
|
||||
]
|
||||
|
||||
def __init__(self, framework):
|
||||
super().__init__(framework)
|
||||
self.traefik_route_public = None
|
||||
self.traefik_route_internal = None
|
||||
self.framework.observe(
|
||||
self.on["peers"].relation_created, self._on_peer_relation_created
|
||||
)
|
||||
self.framework.observe(
|
||||
self.on["peers"].relation_departed, self._on_peer_relation_departed
|
||||
)
|
||||
|
||||
@property
|
||||
def service_conf(self) -> str:
|
||||
"""Service default configuration file."""
|
||||
return "/etc/ironic/ironic.conf"
|
||||
|
||||
@property
|
||||
def service_user(self) -> str:
|
||||
"""Service user file and directory ownership."""
|
||||
return "ironic"
|
||||
|
||||
@property
|
||||
def service_group(self) -> str:
|
||||
"""Service group file and directory ownership."""
|
||||
return "ironic"
|
||||
|
||||
@property
|
||||
def service_endpoints(self) -> List[Dict]:
|
||||
"""Service endpoints for the Ironic API services."""
|
||||
return [
|
||||
{
|
||||
"service_name": "ironic",
|
||||
"type": "baremetal",
|
||||
"description": "OpenStack Ironic bare metal provisioner",
|
||||
"internal_url": self.internal_url,
|
||||
"public_url": self.public_url,
|
||||
"admin_url": self.admin_url,
|
||||
},
|
||||
]
|
||||
|
||||
@property
|
||||
def default_public_ingress_port(self):
|
||||
"""Public ingress port for service."""
|
||||
return IRONIC_API_PORT
|
||||
|
||||
@property
|
||||
def ingress_healthcheck_path(self):
|
||||
"""Healthcheck path for ingress relation."""
|
||||
return "/healthcheck"
|
||||
|
||||
@property
|
||||
def healthcheck_http_url(self) -> str:
|
||||
"""Healthcheck HTTP URL for the service."""
|
||||
return (
|
||||
f"http://localhost:{self.default_public_ingress_port}/healthcheck"
|
||||
)
|
||||
|
||||
@property
|
||||
def databases(self) -> Mapping[str, str]:
|
||||
"""Provide database name for ironic services."""
|
||||
return {"database": "ironic"}
|
||||
|
||||
def get_pebble_handlers(
|
||||
self,
|
||||
) -> List[sunbeam_chandlers.ServicePebbleHandler]:
|
||||
"""Pebble handlers for the operator."""
|
||||
pebble_handlers = [
|
||||
sunbeam_chandlers.WSGIPebbleHandler(
|
||||
self,
|
||||
IRONIC_API_CONTAINER,
|
||||
self.service_name,
|
||||
self.container_configs,
|
||||
self.template_dir,
|
||||
self.configure_charm,
|
||||
f"wsgi-{self.service_name}",
|
||||
),
|
||||
IronicNoVNCProxyPebbleHandler(
|
||||
self,
|
||||
IRONIC_NOVNCPROXY_CONTAINER,
|
||||
IRONIC_NOVNCPROXY_CONTAINER,
|
||||
[],
|
||||
self.template_dir,
|
||||
self.configure_charm,
|
||||
),
|
||||
]
|
||||
return pebble_handlers
|
||||
|
||||
def post_config_setup(self):
|
||||
"""Configuration steps after services have been setup."""
|
||||
super().post_config_setup()
|
||||
self.set_readiness_on_related_units()
|
||||
|
||||
def handle_readiness_request_from_event(
|
||||
self, event: RelationEvent
|
||||
) -> None:
|
||||
"""Set service readiness in relation data."""
|
||||
self.svc_ready_handler.interface.set_service_status(
|
||||
event.relation, self.bootstrapped()
|
||||
)
|
||||
|
||||
def set_readiness_on_related_units(self) -> None:
|
||||
"""Set service readiness on ironic-api related units."""
|
||||
logger.debug(
|
||||
"Set service readiness on all connected placement relations"
|
||||
)
|
||||
for relation in self.framework.model.relations[IRONIC_API_PROVIDES]:
|
||||
self.svc_ready_handler.interface.set_service_status(relation, True)
|
||||
|
||||
@property
|
||||
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
|
||||
"""Container configuration files for the service."""
|
||||
_cconfigs = [
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/ironic/api_audit_map.conf",
|
||||
"root",
|
||||
"ironic",
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/ironic/ironic.conf",
|
||||
"root",
|
||||
"ironic",
|
||||
0o640,
|
||||
),
|
||||
sunbeam_core.ContainerConfigFile(
|
||||
"/etc/ironic/rootwrap.conf",
|
||||
"root",
|
||||
"ironic",
|
||||
0o640,
|
||||
),
|
||||
]
|
||||
return _cconfigs
|
||||
|
||||
@property
|
||||
def db_sync_container_name(self) -> str:
|
||||
"""Name of Container to run db sync from."""
|
||||
return IRONIC_API_CONTAINER
|
||||
|
||||
def get_relation_handlers(
|
||||
self, handlers: List[sunbeam_rhandlers.RelationHandler] = None
|
||||
) -> List[sunbeam_rhandlers.RelationHandler]:
|
||||
"""Relation handlers for operator."""
|
||||
handlers = super().get_relation_handlers(handlers or [])
|
||||
|
||||
self.svc_ready_handler = (
|
||||
sunbeam_rhandlers.ServiceReadinessProviderHandler(
|
||||
self,
|
||||
IRONIC_API_PROVIDES,
|
||||
self.handle_readiness_request_from_event,
|
||||
)
|
||||
)
|
||||
handlers.append(self.svc_ready_handler)
|
||||
|
||||
self.traefik_route_public = sunbeam_rhandlers.TraefikRouteHandler(
|
||||
self,
|
||||
"traefik-route-public",
|
||||
self.handle_traefik_public_ready,
|
||||
"traefik-route-public" in self.mandatory_relations,
|
||||
[IRONIC_NOVNCPROXY_INGRESS_NAME],
|
||||
)
|
||||
handlers.append(self.traefik_route_public)
|
||||
|
||||
self.traefik_route_internal = sunbeam_rhandlers.TraefikRouteHandler(
|
||||
self,
|
||||
"traefik-route-internal",
|
||||
self.handle_traefik_internal_ready,
|
||||
"traefik-route-internal" in self.mandatory_relations,
|
||||
[IRONIC_NOVNCPROXY_INGRESS_NAME],
|
||||
)
|
||||
handlers.append(self.traefik_route_internal)
|
||||
|
||||
return handlers
|
||||
|
||||
def _on_peer_relation_created(
|
||||
self, event: ops.framework.EventBase
|
||||
) -> None:
|
||||
logger.info("Setting peer unit data")
|
||||
self.peers.set_unit_data({"host": socket.getfqdn()})
|
||||
|
||||
def _on_peer_relation_departed(
|
||||
self, event: ops.framework.EventBase
|
||||
) -> None:
|
||||
self.handle_traefik_ready(event)
|
||||
|
||||
def handle_traefik_ready(self, event: ops.EventBase):
|
||||
"""Handle Traefik route ready callback."""
|
||||
self.handle_traefik_public_ready(event)
|
||||
self.handle_traefik_internal_ready(event)
|
||||
|
||||
def handle_traefik_public_ready(self, event: ops.EventBase):
|
||||
"""Handle Traefik public route ready callback."""
|
||||
if not self.unit.is_leader():
|
||||
logger.debug(
|
||||
"Not a leader unit, skipping traefik public route config"
|
||||
)
|
||||
return
|
||||
|
||||
traefik = self.traefik_route_public
|
||||
if traefik and traefik.interface.is_ready():
|
||||
logger.debug("Sending traefik config for public interface")
|
||||
traefik.interface.submit_to_traefik(config=self.traefik_config)
|
||||
|
||||
def handle_traefik_internal_ready(self, event: ops.EventBase):
|
||||
"""Handle Traefik internal route ready callback."""
|
||||
if not self.unit.is_leader():
|
||||
logger.debug(
|
||||
"Not a leader unit, skipping traefik internal route config"
|
||||
)
|
||||
return
|
||||
|
||||
traefik = self.traefik_route_internal
|
||||
if traefik and traefik.interface.is_ready():
|
||||
logger.debug("Sending traefik config for internal interface")
|
||||
traefik.interface.submit_to_traefik(config=self.traefik_config)
|
||||
|
||||
# traefik-route-internal is a mandatory relation. If this is the
|
||||
# last relation added, run configure_charm to potentially kick it
|
||||
# from the Waiting status.
|
||||
self.configure_charm(event)
|
||||
|
||||
@property
|
||||
def traefik_config(self) -> dict:
|
||||
"""Config to publish to traefik."""
|
||||
model = self.model.name
|
||||
router_cfg = {}
|
||||
# Add router for ironic-novncproxy
|
||||
# Rename router tls and add priority as workaround for
|
||||
# bug https://github.com/canonical/traefik-k8s-operator/issues/335
|
||||
router_cfg.update(
|
||||
{
|
||||
f"juju-{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}-router": {
|
||||
"rule": f"PathPrefix(`/{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}`)",
|
||||
"middlewares": [
|
||||
"custom-stripprefix",
|
||||
"custom-wsheaders-http",
|
||||
],
|
||||
"service": f"juju-{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}-service",
|
||||
"entryPoints": ["web"],
|
||||
},
|
||||
f"juju-{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}-router-https": {
|
||||
"rule": f"PathPrefix(`/{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}`)",
|
||||
"middlewares": [
|
||||
"custom-stripprefix",
|
||||
"custom-wsheaders-https",
|
||||
],
|
||||
"service": f"juju-{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}-service",
|
||||
"entryPoints": ["websecure"],
|
||||
"tls": {},
|
||||
"priority": 100,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
# Add middlewares to ironic-novncproxy
|
||||
middleware_cfg = {
|
||||
"custom-stripprefix": {
|
||||
"stripPrefix": {
|
||||
"prefixes": [f"/{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}"],
|
||||
"forceSlash": False,
|
||||
}
|
||||
},
|
||||
"custom-wsheaders-http": {
|
||||
"headers": {
|
||||
"customRequestHeaders": {"X-Forwarded-Proto": "http"}
|
||||
}
|
||||
},
|
||||
"custom-wsheaders-https": {
|
||||
"headers": {
|
||||
"customRequestHeaders": {"X-Forwarded-Proto": "https"}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
# Get host key value from all units
|
||||
hosts = self.peers.get_all_unit_values(
|
||||
key="host", include_local_unit=True
|
||||
)
|
||||
novnc_lb_servers = [
|
||||
{"url": f"http://{host}:{IRONIC_NOVNCPROXY_INGRESS_NAME}"}
|
||||
for host in hosts
|
||||
]
|
||||
# Add services for heat-api and heat-api-cfn
|
||||
service_cfg = {
|
||||
f"juju-{model}-{IRONIC_NOVNCPROXY_INGRESS_NAME}-service": {
|
||||
"loadBalancer": {"servers": novnc_lb_servers},
|
||||
},
|
||||
}
|
||||
|
||||
config = {
|
||||
"http": {
|
||||
"routers": router_cfg,
|
||||
"middlewares": middleware_cfg,
|
||||
"services": service_cfg,
|
||||
},
|
||||
}
|
||||
return config
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: nocover
|
||||
ops.main(IronicOperatorCharm)
|
29
charms/ironic-k8s/src/templates/api_audit_map.conf
Normal file
29
charms/ironic-k8s/src/templates/api_audit_map.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
[DEFAULT]
|
||||
# default target endpoint type
|
||||
# should match the endpoint type defined in service catalog
|
||||
target_endpoint_type = None
|
||||
|
||||
# possible end path of API requests
|
||||
# path of api requests for CADF target typeURI
|
||||
# Just need to include top resource path to identify class
|
||||
# of resources. Ex: Log audit event for API requests
|
||||
# path containing "nodes" keyword and node uuid.
|
||||
[path_keywords]
|
||||
nodes = node
|
||||
drivers = driver
|
||||
chassis = chassis
|
||||
ports = port
|
||||
states = state
|
||||
power = None
|
||||
provision = None
|
||||
maintenance = None
|
||||
validate = None
|
||||
boot_device = None
|
||||
supported = None
|
||||
console = None
|
||||
vendor_passthru = vendor_passthru
|
||||
|
||||
|
||||
# map endpoint type defined in service catalog to CADF typeURI
|
||||
[service_endpoints]
|
||||
baremetal = service/compute/baremetal
|
31
charms/ironic-k8s/src/templates/ironic.conf.j2
Normal file
31
charms/ironic-k8s/src/templates/ironic.conf.j2
Normal file
@@ -0,0 +1,31 @@
|
||||
##############################################
|
||||
# [ WARNING ]
|
||||
# ironic configuration file maintained by Juju
|
||||
# local changes may be overwritten.
|
||||
##############################################
|
||||
|
||||
[DEFAULT]
|
||||
debug = {{ options.debug }}
|
||||
transport_url = {{ amqp.transport_url }}
|
||||
auth_strategy = keystone
|
||||
|
||||
{% include "parts/section-database" %}
|
||||
|
||||
{% include "parts/section-identity" %}
|
||||
|
||||
{% include "parts/section-service-user" %}
|
||||
|
||||
{% include "parts/section-oslo-messaging-rabbit" %}
|
||||
|
||||
[oslo_concurrency]
|
||||
lock_path = /var/lib/ironic/tmp
|
||||
|
||||
[audit]
|
||||
enabled = true
|
||||
audit_map_file = /etc/ironic/api_audit_map.conf
|
||||
|
||||
[healthcheck]
|
||||
enabled = true
|
||||
|
||||
[vnc]
|
||||
enabled = true
|
27
charms/ironic-k8s/src/templates/rootwrap.conf
Normal file
27
charms/ironic-k8s/src/templates/rootwrap.conf
Normal file
@@ -0,0 +1,27 @@
|
||||
# Configuration for ironic-rootwrap
|
||||
# This file should be owned by (and only writable by) the root user
|
||||
|
||||
[DEFAULT]
|
||||
# List of directories to load filter definitions from (separated by ',').
|
||||
# These directories MUST all be only writable by root !
|
||||
filters_path=/etc/ironic/rootwrap.d
|
||||
|
||||
# List of directories to search executables in, in case filters do not
|
||||
# explicitly specify a full path (separated by ',')
|
||||
# If not specified, defaults to system PATH environment variable.
|
||||
# These directories MUST all be only writable by root !
|
||||
exec_dirs=/sbin,/usr/sbin,/bin,/usr/bin
|
||||
|
||||
# Enable logging to syslog
|
||||
# Default value is False
|
||||
use_syslog=False
|
||||
|
||||
# Which syslog facility to use.
|
||||
# Valid values include auth, authpriv, syslog, user0, user1...
|
||||
# Default value is 'syslog'
|
||||
syslog_log_facility=syslog
|
||||
|
||||
# Which messages to log.
|
||||
# INFO means log all usage
|
||||
# ERROR means only log unsuccessful attempts
|
||||
syslog_log_level=ERROR
|
28
charms/ironic-k8s/src/templates/wsgi-ironic-api.conf.j2
Normal file
28
charms/ironic-k8s/src/templates/wsgi-ironic-api.conf.j2
Normal file
@@ -0,0 +1,28 @@
|
||||
Listen 6385
|
||||
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %D(us)" ironic_combined
|
||||
|
||||
<VirtualHost *:6385>
|
||||
WSGIDaemonProcess ironic processes=2 threads=1 user=ironic group=ironic display-name=%{GROUP}
|
||||
WSGIProcessGroup ironic
|
||||
{% if ingress_internal and ingress_internal.ingress_path -%}
|
||||
WSGIScriptAlias {{ ingress_internal.ingress_path }} {{ wsgi_config.wsgi_public_script }}
|
||||
{% endif -%}
|
||||
WSGIScriptAlias / {{ wsgi_config.wsgi_public_script }}
|
||||
WSGIApplicationGroup %{GLOBAL}
|
||||
WSGIPassAuthorization On
|
||||
<IfVersion >= 2.4>
|
||||
ErrorLogFormat "%{cu}t %M"
|
||||
</IfVersion>
|
||||
ErrorLog {{ wsgi_config.error_log }}
|
||||
CustomLog {{ wsgi_config.custom_log }} combined
|
||||
|
||||
<Directory /usr/bin>
|
||||
<IfVersion >= 2.4>
|
||||
Require all granted
|
||||
</IfVersion>
|
||||
<IfVersion < 2.4>
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</IfVersion>
|
||||
</Directory>
|
||||
</VirtualHost>
|
16
charms/ironic-k8s/tests/unit/__init__.py
Normal file
16
charms/ironic-k8s/tests/unit/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#
|
||||
# Copyright 2025 Canonical Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Unit tests for Ironic K8S Operator."""
|
154
charms/ironic-k8s/tests/unit/test_charm.py
Normal file
154
charms/ironic-k8s/tests/unit/test_charm.py
Normal file
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright 2025 Canonical Ltd.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Unit tests for the Ironic K8s charm."""
|
||||
|
||||
import charm
|
||||
import ops_sunbeam.test_utils as test_utils
|
||||
from ops.testing import (
|
||||
Harness,
|
||||
)
|
||||
|
||||
|
||||
class _IronicOperatorCharm(charm.IronicOperatorCharm):
|
||||
"""Test implementation of Ironic operator."""
|
||||
|
||||
def __init__(self, framework):
|
||||
self.seen_events = []
|
||||
self.render_calls = []
|
||||
super().__init__(framework)
|
||||
|
||||
def _log_event(self, event):
|
||||
self.seen_events.append(type(event).__name__)
|
||||
|
||||
def renderer(self, containers, container_configs, template_dir, adapters):
|
||||
"""Intercept and record all calls to render config files."""
|
||||
self.render_calls.append(
|
||||
(containers, container_configs, template_dir, adapters)
|
||||
)
|
||||
|
||||
def configure_charm(self, event):
|
||||
"""Intercept and record full charm configuration events."""
|
||||
super().configure_charm(event)
|
||||
self._log_event(event)
|
||||
|
||||
@property
|
||||
def public_ingress_address(self):
|
||||
return "ironic.juju"
|
||||
|
||||
|
||||
class TestIronicOperatorCharm(test_utils.CharmTestCase):
|
||||
"""Unit tests for Ironic Operator."""
|
||||
|
||||
PATCHES = []
|
||||
|
||||
def setUp(self):
|
||||
"""Setup test fixtures for test."""
|
||||
super().setUp(charm, self.PATCHES)
|
||||
self.harness = test_utils.get_harness(
|
||||
_IronicOperatorCharm, container_calls=self.container_calls
|
||||
)
|
||||
|
||||
# clean up events that were dynamically defined,
|
||||
# otherwise we get issues because they'll be redefined,
|
||||
# which is not allowed.
|
||||
from charms.data_platform_libs.v0.data_interfaces import (
|
||||
DatabaseRequiresEvents,
|
||||
)
|
||||
|
||||
for attr in (
|
||||
"database_database_created",
|
||||
"database_endpoints_changed",
|
||||
"database_read_only_endpoints_changed",
|
||||
):
|
||||
try:
|
||||
delattr(DatabaseRequiresEvents, attr)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
self.addCleanup(self.harness.cleanup)
|
||||
self.harness.begin()
|
||||
|
||||
def add_ironic_api_relation(self):
|
||||
"""Add ironic-api relation."""
|
||||
return self.harness.add_relation(charm.IRONIC_API_PROVIDES, "consumer")
|
||||
|
||||
def add_db_relation(self, harness: Harness, name: str) -> str:
|
||||
"""Add db relation."""
|
||||
rel_id = harness.add_relation(name, "mysql")
|
||||
harness.add_relation_unit(rel_id, "mysql/0")
|
||||
harness.update_relation_data(
|
||||
rel_id, "mysql/0", {"ingress-address": "10.0.0.3"}
|
||||
)
|
||||
return rel_id
|
||||
|
||||
def add_complete_ingress_relation(self) -> None:
|
||||
"""Add complete ingress and traefik-route relations."""
|
||||
test_utils.add_complete_ingress_relation(self.harness)
|
||||
self.harness.add_relation(
|
||||
"traefik-route-public",
|
||||
"traefik",
|
||||
app_data={"external_host": "dummy-ip", "scheme": "http"},
|
||||
)
|
||||
self.harness.add_relation(
|
||||
"traefik-route-internal",
|
||||
"ironic",
|
||||
app_data={"external_host": "dummy-ip", "scheme": "http"},
|
||||
)
|
||||
|
||||
def test_pebble_ready_handler(self):
|
||||
"""Test pebble ready event handling."""
|
||||
self.assertEqual(self.harness.charm.seen_events, [])
|
||||
test_utils.set_all_pebbles_ready(self.harness)
|
||||
self.assertEqual(len(self.harness.charm.seen_events), 2)
|
||||
|
||||
def test_all_relations(self):
|
||||
"""Test all integrations for operator."""
|
||||
self.harness.set_leader()
|
||||
test_utils.set_all_pebbles_ready(self.harness)
|
||||
self.add_ironic_api_relation()
|
||||
|
||||
ironic_api_rel = self.harness.model.get_relation("ironic-api")
|
||||
rel_data = ironic_api_rel.data[self.harness.model.app]
|
||||
self.assertEqual({}, rel_data)
|
||||
|
||||
# this adds all the default/common relations
|
||||
test_utils.add_all_relations(self.harness)
|
||||
self.add_complete_ingress_relation()
|
||||
|
||||
setup_cmds = [
|
||||
["a2ensite", "wsgi-ironic-api"],
|
||||
]
|
||||
for cmd in setup_cmds:
|
||||
self.assertIn(cmd, self.container_calls.execute["ironic-api"])
|
||||
|
||||
config_files = [
|
||||
"/etc/apache2/sites-available/wsgi-ironic-api.conf",
|
||||
"/etc/ironic/api_audit_map.conf",
|
||||
"/etc/ironic/ironic.conf",
|
||||
"/etc/ironic/rootwrap.conf",
|
||||
]
|
||||
for f in config_files:
|
||||
self.check_file("ironic-api", f)
|
||||
|
||||
config_files = [
|
||||
"/etc/ironic/ironic.conf",
|
||||
"/etc/ironic/rootwrap.conf",
|
||||
]
|
||||
for f in config_files:
|
||||
self.check_file("ironic-novncproxy", f)
|
||||
|
||||
self.assertEqual({"ready": "true"}, rel_data)
|
323
charms/ironic-k8s/uv.lock
generated
Normal file
323
charms/ironic-k8s/uv.lock
generated
Normal file
@@ -0,0 +1,323 @@
|
||||
version = 1
|
||||
revision = 3
|
||||
requires-python = "==3.12.*"
|
||||
|
||||
[[package]]
|
||||
name = "annotated-types"
|
||||
version = "0.7.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "attrs"
|
||||
version = "25.3.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032, upload-time = "2025-03-13T11:10:22.779Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815, upload-time = "2025-03-13T11:10:21.14Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deprecated"
|
||||
version = "1.2.18"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wrapt" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/98/97/06afe62762c9a8a86af0cfb7bfdab22a43ad17138b07af5b1a58442690a2/deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d", size = 2928744, upload-time = "2025-01-27T10:46:25.7Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/c6/ac0b6c1e2d138f1002bcf799d330bd6d85084fece321e662a14223794041/Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec", size = 9998, upload-time = "2025-01-27T10:46:09.186Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "6.11.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "zipp" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ee/eb/58c2ab27ee628ad801f56d4017fe62afab0293116f6d0b08f1d5bd46e06f/importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443", size = 54593, upload-time = "2023-12-03T17:33:10.693Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/9b/ecce94952ab5ea74c31dcf9ccf78ccd484eebebef06019bf8cb579ab4519/importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b", size = 23427, upload-time = "2023-12-03T17:33:08.965Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "interface-tls-certificates"
|
||||
version = "0.0.1.dev1"
|
||||
source = { git = "https://opendev.org/openstack/charm-ops-interface-tls-certificates#f720af03a1acb640ab1fed86fd30ff9aafda1ea4" }
|
||||
dependencies = [
|
||||
{ name = "ops" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ironic-k8s"
|
||||
version = "2025.1"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "interface-tls-certificates" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "ops" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "tenacity" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "interface-tls-certificates", git = "https://opendev.org/openstack/charm-ops-interface-tls-certificates" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "jsonschema" },
|
||||
{ name = "opentelemetry-api", specifier = "~=1.21.0" },
|
||||
{ name = "ops" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "tenacity" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema"
|
||||
version = "4.23.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "jsonschema-specifications" },
|
||||
{ name = "referencing" },
|
||||
{ name = "rpds-py" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778, upload-time = "2024-07-08T18:40:05.546Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462, upload-time = "2024-07-08T18:40:00.165Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jsonschema-specifications"
|
||||
version = "2025.4.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "referencing" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/ce/46fbd9c8119cfc3581ee5643ea49464d168028cfb5caff5fc0596d0cf914/jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608", size = 15513, upload-time = "2025-04-23T12:34:07.418Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/01/0e/b27cdbaccf30b890c40ed1da9fd4a3593a5cf94dae54fb34f8a4b74fcd3f/jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af", size = 18437, upload-time = "2025-04-23T12:34:05.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537, upload-time = "2024-10-18T15:21:54.129Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274, upload-time = "2024-10-18T15:21:13.777Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348, upload-time = "2024-10-18T15:21:14.822Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149, upload-time = "2024-10-18T15:21:15.642Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118, upload-time = "2024-10-18T15:21:17.133Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993, upload-time = "2024-10-18T15:21:18.064Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178, upload-time = "2024-10-18T15:21:18.859Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319, upload-time = "2024-10-18T15:21:19.671Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352, upload-time = "2024-10-18T15:21:20.971Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097, upload-time = "2024-10-18T15:21:22.646Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601, upload-time = "2024-10-18T15:21:23.499Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opentelemetry-api"
|
||||
version = "1.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "deprecated" },
|
||||
{ name = "importlib-metadata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4d/aa/1a10f310275fdd05a1062d4a8a641a5f041db2377956a80ff3c4dc325a6c/opentelemetry_api-1.21.0.tar.gz", hash = "sha256:d6185fd5043e000075d921822fd2d26b953eba8ca21b1e2fa360dd46a7686316", size = 56674, upload-time = "2023-11-07T23:16:23.155Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/51/3a/945e6c21f405ac4ea526f91ee09cc1568c04e0c95d3392903e6984c8f0e0/opentelemetry_api-1.21.0-py3-none-any.whl", hash = "sha256:4bb86b28627b7e41098f0e93280fe4892a1abed1b79a19aec6f928f39b17dffb", size = 57947, upload-time = "2023-11-07T23:15:46.656Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ops"
|
||||
version = "2.21.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "importlib-metadata" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "websocket-client" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/97/c5/f0098a9b1b72680b3682043227a628a08a7b5b9592fc98ea6efa0d638017/ops-2.21.1.tar.gz", hash = "sha256:4a8190420813ba37e7a0399d656008f99c79015d7f72e138bad7cb1ac403d0b0", size = 496427, upload-time = "2025-05-01T03:03:23.038Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e6/c7/b70271ee12418144d5c596f37745c21da105470d365d834a9fce071f7bc2/ops-2.21.1-py3-none-any.whl", hash = "sha256:6745084c8e73bc485c254f95664bd85ddcf786c91b90782f2830c8333a1440b2", size = 182682, upload-time = "2025-05-01T03:03:20.946Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic"
|
||||
version = "2.11.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "annotated-types" },
|
||||
{ name = "pydantic-core" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "typing-inspection" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/77/ab/5250d56ad03884ab5efd07f734203943c8a8ab40d551e208af81d0257bf2/pydantic-2.11.4.tar.gz", hash = "sha256:32738d19d63a226a52eed76645a98ee07c1f410ee41d93b4afbfa85ed8111c2d", size = 786540, upload-time = "2025-04-29T20:38:55.02Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/12/46b65f3534d099349e38ef6ec98b1a5a81f42536d17e0ba382c28c67ba67/pydantic-2.11.4-py3-none-any.whl", hash = "sha256:d9615eaa9ac5a063471da949c8fc16376a84afb5024688b3ff885693506764eb", size = 443900, upload-time = "2025-04-29T20:38:52.724Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pydantic-core"
|
||||
version = "2.33.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ad/88/5f2260bdfae97aabf98f1778d43f69574390ad787afb646292a638c923d4/pydantic_core-2.33.2.tar.gz", hash = "sha256:7cb8bc3605c29176e1b105350d2e6474142d7c1bd1d9327c4a9bdb46bf827acc", size = 435195, upload-time = "2025-04-23T18:33:52.104Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/18/8a/2b41c97f554ec8c71f2a8a5f85cb56a8b0956addfe8b0efb5b3d77e8bdc3/pydantic_core-2.33.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a7ec89dc587667f22b6a0b6579c249fca9026ce7c333fc142ba42411fa243cdc", size = 2009000, upload-time = "2025-04-23T18:31:25.863Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/02/6224312aacb3c8ecbaa959897af57181fb6cf3a3d7917fd44d0f2917e6f2/pydantic_core-2.33.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3c6db6e52c6d70aa0d00d45cdb9b40f0433b96380071ea80b09277dba021ddf7", size = 1847996, upload-time = "2025-04-23T18:31:27.341Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d6/46/6dcdf084a523dbe0a0be59d054734b86a981726f221f4562aed313dbcb49/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e61206137cbc65e6d5256e1166f88331d3b6238e082d9f74613b9b765fb9025", size = 1880957, upload-time = "2025-04-23T18:31:28.956Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/6b/1ec2c03837ac00886ba8160ce041ce4e325b41d06a034adbef11339ae422/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb8c529b2819c37140eb51b914153063d27ed88e3bdc31b71198a198e921e011", size = 1964199, upload-time = "2025-04-23T18:31:31.025Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/1d/6bf34d6adb9debd9136bd197ca72642203ce9aaaa85cfcbfcf20f9696e83/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c52b02ad8b4e2cf14ca7b3d918f3eb0ee91e63b3167c32591e57c4317e134f8f", size = 2120296, upload-time = "2025-04-23T18:31:32.514Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/e0/94/2bd0aaf5a591e974b32a9f7123f16637776c304471a0ab33cf263cf5591a/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:96081f1605125ba0855dfda83f6f3df5ec90c61195421ba72223de35ccfb2f88", size = 2676109, upload-time = "2025-04-23T18:31:33.958Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/f9/41/4b043778cf9c4285d59742281a769eac371b9e47e35f98ad321349cc5d61/pydantic_core-2.33.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f57a69461af2a5fa6e6bbd7a5f60d3b7e6cebb687f55106933188e79ad155c1", size = 2002028, upload-time = "2025-04-23T18:31:39.095Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/cb/d5/7bb781bf2748ce3d03af04d5c969fa1308880e1dca35a9bd94e1a96a922e/pydantic_core-2.33.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:572c7e6c8bb4774d2ac88929e3d1f12bc45714ae5ee6d9a788a9fb35e60bb04b", size = 2100044, upload-time = "2025-04-23T18:31:41.034Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/36/def5e53e1eb0ad896785702a5bbfd25eed546cdcf4087ad285021a90ed53/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:db4b41f9bd95fbe5acd76d89920336ba96f03e149097365afe1cb092fceb89a1", size = 2058881, upload-time = "2025-04-23T18:31:42.757Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/01/6c/57f8d70b2ee57fc3dc8b9610315949837fa8c11d86927b9bb044f8705419/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:fa854f5cf7e33842a892e5c73f45327760bc7bc516339fda888c75ae60edaeb6", size = 2227034, upload-time = "2025-04-23T18:31:44.304Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/27/b9/9c17f0396a82b3d5cbea4c24d742083422639e7bb1d5bf600e12cb176a13/pydantic_core-2.33.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5f483cfb75ff703095c59e365360cb73e00185e01aaea067cd19acffd2ab20ea", size = 2234187, upload-time = "2025-04-23T18:31:45.891Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/6a/adf5734ffd52bf86d865093ad70b2ce543415e0e356f6cacabbc0d9ad910/pydantic_core-2.33.2-cp312-cp312-win32.whl", hash = "sha256:9cb1da0f5a471435a7bc7e439b8a728e8b61e59784b2af70d7c169f8dd8ae290", size = 1892628, upload-time = "2025-04-23T18:31:47.819Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/43/e4/5479fecb3606c1368d496a825d8411e126133c41224c1e7238be58b87d7e/pydantic_core-2.33.2-cp312-cp312-win_amd64.whl", hash = "sha256:f941635f2a3d96b2973e867144fde513665c87f13fe0e193c158ac51bfaaa7b2", size = 1955866, upload-time = "2025-04-23T18:31:49.635Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/24/8b11e8b3e2be9dd82df4b11408a67c61bb4dc4f8e11b5b0fc888b38118b5/pydantic_core-2.33.2-cp312-cp312-win_arm64.whl", hash = "sha256:cca3868ddfaccfbc4bfb1d608e2ccaaebe0ae628e1416aeb9c4d88c001bb45ab", size = 1888894, upload-time = "2025-04-23T18:31:51.609Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload-time = "2024-08-06T20:33:50.674Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload-time = "2024-08-06T20:32:25.131Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload-time = "2024-08-06T20:32:26.511Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload-time = "2024-08-06T20:32:28.363Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload-time = "2024-08-06T20:32:30.058Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload-time = "2024-08-06T20:32:31.881Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload-time = "2024-08-06T20:32:37.083Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload-time = "2024-08-06T20:32:38.898Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload-time = "2024-08-06T20:32:40.241Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload-time = "2024-08-06T20:32:41.93Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "referencing"
|
||||
version = "0.36.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "rpds-py" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rpds-py"
|
||||
version = "0.24.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863, upload-time = "2025-03-26T14:56:01.518Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945, upload-time = "2025-03-26T14:53:28.149Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935, upload-time = "2025-03-26T14:53:29.684Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817, upload-time = "2025-03-26T14:53:31.177Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983, upload-time = "2025-03-26T14:53:33.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719, upload-time = "2025-03-26T14:53:34.721Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546, upload-time = "2025-03-26T14:53:36.26Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695, upload-time = "2025-03-26T14:53:37.728Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218, upload-time = "2025-03-26T14:53:39.326Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062, upload-time = "2025-03-26T14:53:40.885Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262, upload-time = "2025-03-26T14:53:42.544Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306, upload-time = "2025-03-26T14:53:44.2Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281, upload-time = "2025-03-26T14:53:45.769Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719, upload-time = "2025-03-26T14:53:47.187Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tenacity"
|
||||
version = "9.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.13.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typing-inspection"
|
||||
version = "0.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222, upload-time = "2025-02-25T17:27:59.638Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125, upload-time = "2025-02-25T17:27:57.754Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websocket-client"
|
||||
version = "1.8.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wrapt"
|
||||
version = "1.17.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/fc/e91cc220803d7bc4db93fb02facd8461c37364151b8494762cc88b0fbcef/wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3", size = 55531, upload-time = "2025-01-14T10:35:45.465Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a1/bd/ab55f849fd1f9a58ed7ea47f5559ff09741b25f00c191231f9f059c83949/wrapt-1.17.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:d5e2439eecc762cd85e7bd37161d4714aa03a33c5ba884e26c81559817ca0925", size = 53799, upload-time = "2025-01-14T10:33:57.4Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/53/18/75ddc64c3f63988f5a1d7e10fb204ffe5762bc663f8023f18ecaf31a332e/wrapt-1.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:3fc7cb4c1c744f8c05cd5f9438a3caa6ab94ce8344e952d7c45a8ed59dd88392", size = 38821, upload-time = "2025-01-14T10:33:59.334Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/48/2a/97928387d6ed1c1ebbfd4efc4133a0633546bec8481a2dd5ec961313a1c7/wrapt-1.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8fdbdb757d5390f7c675e558fd3186d590973244fab0c5fe63d373ade3e99d40", size = 38919, upload-time = "2025-01-14T10:34:04.093Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/54/3bfe5a1febbbccb7a2f77de47b989c0b85ed3a6a41614b104204a788c20e/wrapt-1.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb1d0dbf99411f3d871deb6faa9aabb9d4e744d67dcaaa05399af89d847a91d", size = 88721, upload-time = "2025-01-14T10:34:07.163Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/cb/7262bc1b0300b4b64af50c2720ef958c2c1917525238d661c3e9a2b71b7b/wrapt-1.17.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d18a4865f46b8579d44e4fe1e2bcbc6472ad83d98e22a26c963d46e4c125ef0b", size = 80899, upload-time = "2025-01-14T10:34:09.82Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2a/5a/04cde32b07a7431d4ed0553a76fdb7a61270e78c5fd5a603e190ac389f14/wrapt-1.17.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc570b5f14a79734437cb7b0500376b6b791153314986074486e0b0fa8d71d98", size = 89222, upload-time = "2025-01-14T10:34:11.258Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/28/2e45a4f4771fcfb109e244d5dbe54259e970362a311b67a965555ba65026/wrapt-1.17.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6d9187b01bebc3875bac9b087948a2bccefe464a7d8f627cf6e48b1bbae30f82", size = 86707, upload-time = "2025-01-14T10:34:12.49Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/d2/dcb56bf5f32fcd4bd9aacc77b50a539abdd5b6536872413fd3f428b21bed/wrapt-1.17.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e8659775f1adf02eb1e6f109751268e493c73716ca5761f8acb695e52a756ae", size = 79685, upload-time = "2025-01-14T10:34:15.043Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/80/4e/eb8b353e36711347893f502ce91c770b0b0929f8f0bed2670a6856e667a9/wrapt-1.17.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e8b2816ebef96d83657b56306152a93909a83f23994f4b30ad4573b00bd11bb9", size = 87567, upload-time = "2025-01-14T10:34:16.563Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/27/4fe749a54e7fae6e7146f1c7d914d28ef599dacd4416566c055564080fe2/wrapt-1.17.2-cp312-cp312-win32.whl", hash = "sha256:468090021f391fe0056ad3e807e3d9034e0fd01adcd3bdfba977b6fdf4213ea9", size = 36672, upload-time = "2025-01-14T10:34:17.727Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/15/06/1dbf478ea45c03e78a6a8c4be4fdc3c3bddea5c8de8a93bc971415e47f0f/wrapt-1.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:ec89ed91f2fa8e3f52ae53cd3cf640d6feff92ba90d62236a81e4e563ac0e991", size = 38865, upload-time = "2025-01-14T10:34:19.577Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/2d/82/f56956041adef78f849db6b289b282e72b55ab8045a75abad81898c28d19/wrapt-1.17.2-py3-none-any.whl", hash = "sha256:b18f2d1533a71f069c7f82d524a52599053d4c7166e9dd374ae2136b7f40f7c8", size = 23594, upload-time = "2025-01-14T10:35:44.018Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.21.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545, upload-time = "2024-11-10T15:05:20.202Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630, upload-time = "2024-11-10T15:05:19.275Z" },
|
||||
]
|
@@ -225,6 +225,20 @@ applications:
|
||||
resources:
|
||||
heat-api-image: ghcr.io/canonical/heat-consolidated:2025.1
|
||||
heat-engine-image: ghcr.io/canonical/heat-consolidated:2025.1
|
||||
# Bare metal feature
|
||||
ironic:
|
||||
{% if ironic_k8s is defined and ironic_k8s is sameas true -%}
|
||||
charm: ../../../ironic-k8s.charm
|
||||
{% else -%}
|
||||
charm: ch:ironic-k8s
|
||||
channel: 2025.1/edge
|
||||
{% endif -%}
|
||||
base: ubuntu@24.04
|
||||
scale: 1
|
||||
trust: true
|
||||
resources:
|
||||
ironic-api-image: ghcr.io/canonical/ironic-consolidated:2025.1
|
||||
ironic-novncproxy-image: ghcr.io/canonical/ironic-consolidated:2025.1
|
||||
# Load Balancer feature
|
||||
octavia:
|
||||
{% if octavia_k8s is defined and octavia_k8s is sameas true -%}
|
||||
@@ -505,6 +519,19 @@ relations:
|
||||
- - keystone:send-ca-cert
|
||||
- heat:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- ironic:database
|
||||
- - keystone:identity-service
|
||||
- ironic:identity-service
|
||||
- - traefik:ingress
|
||||
- ironic:ingress-internal
|
||||
- - traefik:traefik-route
|
||||
- ironic:traefik-route-internal
|
||||
- - rabbitmq:amqp
|
||||
- ironic:amqp
|
||||
- - keystone:send-ca-cert
|
||||
- ironic:receive-ca-cert
|
||||
|
||||
- - mysql:database
|
||||
- octavia:database
|
||||
- - keystone:identity-service
|
||||
|
@@ -94,6 +94,9 @@ target_deploy_status:
|
||||
heat:
|
||||
workload-status: active
|
||||
workload-status-message-regex: '^.*$'
|
||||
ironic:
|
||||
workload-status: active
|
||||
workload-status-message-regex: '^.*$'
|
||||
octavia:
|
||||
workload-status: active
|
||||
workload-status-message-regex: '^$'
|
||||
|
@@ -178,6 +178,18 @@
|
||||
- rebuild
|
||||
vars:
|
||||
charm: heat-k8s
|
||||
- job:
|
||||
name: charm-build-ironic-k8s
|
||||
description: Build sunbeam ironic-k8s charm
|
||||
run: playbooks/charm/build.yaml
|
||||
timeout: 3600
|
||||
match-on-config-updates: false
|
||||
files:
|
||||
- ops-sunbeam/ops_sunbeam/
|
||||
- charms/ironic-k8s/
|
||||
- rebuild
|
||||
vars:
|
||||
charm: ironic-k8s
|
||||
- job:
|
||||
name: charm-build-octavia-k8s
|
||||
description: Build sunbeam octavia-k8s charm
|
||||
@@ -902,6 +914,19 @@
|
||||
- charmhub_token
|
||||
timeout: 3600
|
||||
|
||||
- job:
|
||||
name: publish-charm-ironic-k8s
|
||||
description: |
|
||||
Publish ironic-k8s built in gate pipeline.
|
||||
run: playbooks/charm/publish.yaml
|
||||
files:
|
||||
- ops-sunbeam/ops_sunbeam/
|
||||
- charms/ironic-k8s/
|
||||
- rebuild
|
||||
secrets:
|
||||
- charmhub_token
|
||||
timeout: 3600
|
||||
|
||||
- job:
|
||||
name: publish-charm-horizon-k8s
|
||||
description: |
|
||||
@@ -1253,6 +1278,8 @@
|
||||
soft: true
|
||||
- name: charm-build-heat-k8s
|
||||
soft: true
|
||||
- name: charm-build-ironic-k8s
|
||||
soft: true
|
||||
- name: charm-build-octavia-k8s
|
||||
soft: true
|
||||
- name: charm-build-barbican-k8s
|
||||
@@ -1296,6 +1323,7 @@
|
||||
- charms/aodh-k8s/
|
||||
- charms/watcher-k8s/
|
||||
- charms/heat-k8s/
|
||||
- charms/ironic-k8s/
|
||||
- charms/octavia-k8s/
|
||||
- charms/barbican-k8s/
|
||||
- charms/magnum-k8s/
|
||||
@@ -1329,6 +1357,7 @@
|
||||
- charm-build-aodh-k8s
|
||||
- charm-build-watcher-k8s
|
||||
- charm-build-heat-k8s
|
||||
- charm-build-ironic-k8s
|
||||
- charm-build-octavia-k8s
|
||||
- charm-build-barbican-k8s
|
||||
- charm-build-magnum-k8s
|
||||
|
@@ -66,6 +66,8 @@
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-heat-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-ironic-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-octavia-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-aodh-k8s:
|
||||
@@ -145,6 +147,8 @@
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-heat-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-ironic-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-octavia-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- charm-build-aodh-k8s:
|
||||
@@ -228,6 +232,8 @@
|
||||
nodeset: ubuntu-jammy
|
||||
- publish-charm-heat-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- publish-charm-ironic-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- publish-charm-octavia-k8s:
|
||||
nodeset: ubuntu-jammy
|
||||
- publish-charm-aodh-k8s:
|
||||
|
@@ -35,6 +35,7 @@
|
||||
cinder-ceph-k8s: 2025.1/edge
|
||||
horizon-k8s: 2025.1/edge
|
||||
heat-k8s: 2025.1/edge
|
||||
ironic-k8s: 2025.1/edge
|
||||
octavia-k8s: 2025.1/edge
|
||||
aodh-k8s: 2025.1/edge
|
||||
ceilometer-k8s: 2025.1/edge
|
||||
|
Reference in New Issue
Block a user