diff --git a/NOTICE b/NOTICE
index 1c8bc8fc..557e25eb 100644
--- a/NOTICE
+++ b/NOTICE
@@ -2036,7 +2036,7 @@ SOFTWARE.
## github.com/go-logr/logr
* Name: github.com/go-logr/logr
-* Version: v1.4.3
+* Version: v1.4.4
* License: Apache-2.0
```
@@ -2669,7 +2669,7 @@ SOFTWARE.
## github.com/go-openapi/jsonpointer
* Name: github.com/go-openapi/jsonpointer
-* Version: v0.23.1
+* Version: v1.0.0
* License: Apache-2.0
```
@@ -2880,7 +2880,7 @@ SOFTWARE.
## github.com/go-openapi/jsonreference
* Name: github.com/go-openapi/jsonreference
-* Version: v0.21.6
+* Version: v1.0.0
* License: Apache-2.0
```
@@ -3092,7 +3092,7 @@ SOFTWARE.
## github.com/go-openapi/swag
* Name: github.com/go-openapi/swag
-* Version: v0.25.4
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -3304,7 +3304,7 @@ SOFTWARE.
## github.com/go-openapi/swag/cmdutils
* Name: github.com/go-openapi/swag/cmdutils
-* Version: v0.25.4
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -3516,7 +3516,7 @@ SOFTWARE.
## github.com/go-openapi/swag/conv
* Name: github.com/go-openapi/swag/conv
-* Version: v0.26.0
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -3728,7 +3728,7 @@ SOFTWARE.
## github.com/go-openapi/swag/fileutils
* Name: github.com/go-openapi/swag/fileutils
-* Version: v0.26.0
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -3937,10 +3937,10 @@ SOFTWARE.
```
-## github.com/go-openapi/swag/jsonname
+## github.com/go-openapi/swag/jsonutils
-* Name: github.com/go-openapi/swag/jsonname
-* Version: v0.26.0
+* Name: github.com/go-openapi/swag/jsonutils
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -4149,10 +4149,10 @@ SOFTWARE.
```
-## github.com/go-openapi/swag/jsonutils
+## github.com/go-openapi/swag/loading
-* Name: github.com/go-openapi/swag/jsonutils
-* Version: v0.26.0
+* Name: github.com/go-openapi/swag/loading
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -4361,10 +4361,10 @@ SOFTWARE.
```
-## github.com/go-openapi/swag/loading
+## github.com/go-openapi/swag/mangling
-* Name: github.com/go-openapi/swag/loading
-* Version: v0.26.0
+* Name: github.com/go-openapi/swag/mangling
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -4573,10 +4573,10 @@ SOFTWARE.
```
-## github.com/go-openapi/swag/mangling
+## github.com/go-openapi/swag/netutils
-* Name: github.com/go-openapi/swag/mangling
-* Version: v0.26.0
+* Name: github.com/go-openapi/swag/netutils
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -4785,10 +4785,10 @@ SOFTWARE.
```
-## github.com/go-openapi/swag/netutils
+## github.com/go-openapi/swag/pools
-* Name: github.com/go-openapi/swag/netutils
-* Version: v0.25.4
+* Name: github.com/go-openapi/swag/pools
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -5000,7 +5000,7 @@ SOFTWARE.
## github.com/go-openapi/swag/stringutils
* Name: github.com/go-openapi/swag/stringutils
-* Version: v0.26.0
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -5212,7 +5212,7 @@ SOFTWARE.
## github.com/go-openapi/swag/typeutils
* Name: github.com/go-openapi/swag/typeutils
-* Version: v0.26.0
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -5424,7 +5424,7 @@ SOFTWARE.
## github.com/go-openapi/swag/yamlutils
* Name: github.com/go-openapi/swag/yamlutils
-* Version: v0.26.0
+* Version: v0.28.0
* License: Apache-2.0
```
@@ -6652,7 +6652,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/grpc-ecosystem/grpc-gateway/v2
* Name: github.com/grpc-ecosystem/grpc-gateway/v2
-* Version: v2.29.0
+* Version: v2.30.0
* License: BSD-3-Clause
```
@@ -6717,6 +6717,248 @@ SOFTWARE.
```
+## github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io
+
+* Name: github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io
+* Version: v1.7.7
+* License: Apache-2.0
+
+```
+ 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.
+
+```
+
+## github.com/kenshaw/baseconv
+
+* Name: github.com/kenshaw/baseconv
+* Version: v0.1.1
+* License: MIT
+
+```
+The MIT License (MIT)
+
+Copyright (c) 2015 Kenneth Shaw
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+```
+
## github.com/mitchellh/go-homedir
* Name: github.com/mitchellh/go-homedir
@@ -7440,7 +7682,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/onsi/ginkgo/v2
* Name: github.com/onsi/ginkgo/v2
-* Version: v2.28.1
+* Version: v2.28.2
* License: MIT
```
@@ -7536,7 +7778,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil
* Name: github.com/prometheus/client_golang/internal/github.com/golang/gddo/httputil
-* Version: v1.23.2
+* Version: v1.25.0
* License: BSD-3-Clause
```
@@ -7573,7 +7815,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/prometheus/client_golang/prometheus
* Name: github.com/prometheus/client_golang/prometheus
-* Version: v1.23.2
+* Version: v1.25.0
* License: Apache-2.0
```
@@ -7784,7 +8026,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/prometheus/client_model/go
* Name: github.com/prometheus/client_model/go
-* Version: v0.6.2
+* Version: v0.6.3
* License: Apache-2.0
```
@@ -7995,7 +8237,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/prometheus/common
* Name: github.com/prometheus/common
-* Version: v0.67.5
+* Version: v0.72.0
* License: Apache-2.0
```
@@ -8206,7 +8448,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## github.com/prometheus/procfs
* Name: github.com/prometheus/procfs
-* Version: v0.20.1
+* Version: v0.22.0
* License: Apache-2.0
```
@@ -9113,6 +9355,676 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
```
+## go.datum.net/galactic/api/cloud/v1alpha1
+
+* Name: go.datum.net/galactic/api/cloud/v1alpha1
+* Version: v0.5.8-0.20261016143526-45d3c3514bec
+* License: AGPL-3.0
+
+```
+ GNU AFFERO GENERAL PUBLIC LICENSE
+ Version 3, 19 November 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU Affero General Public License is a free, copyleft license for
+software and other kinds of works, specifically designed to ensure
+cooperation with the community in the case of network server software.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+our General Public Licenses are intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ Developers that use our General Public Licenses protect your rights
+with two steps: (1) assert copyright on the software, and (2) offer
+you this License which gives you legal permission to copy, distribute
+and/or modify the software.
+
+ A secondary benefit of defending all users' freedom is that
+improvements made in alternate versions of the program, if they
+receive widespread use, become available for other developers to
+incorporate. Many developers of free software are heartened and
+encouraged by the resulting cooperation. However, in the case of
+software used on network servers, this result may fail to come about.
+The GNU General Public License permits making a modified version and
+letting the public access it on a server without ever releasing its
+source code to the public.
+
+ The GNU Affero General Public License is designed specifically to
+ensure that, in such cases, the modified source code becomes available
+to the community. It requires the operator of a network server to
+provide the source code of the modified version running there to the
+users of that server. Therefore, public use of a modified version, on
+a publicly accessible server, gives the public access to the source
+code of the modified version.
+
+ An older license, called the Affero General Public License and
+published by Affero, was designed to accomplish similar goals. This is
+a different license, not a version of the Affero GPL, but Affero has
+released a new version of the Affero GPL which permits relicensing under
+this license.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU Affero General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Remote Network Interaction; Use with the GNU General Public License.
+
+ Notwithstanding any other provision of this License, if you modify the
+Program, your modified version must prominently offer all users
+interacting with it remotely through a computer network (if your version
+supports such interaction) an opportunity to receive the Corresponding
+Source of your version by providing access to the Corresponding Source
+from a network server at no charge, through some standard or customary
+means of facilitating copying of software. This Corresponding Source
+shall include the Corresponding Source for any work covered by version 3
+of the GNU General Public License that is incorporated pursuant to the
+following paragraph.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the work with which it is combined will remain governed by version
+3 of the GNU General Public License.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU Affero General Public License from time to time. Such new versions
+will be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU Affero General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU Affero General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU Affero General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Affero General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU Affero General Public License for more details.
+
+ You should have received a copy of the GNU Affero General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If your software can interact with users remotely through a computer
+network, you should also make sure that it provides a way for users to
+get its source. For example, if your program is a web application, its
+interface could display a "Source" link that leads users to an archive
+of the code. There are many ways you could offer source, and different
+solutions will be better for different programs; see section 13 for the
+specific requirements.
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU AGPL, see
+.
+```
+
## go.miloapis.com/dns-operator/api/v1alpha1
* Name: go.miloapis.com/dns-operator/api/v1alpha1
@@ -9156,7 +10068,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.miloapis.com/locations
* Name: go.miloapis.com/locations
-* Version: v0.0.0-20260825185141-507ac2cbd48c
+* Version: v0.0.1
* License: AGPL-3.0
```
@@ -10708,7 +11620,7 @@ For more information on this, and how to apply and follow the GNU AGPL, see
## go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
* Name: go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
-* Version: v0.67.0
+* Version: v0.72.0
* License: Apache-2.0
```
@@ -10948,7 +11860,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
* Name: go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
-* Version: v0.69.0
+* Version: v0.70.0
* License: Apache-2.0
```
@@ -11188,7 +12100,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/otel
* Name: go.opentelemetry.io/otel
-* Version: v1.44.0
+* Version: v1.47.0
* License: Apache-2.0
```
@@ -11428,7 +12340,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/otel/exporters/otlp/otlptrace
* Name: go.opentelemetry.io/otel/exporters/otlp/otlptrace
-* Version: v1.44.0
+* Version: v1.47.0
* License: Apache-2.0
```
@@ -11668,7 +12580,247 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
* Name: go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
-* Version: v1.44.0
+* Version: v1.47.0
+* License: Apache-2.0
+
+```
+ 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.
+
+--------------------------------------------------------------------------------
+
+Copyright 2009 The Go Authors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+ * Neither the name of Google LLC nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+```
+
+## go.opentelemetry.io/otel/log
+
+* Name: go.opentelemetry.io/otel/log
+* Version: v1.47.0
* License: Apache-2.0
```
@@ -11908,7 +13060,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/otel/metric
* Name: go.opentelemetry.io/otel/metric
-* Version: v1.44.0
+* Version: v1.47.0
* License: Apache-2.0
```
@@ -12148,7 +13300,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/otel/sdk
* Name: go.opentelemetry.io/otel/sdk
-* Version: v1.44.0
+* Version: v1.47.0
* License: Apache-2.0
```
@@ -12388,7 +13540,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/otel/trace
* Name: go.opentelemetry.io/otel/trace
-* Version: v1.44.0
+* Version: v1.47.0
* License: Apache-2.0
```
@@ -12628,7 +13780,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## go.opentelemetry.io/proto/otlp
* Name: go.opentelemetry.io/proto/otlp
-* Version: v1.10.0
+* Version: v1.11.0
* License: Apache-2.0
```
@@ -13108,7 +14260,7 @@ THE SOFTWARE.
## go.yaml.in/yaml/v3
* Name: go.yaml.in/yaml/v3
-* Version: v3.0.4
+* Version: v3.0.5
* License: MIT
```
@@ -13168,7 +14320,7 @@ limitations under the License.
## golang.org/x/crypto/pkcs12
* Name: golang.org/x/crypto/pkcs12
-* Version: v0.54.0
+* Version: v0.58.0
* License: BSD-3-Clause
```
@@ -13242,7 +14394,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/mod/semver
* Name: golang.org/x/mod/semver
-* Version: v0.38.0
+* Version: v0.41.0
* License: BSD-3-Clause
```
@@ -13279,7 +14431,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/net
* Name: golang.org/x/net
-* Version: v0.56.0
+* Version: v0.61.0
* License: BSD-3-Clause
```
@@ -13316,7 +14468,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/oauth2
* Name: golang.org/x/oauth2
-* Version: v0.36.0
+* Version: v0.37.0
* License: BSD-3-Clause
```
@@ -13353,7 +14505,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/sync
* Name: golang.org/x/sync
-* Version: v0.22.0
+* Version: v0.24.0
* License: BSD-3-Clause
```
@@ -13390,7 +14542,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/sys
* Name: golang.org/x/sys
-* Version: v0.47.0
+* Version: v0.49.0
* License: BSD-3-Clause
```
@@ -13427,7 +14579,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/term
* Name: golang.org/x/term
-* Version: v0.45.0
+* Version: v0.47.0
* License: BSD-3-Clause
```
@@ -13464,7 +14616,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/text
* Name: golang.org/x/text
-* Version: v0.40.0
+* Version: v0.43.0
* License: BSD-3-Clause
```
@@ -13501,7 +14653,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/time/rate
* Name: golang.org/x/time/rate
-* Version: v0.15.0
+* Version: v0.16.0
* License: BSD-3-Clause
```
@@ -13538,7 +14690,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## golang.org/x/tools
* Name: golang.org/x/tools
-* Version: v0.47.0
+* Version: v0.51.0
* License: BSD-3-Clause
```
@@ -13787,7 +14939,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## google.golang.org/genproto/googleapis/api
* Name: google.golang.org/genproto/googleapis/api
-* Version: v0.0.0-20260526163538-3dc84a4a5aaa
+* Version: v0.0.0-20260928230214-8a89bd6388cc
* License: Apache-2.0
```
@@ -13999,7 +15151,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## google.golang.org/genproto/googleapis/rpc
* Name: google.golang.org/genproto/googleapis/rpc
-* Version: v0.0.0-20260526163538-3dc84a4a5aaa
+* Version: v0.0.0-20261005182115-fad411399dd8
* License: Apache-2.0
```
@@ -14211,7 +15363,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## google.golang.org/grpc
* Name: google.golang.org/grpc
-* Version: v1.81.1
+* Version: v1.84.0
* License: Apache-2.0
```
@@ -14423,7 +15575,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## google.golang.org/protobuf
* Name: google.golang.org/protobuf
-* Version: v1.36.12-0.20260120151049-f2248ac996af
+* Version: v1.36.12
* License: BSD-3-Clause
```
@@ -14533,7 +15685,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## k8s.io/api
* Name: k8s.io/api
-* Version: v0.36.1
+* Version: v0.36.3
* License: Apache-2.0
```
@@ -14957,7 +16109,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## k8s.io/apimachinery/pkg
* Name: k8s.io/apimachinery/pkg
-* Version: v0.36.1
+* Version: v0.36.3
* License: Apache-2.0
```
@@ -15169,7 +16321,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## k8s.io/apimachinery/third_party/forked/golang
* Name: k8s.io/apimachinery/third_party/forked/golang
-* Version: v0.36.1
+* Version: v0.36.3
* License: BSD-3-Clause
```
@@ -15418,7 +16570,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## k8s.io/client-go
* Name: k8s.io/client-go
-* Version: v0.36.1
+* Version: v0.36.3
* License: Apache-2.0
```
@@ -16958,7 +18110,7 @@ SOFTWARE.
## k8s.io/streaming/pkg
* Name: k8s.io/streaming/pkg
-* Version: v0.36.1
+* Version: v0.36.3
* License: Apache-2.0
```
@@ -17170,7 +18322,7 @@ SOFTWARE.
## k8s.io/utils
* Name: k8s.io/utils
-* Version: v0.0.0-20260319190234-28399d86e0b5
+* Version: v0.0.0-20260707023825-cf1189d6abe3
* License: Apache-2.0
```
@@ -17382,7 +18534,7 @@ SOFTWARE.
## k8s.io/utils/internal/third_party/forked/golang
* Name: k8s.io/utils/internal/third_party/forked/golang
-* Version: v0.0.0-20260319190234-28399d86e0b5
+* Version: v0.0.0-20260707023825-cf1189d6abe3
* License: BSD-3-Clause
```
@@ -17419,7 +18571,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## k8s.io/utils/third_party/forked/golang/btree
* Name: k8s.io/utils/third_party/forked/golang/btree
-* Version: v0.0.0-20260319190234-28399d86e0b5
+* Version: v0.0.0-20260707023825-cf1189d6abe3
* License: Apache-2.0
```
@@ -19145,7 +20297,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
## sigs.k8s.io/structured-merge-diff/v6
* Name: sigs.k8s.io/structured-merge-diff/v6
-* Version: v6.3.2
+* Version: v6.3.3
* License: Apache-2.0
```
diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml
index 356c583b..f073056f 100644
--- a/config/rbac/role.yaml
+++ b/config/rbac/role.yaml
@@ -176,6 +176,36 @@ rules:
- get
- patch
- update
+- apiGroups:
+ - galactic.datumapis.com
+ resources:
+ - networkfabricidentities
+ verbs:
+ - get
+ - list
+ - watch
+- apiGroups:
+ - galactic.datumapis.com
+ resources:
+ - vpcattachments
+ - vpcs
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
+- apiGroups:
+ - galactic.datumapis.com
+ resources:
+ - vpcattachments/status
+ - vpcs/status
+ verbs:
+ - get
+ - patch
+ - update
- apiGroups:
- gateway.envoyproxy.io
resources:
@@ -246,6 +276,18 @@ rules:
- get
- patch
- update
+- apiGroups:
+ - k8s.cni.cncf.io
+ resources:
+ - network-attachment-definitions
+ verbs:
+ - create
+ - delete
+ - get
+ - list
+ - patch
+ - update
+ - watch
- apiGroups:
- locations.miloapis.com
resources:
diff --git a/docs/enhancements/network-interfaces.md b/docs/enhancements/network-interfaces.md
index dfb14e30..f5101195 100644
--- a/docs/enhancements/network-interfaces.md
+++ b/docs/enhancements/network-interfaces.md
@@ -221,7 +221,7 @@ status:
networkContextRef:
name: default-us-central-1
attachmentRef:
- apiGroup: cloud.datumapis.com
+ apiGroup: galactic.datumapis.com
kind: VPCAttachment
name: nic-4f2a9c1e
vpc: 3kF9qP2x
@@ -399,7 +399,7 @@ status:
# The data-plane realization, once one exists.
attachmentRef:
- apiGroup: cloud.datumapis.com
+ apiGroup: galactic.datumapis.com
kind: VPCAttachment
name: nic-4f2a9c1e
@@ -489,12 +489,12 @@ the allocator to preserve.
### Reaching the data plane
A `NetworkInterface` says what an interface must be. `VPCAttachment`, in the
-[cloud](https://github.com/datum-cloud/cloud) API group, is where it becomes real, and the
+`galactic.datumapis.com` API group, is where it becomes real, and the
split is deliberate: an interface is allocated as soon as a claim exists, before an instance
has been scheduled to any node, while what a node ends up carrying — a container ID, a host
device, a VRF — is only knowable once the sandbox exists.
-The attachment is therefore **written as intent by the infrastructure provider and reported
+The attachment is therefore **written as intent by NSO's cell manager and reported
on by the node**, not created by the node. It has to be, because the data plane reads it
before the sandbox exists: the CNI configuration a `VPCAttachment` produces must already be
in place when the pod's sandbox is created, so an attachment that only came into being as a
@@ -507,7 +507,7 @@ NetworkInterfaceClaim compute's intent created per instance, per interfac
NetworkInterface NSO's answer addresses, gateway, MTU
│ realized by
▼
-VPCAttachment spec: the provider intent, written before the pod
+VPCAttachment spec: NSO intent, written before the pod
│ status: the node node, containerID, VRF, host device
│ attaches to
▼
diff --git a/go.mod b/go.mod
index 88378902..2eccabe3 100644
--- a/go.mod
+++ b/go.mod
@@ -6,44 +6,46 @@ require (
github.com/alicebob/miniredis/v2 v2.35.0
github.com/cert-manager/cert-manager v1.17.2
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2
- github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
github.com/domainr/whois v0.1.0
github.com/envoyproxy/gateway v1.8.1
github.com/envoyproxy/gateway/test v0.0.0-20260617000843-7e4492d9a99e
github.com/envoyproxy/go-control-plane/contrib v1.36.1-0.20260409050421-3f47accd6e14
github.com/envoyproxy/go-control-plane/envoy v1.37.1-0.20260409050421-3f47accd6e14
- github.com/go-logr/logr v1.4.3
+ github.com/go-logr/logr v1.4.4
github.com/go-redis/redis/v7 v7.4.1
github.com/google/cel-go v0.27.0
github.com/google/go-cmp v0.7.0
github.com/google/uuid v1.6.0
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3
+ github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7
+ github.com/kenshaw/baseconv v0.1.1
github.com/modelcontextprotocol/go-sdk v1.7.0
- github.com/onsi/ginkgo/v2 v2.28.1
+ github.com/onsi/ginkgo/v2 v2.28.2
github.com/openrdap/rdap v0.9.1
- github.com/prometheus/client_golang v1.23.2
+ github.com/prometheus/client_golang v1.25.0
github.com/spf13/cobra v1.10.2
- github.com/stretchr/testify v1.11.1
+ github.com/stretchr/testify v1.12.1
go.datum.net/datumctl v0.18.1
+ go.datum.net/galactic v0.5.8-0.20261016143526-45d3c3514bec
go.miloapis.com/activity v0.7.1
go.miloapis.com/dns-operator v0.5.1
- go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c
+ go.miloapis.com/locations v0.0.1
go.miloapis.com/milo v0.36.2
- go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0
- go.opentelemetry.io/otel v1.44.0
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0
- go.opentelemetry.io/otel/sdk v1.44.0
- go.opentelemetry.io/otel/trace v1.44.0
- golang.org/x/net v0.56.0
- golang.org/x/sync v0.22.0
- google.golang.org/grpc v1.81.1
- google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af
- k8s.io/api v0.36.1
+ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0
+ go.opentelemetry.io/otel v1.47.0
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0
+ go.opentelemetry.io/otel/sdk v1.47.0
+ go.opentelemetry.io/otel/trace v1.47.0
+ golang.org/x/net v0.61.0
+ golang.org/x/sync v0.24.0
+ google.golang.org/grpc v1.84.0
+ google.golang.org/protobuf v1.36.12
+ k8s.io/api v0.36.3
k8s.io/apiextensions-apiserver v0.36.1
- k8s.io/apimachinery v0.36.1
- k8s.io/client-go v0.36.1
- k8s.io/utils v0.0.0-20260319190234-28399d86e0b5
+ k8s.io/apimachinery v0.36.3
+ k8s.io/client-go v0.36.3
+ k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3
sigs.k8s.io/controller-runtime v0.24.1
sigs.k8s.io/gateway-api v1.5.1
sigs.k8s.io/gateway-api/conformance v1.5.1
@@ -53,11 +55,14 @@ require (
require (
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
- github.com/go-openapi/swag/cmdutils v0.25.4 // indirect
- github.com/go-openapi/swag/netutils v0.25.4 // indirect
+ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
+ github.com/go-openapi/swag/cmdutils v0.28.0 // indirect
+ github.com/go-openapi/swag/netutils v0.28.0 // indirect
+ github.com/go-openapi/swag/pools v0.28.0 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
+ go.opentelemetry.io/otel/log v1.47.0 // indirect
)
require (
@@ -114,7 +119,7 @@ require (
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.3 // indirect
- github.com/cilium/ebpf v0.21.0 // indirect
+ github.com/cilium/ebpf v0.22.0 // indirect
github.com/containerd/cgroups/v3 v3.1.3 // indirect
github.com/containerd/containerd v1.7.32 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
@@ -152,24 +157,23 @@ require (
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0
github.com/go-ole/go-ole v1.3.0 // indirect
- github.com/go-openapi/analysis v0.25.1 // indirect
- github.com/go-openapi/errors v0.22.7 // indirect
- github.com/go-openapi/jsonpointer v0.23.1 // indirect
- github.com/go-openapi/jsonreference v0.21.6 // indirect
- github.com/go-openapi/loads v0.23.3 // indirect
- github.com/go-openapi/spec v0.22.5 // indirect
- github.com/go-openapi/strfmt v0.26.3 // indirect
- github.com/go-openapi/swag v0.25.4 // indirect
- github.com/go-openapi/swag/conv v0.26.0 // indirect
- github.com/go-openapi/swag/fileutils v0.26.0 // indirect
- github.com/go-openapi/swag/jsonname v0.26.0 // indirect
- github.com/go-openapi/swag/jsonutils v0.26.0 // indirect
- github.com/go-openapi/swag/loading v0.26.0 // indirect
- github.com/go-openapi/swag/mangling v0.26.0 // indirect
- github.com/go-openapi/swag/stringutils v0.26.0 // indirect
- github.com/go-openapi/swag/typeutils v0.26.0 // indirect
- github.com/go-openapi/swag/yamlutils v0.26.0 // indirect
- github.com/go-openapi/validate v0.25.3 // indirect
+ github.com/go-openapi/analysis v0.25.5 // indirect
+ github.com/go-openapi/errors v0.22.8 // indirect
+ github.com/go-openapi/jsonpointer v1.0.0 // indirect
+ github.com/go-openapi/jsonreference v1.0.0 // indirect
+ github.com/go-openapi/loads v0.25.0 // indirect
+ github.com/go-openapi/spec v0.22.9 // indirect
+ github.com/go-openapi/strfmt v0.27.0 // indirect
+ github.com/go-openapi/swag v0.28.0 // indirect
+ github.com/go-openapi/swag/conv v0.28.0 // indirect
+ github.com/go-openapi/swag/fileutils v0.28.0 // indirect
+ github.com/go-openapi/swag/jsonutils v0.28.0 // indirect
+ github.com/go-openapi/swag/loading v0.28.0 // indirect
+ github.com/go-openapi/swag/mangling v0.28.0 // indirect
+ github.com/go-openapi/swag/stringutils v0.28.0 // indirect
+ github.com/go-openapi/swag/typeutils v0.28.0 // indirect
+ github.com/go-openapi/swag/yamlutils v0.28.0 // indirect
+ github.com/go-openapi/validate v0.26.1 // indirect
github.com/go-sql-driver/mysql v1.10.0 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
@@ -187,7 +191,7 @@ require (
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
- github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect
+ github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/huandu/xstrings v1.5.0 // indirect
@@ -197,7 +201,7 @@ require (
github.com/jackc/pgx/v5 v5.9.2 // indirect
github.com/jmoiron/sqlx v1.4.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
- github.com/klauspost/compress v1.18.6 // indirect
+ github.com/klauspost/compress v1.20.1 // indirect
github.com/kortschak/goroutine v1.1.3 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
@@ -243,10 +247,10 @@ require (
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
- github.com/prometheus/client_model v0.6.2 // indirect
- github.com/prometheus/common v0.67.5 // indirect
+ github.com/prometheus/client_model v0.6.3
+ github.com/prometheus/common v0.72.0 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
- github.com/prometheus/procfs v0.20.1 // indirect
+ github.com/prometheus/procfs v0.22.0 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.1 // indirect
github.com/replicatedhq/troubleshoot v0.129.1 // indirect
@@ -268,7 +272,7 @@ require (
github.com/subosito/gotenv v1.6.0 // indirect
github.com/tklauser/go-sysconf v0.4.0 // indirect
github.com/tklauser/numcpus v0.12.0 // indirect
- github.com/vishvananda/netlink v1.3.1 // indirect
+ github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c // indirect
github.com/vishvananda/netns v0.0.5 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
@@ -282,31 +286,31 @@ require (
go.miloapis.com/ipam v0.4.0
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 // indirect
- go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
+ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 // indirect
go.opentelemetry.io/otel/exporters/prometheus v0.66.0 // indirect
- go.opentelemetry.io/otel/metric v1.44.0 // indirect
- go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect
- go.opentelemetry.io/proto/otlp v1.10.0 // indirect
+ go.opentelemetry.io/otel/metric v1.47.0 // indirect
+ go.opentelemetry.io/otel/sdk/metric v1.47.0 // indirect
+ go.opentelemetry.io/proto/otlp v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.28.0
go.yaml.in/yaml/v2 v2.4.4 // indirect
- go.yaml.in/yaml/v3 v3.0.4 // indirect
- golang.org/x/crypto v0.54.0 // indirect
+ go.yaml.in/yaml/v3 v3.0.5 // indirect
+ golang.org/x/crypto v0.58.0 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb // indirect
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect
- golang.org/x/mod v0.38.0 // indirect
- golang.org/x/oauth2 v0.36.0 // indirect
- golang.org/x/sys v0.47.0 // indirect
- golang.org/x/term v0.45.0
- golang.org/x/text v0.40.0 // indirect
- golang.org/x/time v0.15.0 // indirect
- golang.org/x/tools v0.47.0 // indirect
+ golang.org/x/mod v0.41.0 // indirect
+ golang.org/x/oauth2 v0.37.0 // indirect
+ golang.org/x/sys v0.49.0 // indirect
+ golang.org/x/term v0.47.0
+ golang.org/x/text v0.43.0 // indirect
+ golang.org/x/time v0.16.0 // indirect
+ golang.org/x/tools v0.51.0 // indirect
gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect
- google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect
- google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
+ google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc // indirect
+ google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 // indirect
gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
@@ -319,7 +323,7 @@ require (
k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 // indirect
k8s.io/kubectl v0.36.1 // indirect
k8s.io/metrics v0.36.1 // indirect
- k8s.io/streaming v0.36.1 // indirect
+ k8s.io/streaming v0.36.3 // indirect
oras.land/oras-go/v2 v2.6.0 // indirect
periph.io/x/host/v3 v3.8.5 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.34.0 // indirect
@@ -329,7 +333,7 @@ require (
sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect
sigs.k8s.io/mcs-api v0.5.0 // indirect
sigs.k8s.io/randfill v1.0.0 // indirect
- sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect
+ sigs.k8s.io/structured-merge-diff/v6 v6.3.3 // indirect
)
replace sigs.k8s.io/multicluster-runtime => github.com/datum-cloud/multicluster-runtime v0.24.2-0.20261008054817-748176e5c835
diff --git a/go.sum b/go.sum
index 4c749d46..c54671b3 100644
--- a/go.sum
+++ b/go.sum
@@ -129,8 +129,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.3 h1:9liNh8t+u26xl5ddmWLmsOsdNLwkdRTg5AG+JnTiM80=
github.com/chai2010/gettext-go v1.0.3/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
-github.com/cilium/ebpf v0.21.0 h1:4dpx1J/B/1apeTmWBH5BkVLayHTkFrMovVPnHEk+l3k=
-github.com/cilium/ebpf v0.21.0/go.mod h1:1kHKv6Kvh5a6TePP5vvvoMa1bclRyzUXELSs272fmIQ=
+github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY=
+github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cncf/xds/go v0.0.0-20260202195803-dba9d589def2 h1:aBangftG7EVZoUb69Os8IaYg++6uMOdKK83QtkkvJik=
@@ -247,8 +247,8 @@ github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpj
github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE=
github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
-github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
+github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8=
+github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
@@ -256,52 +256,52 @@ github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
-github.com/go-openapi/analysis v0.25.1 h1:We0FXVvCY5XNSb8GrE+k+wsgkUqTJqnkqsVAC9EGdrM=
-github.com/go-openapi/analysis v0.25.1/go.mod h1:/S7h3rOOTHDGoPwyyjTGQpGeLKxWecr8QWSBdE68UgM=
-github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA=
-github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w=
-github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4=
-github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY=
-github.com/go-openapi/jsonreference v0.21.6 h1:NZ5nGfnaM1n4I43Xjm1e5/M2GjOwQwndQz22uhxwD+Y=
-github.com/go-openapi/jsonreference v0.21.6/go.mod h1:xzbgtQ3ZbWxvET3AxdzCJlJt6vkovbf+IfSPJjD0tUY=
-github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ=
-github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA=
-github.com/go-openapi/spec v0.22.5 h1:KhO7RBlKQfonUWX2WzQCoLIXVA6AcNqDGZ3a1Dutdlo=
-github.com/go-openapi/spec v0.22.5/go.mod h1:vxpOtMya5TXtENXKE5bKqv5NjocVhyhxHrlZfvKnZ74=
-github.com/go-openapi/strfmt v0.26.3 h1:rzmslHarJgBbf2qfGge+X3htclQfmXqBZMm0Too0HhU=
-github.com/go-openapi/strfmt v0.26.3/go.mod h1:a5nsUw0oRpQzZeOwx8bi6cKbzFZslpbCKt1LEot+KnQ=
-github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU=
-github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ=
-github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4=
-github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0=
-github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I=
-github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE=
-github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU=
-github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc=
-github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w=
-github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M=
-github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA=
-github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E=
-github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM=
-github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y=
-github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko=
-github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg=
-github.com/go-openapi/swag/mangling v0.26.0 h1:Du2YC4YLA/Y5m/YKQd7AnY5qq0wRKSFZTTt8ktFaXcQ=
-github.com/go-openapi/swag/mangling v0.26.0/go.mod h1:jifS7W9vbg+pw63bT+GI53otluMQL3CeemuyCHKwVx0=
-github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0=
-github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg=
-github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg=
-github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE=
-github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4=
-github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE=
-github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ=
-github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU=
-github.com/go-openapi/testify/enable/yaml/v2 v2.5.1 h1:q9NtHwK4qHF7yZziBPvZyv7zWAIk8ok88Gh2mR6Jpc8=
-github.com/go-openapi/testify/enable/yaml/v2 v2.5.1/go.mod h1:JW0MXIotCYps/XsgJnG3a8Q7rE5xAiBwoOD5OfaIQBk=
-github.com/go-openapi/testify/v2 v2.5.1 h1:TMdhCaw8fUNraVSf3Omoob1dO/AzBfhtFAPW0an6sBo=
-github.com/go-openapi/testify/v2 v2.5.1/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
-github.com/go-openapi/validate v0.25.3 h1:4nzAIavcJ7WveHK2+V1UAkZK3kWcjzxZCzjfZAfavKs=
-github.com/go-openapi/validate v0.25.3/go.mod h1:GemfuGMyYpIaBoKpX3z8sLywrmxpzWVOoJ7R0VeAVuk=
+github.com/go-openapi/analysis v0.25.5 h1:xPYEvTb90o1y0epuiOPAoG4QqahjP3cdp5xNlHeKJRI=
+github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc=
+github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I=
+github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w=
+github.com/go-openapi/jsonpointer v1.0.0 h1:kR9tHqY0CtZaOPVFm622dPVNhrvYpwr4uCxgL3h1H8s=
+github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y=
+github.com/go-openapi/jsonreference v1.0.0 h1:jlmTr6torcd1YgDQvSfNmRtKzYDO4FGBkrAdlAVWnpY=
+github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0=
+github.com/go-openapi/loads v0.25.0 h1:74Bc2snfaVlsHzwdQj/3gsA9XJz3daXTJVs+4ZaK7jI=
+github.com/go-openapi/loads v0.25.0/go.mod h1:JFBw4SIB9+PTIFHDfcXuSSy5h6aWzjtUCrPYyx3qWU8=
+github.com/go-openapi/spec v0.22.9 h1:/vKIFDcGKp0ktZWGbym/tJEWbk6/XOEmAVU0kqKMH+w=
+github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0=
+github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM=
+github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc=
+github.com/go-openapi/swag v0.28.0 h1:xkgbOSKj6DZziNpyqRRAOt3GJGtgjgsd2RoyT30VWuw=
+github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg=
+github.com/go-openapi/swag/cmdutils v0.28.0 h1:7TOeNtkYru1SG8Y34tDh9WBbLsMqGnptuxWiHREPZ4Q=
+github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM=
+github.com/go-openapi/swag/conv v0.28.0 h1:GtqqbyFe7vR5Y7ehxG9W6/OvrSFdf1OLeTGp40TqxH8=
+github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k=
+github.com/go-openapi/swag/fileutils v0.28.0 h1:Z04XWQD7R8Eq+7GnOrjovBxPPmZzsS4gt2H2GPGIViU=
+github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8=
+github.com/go-openapi/swag/jsonutils v0.28.0 h1:YIch6FwO7RXzeAnbO8Tu7dWBZeUEH+4nA0HXltVTnv4=
+github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0 h1:qV+VVUAx5Oro8WjVWpZeql7YReTKhT4smR4zhcOQZr0=
+github.com/go-openapi/swag/jsonutils/fixtures_test v0.28.0/go.mod h1:mofwUWx70wvskwESqRJ//k/9kURmCgyJl5m5Ppoh5kY=
+github.com/go-openapi/swag/loading v0.28.0 h1:td8QZdZC9MIYGGSnSPKShKiK22I2tU5UQvuUhIBPRLU=
+github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k=
+github.com/go-openapi/swag/mangling v0.28.0 h1:pH8eyeNO9SLYsTMWJrurnNfKmDa28XrlA+HePVD53VM=
+github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w=
+github.com/go-openapi/swag/netutils v0.28.0 h1:YXN6TALEi2pzts8/8GNm6T61HTAZsieukGZidap989k=
+github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ=
+github.com/go-openapi/swag/pools v0.28.0 h1:HPMZWSAfce3rdVTFcjFiCIBtDg9h4x2QlRrHipwhxeU=
+github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE=
+github.com/go-openapi/swag/stringutils v0.28.0 h1:ixsc9iYgDPubHL/8nSkbnryEHpD2VRlBMLKpQyPXcDU=
+github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM=
+github.com/go-openapi/swag/typeutils v0.28.0 h1:nRBKSBXjDgf01VDPB3fWeD9nQuhCOVeIYAkUx2tbkyY=
+github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ=
+github.com/go-openapi/swag/yamlutils v0.28.0 h1:TV3JXH6DS46KUroDtMLAYHGkdWf5VDq3wVWFirmzROY=
+github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs=
+github.com/go-openapi/testify/enable/yaml/v2 v2.6.0 h1:gGHwAJ0R/5jU8BEGDbfRNR3hL68dAVi84WuOApp29B0=
+github.com/go-openapi/testify/enable/yaml/v2 v2.6.0/go.mod h1:tY+St1SGq4NFl0QIqdTY4aEdbChAHxhyB77XQi9iJCo=
+github.com/go-openapi/testify/v2 v2.6.0 h1:5PKH2HE7YJ/LuRPQGvSxBRlFXNQhSetBLlGAgUEu3ug=
+github.com/go-openapi/testify/v2 v2.6.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw=
+github.com/go-openapi/validate v0.26.1 h1:pZSbvtRO8G2R2FpWTYRn3w8LrsNwbtaVhP2dWiBa0Us=
+github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo=
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6 h1:teYtXy9B7y5lHTp8V9KPxpYRAVA7dozigQcMiBust1s=
github.com/go-quicktest/qt v1.101.1-0.20240301121107-c6c8733fa1e6/go.mod h1:p4lGIVX+8Wa6ZPNDvqcxq36XpUDLh42FLetFU7odllI=
github.com/go-redis/redis/v7 v7.4.1 h1:PASvf36gyUpr2zdOUS/9Zqc80GbM+9BDyiJSJDDOrTI=
@@ -366,8 +366,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0 h1:QGLs
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.1.0/go.mod h1:hM2alZsMUni80N33RBe6J0e423LB+odMj7d3EMP9l20=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns=
github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 h1:/Tnpcb2E0Pz/tN9s3bfEY2Q8ePCEX9iuS+cneUwncnw=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0/go.mod h1:zOBXOsUaBSjKgmH4OGzV1esUpR3oUSCPYVd2cUBjKYY=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
@@ -396,8 +396,6 @@ github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
-github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
-github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE=
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -406,11 +404,15 @@ github.com/jsimonetti/rtnetlink/v2 v2.0.1 h1:xda7qaHDSVOsADNouv7ukSuicKZO7GgVUCX
github.com/jsimonetti/rtnetlink/v2 v2.0.1/go.mod h1:7MoNYNbb3UaDHtF8udiJo/RH6VsTKP1pqKLUTVCvToE=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7 h1:z4P744DR+PIpkjwXSEc6TvN3L6LVzmUquFgmNm8wSUc=
+github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.7.7/go.mod h1:CM7HAH5PNuIsqjMN0fGc1ydM74Uj+0VZFhob620nklw=
+github.com/kenshaw/baseconv v0.1.1 h1:oAu/C7ipUT2PqT9DT0mZDGDg4URIglizZMjPv9oCu0E=
+github.com/kenshaw/baseconv v0.1.1/go.mod h1:yy9zGmnnR6vgOxOQb702nVdAG30JhyYZpj/5/m0siRI=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao=
-github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
+github.com/klauspost/compress v1.20.1 h1:T7kKElXUMXrUJ2E9QhQhxFtcK5rPyLdsGZvdbLMPdiQ=
+github.com/klauspost/compress v1.20.1/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kortschak/goroutine v1.1.3 h1:kELvAfi7jpVD7a+MPWjmIxuQVJVYo/RELaOeGJZBb88=
github.com/kortschak/goroutine v1.1.3/go.mod h1:zKpXs1FWN/6mXasDQzfl7g0LrGFIOiA6cLs9eXKyaMY=
@@ -448,10 +450,10 @@ github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
-github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
-github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
-github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
-github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
+github.com/mdlayher/netlink v1.11.2 h1:HKh2jqe+omdSWcQ88nrT7INE61B0NXfiSPFdgL4YbNI=
+github.com/mdlayher/netlink v1.11.2/go.mod h1:uT2Yc/QLaZubzDpZIBi9d4GoeLwtp3x1AMeqSRrK2sA=
+github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU=
+github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/microsoft/go-mssqldb v1.10.0 h1:pHEt+Qz6YFPWqREq10mqSE524QQo+/QremwTCQht7TY=
@@ -514,8 +516,8 @@ github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
-github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
-github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
+github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps=
+github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg=
@@ -553,16 +555,16 @@ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
-github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
-github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
-github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
-github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
-github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
-github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
+github.com/prometheus/client_golang v1.25.0 h1:cDtW1TcFsMi1RJc8khwCKB4VhzwV04aKqbYtNOXuiK0=
+github.com/prometheus/client_golang v1.25.0/go.mod h1:j2X/4/kaQHLPnmyEU8zjmkndo1T317VpCIQEml8LTKk=
+github.com/prometheus/client_model v0.6.3 h1:O0jaTVAYNxTHYInEPFJt5I3+sN8zqBtVMPTB1qyxiEo=
+github.com/prometheus/client_model v0.6.3/go.mod h1:gpN5P9S7Rr6Yr92PiQ+Ixvhf6JZEkF1dnxsYL2aPBEM=
+github.com/prometheus/common v0.72.0 h1:tAYsE+sPJxIncDAobm4H5aQjmox9ZxEIIqPbiffa8G4=
+github.com/prometheus/common v0.72.0/go.mod h1:77NWqAQ2tXT7BIK40qjJdw5Acrsrg1TlHAnsQi3i6mk=
github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos=
github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM=
-github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
-github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
+github.com/prometheus/procfs v0.22.0 h1:6q9+/JL9IKAPbCmBrv9n5O5Ty3NKnciV5X7YGw0oics=
+github.com/prometheus/procfs v0.22.0/go.mod h1:CvmFr/GVhIjIvWJZW3tgkODBQMRIf0EyWMQLHCHab58=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.1 h1:0Gmua0HW1Tv7ANR7hUYwRyD0MG5OJfgvYSZasGZzBic=
@@ -623,8 +625,9 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
-github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
+github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
+github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -633,8 +636,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
-github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
-github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
+github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
@@ -652,8 +655,8 @@ github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqo
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
-github.com/vishvananda/netlink v1.3.1 h1:3AEMt62VKqz90r0tmNhog0r/PpWKmrEShJU0wJW6bV0=
-github.com/vishvananda/netlink v1.3.1/go.mod h1:ARtKouGSTGchR8aMwmkzC0qiNPrrWO5JS/XMVl45+b4=
+github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c h1:88GLXNKgc6pwIU/4yq+gleF5ISv3AsJkYmFlS1GjHvo=
+github.com/vishvananda/netlink v1.3.2-0.20261002201547-1d1c62507e3c/go.mod h1:lEui7SPMd9fgxzHVGRAvTxsBGCF6PRH81o2kLWLWHgw=
github.com/vishvananda/netns v0.0.5 h1:DfiHV+j8bA32MFM7bfEunvT8IAqQ/NzSJHtcmW5zdEY=
github.com/vishvananda/netns v0.0.5/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM=
github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM=
@@ -684,6 +687,8 @@ github.com/zonedb/zonedb v1.0.3544 h1:u5a3xOaI338FclecJ/H6J7fiImVEZ/qZomJnVYIlTe
github.com/zonedb/zonedb v1.0.3544/go.mod h1:h9mfHV/S6lboOkltULrbNY52cd7JZo6MbxIiqKMWPLg=
go.datum.net/datumctl v0.18.1 h1:MP83cRkJ94aq4lYyOm5dSLU60JNnmR486LIeZnaCw8s=
go.datum.net/datumctl v0.18.1/go.mod h1:0L0BBgFfud8ZuV9sbsSmeEBUPoK2q9g4boU5yczF92o=
+go.datum.net/galactic v0.5.8-0.20261016143526-45d3c3514bec h1:voR/MqaNhUuh9OylFZ3IamgoSkWGsKi+9Rta+PqlHc4=
+go.datum.net/galactic v0.5.8-0.20261016143526-45d3c3514bec/go.mod h1:g9KA3v6fQ4fMFBoWxFNA2ABlacKecn2l6ftJQeUdVE4=
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
go.etcd.io/etcd/api/v3 v3.6.8 h1:gqb1VN92TAI6G2FiBvWcqKtHiIjr4SU2GdXxTwyexbM=
@@ -704,10 +709,8 @@ go.miloapis.com/dns-operator v0.5.1 h1:3HmturI/Opl+l4/KurN6B0/XEeVefoSvYU0ewcZ8B
go.miloapis.com/dns-operator v0.5.1/go.mod h1:yQSBOx1ZhSzZgg5RyNKyNHfZgjXRKhxkcGD+jvbebQE=
go.miloapis.com/ipam v0.4.0 h1:U+mg3RMFXj0c2eQ0Lm15CI0bYBPFDqy8IsGLN8v/eGs=
go.miloapis.com/ipam v0.4.0/go.mod h1:Jj7xg4lJi9psE0+4PuOg/GQOG8rG13h112xYoM994rc=
-go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c h1:+BQirT3wYCgv7H2lEZtAH+dpMiWu9j/Wa+c8s4jJUIA=
-go.miloapis.com/locations v0.0.0-20260825185141-507ac2cbd48c/go.mod h1:gzfAfHhSMwl/N68k/uSNYXOKK3IOBJCdXYaBgCE3gdE=
-go.miloapis.com/milo v0.31.0 h1:gBZ9NaplTCFhgWUZJ18N38JsFDCVtdKmypwJvffSdUU=
-go.miloapis.com/milo v0.31.0/go.mod h1:GKK3afjCwshfZfvhjNe1wp/H45z4m7x5oG/8xbSgU1M=
+go.miloapis.com/locations v0.0.1 h1:voJKqBzyLX5x96M3+5y/ga7fgq9/+vypTizd+bBvqUY=
+go.miloapis.com/locations v0.0.1/go.mod h1:gzfAfHhSMwl/N68k/uSNYXOKK3IOBJCdXYaBgCE3gdE=
go.miloapis.com/milo v0.36.2 h1:ZXKX6KIQ9O6KF87IVHfXoQ0xmF9MztiP91fFLWasznQ=
go.miloapis.com/milo v0.36.2/go.mod h1:zMzwGXexUlPlFaZ9/4U+2DDbzPj4xzs9I76Z901g2sE=
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
@@ -717,12 +720,12 @@ go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXv
go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4=
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8=
go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04=
-go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo=
-go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI=
-go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
-go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0 h1:Tq+E65HaNbZTeqcLw6QUhsV+/EIWdkfUH2CQzcrlE5M=
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.72.0/go.mod h1:A+EVhDakAj1waJBPhmTszZaSr08ww8MRRKy1nB1rqck=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0 h1:LMuyCAyfalSjDyjdC65nK6N0zoTT63+E/u95X0JovZI=
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04=
+go.opentelemetry.io/otel v1.47.0 h1:j7ALJ/zgkS7Z6aeJW09p8VC9804bC+PpeTfCD4XPnOM=
+go.opentelemetry.io/otel v1.47.0/go.mod h1:8wS9O2qfXrYrzp6hIF/HOYJJf/wIhFPhR2xLuP+iXQU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU=
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag=
@@ -731,10 +734,10 @@ go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0 h1:SUp
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.44.0/go.mod h1:ho2g4N+ane+swq5I/VBkKWnRDY4kUINH3FuqyZqX/Ug=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0 h1:RuynHbfU8JUEw7DyONgkVYg2SVtsoF28y0LGIr69jgA=
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.44.0/go.mod h1:qZF+/lBs71APw8mlnEZcqZHMzqrYrsFiJOv83lX1OGo=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0 h1:4YsVu3B8+3qtWYYrsUYgn0OG78pN0rnNPRGX4SbokQI=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.44.0/go.mod h1:+wnlSn0mD1ADVMe3v9Z/WIaiz6q6gL2J/ejaAmdmv80=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0 h1:qazEJlUOQzhCpzQpFETGby7EdqjI1wsd0W+6Gg1SCTU=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.44.0/go.mod h1:fOD2Yefuxixkx3ahVNf0O/PERb6r4OlbxfATVnYvzCo=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0 h1:julhjPeUH/q/7hinbSdDdqt5h7Zw9YWmRlWRhI0jd54=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.47.0/go.mod h1:Ao2mz688LH/tFf0yMAenidq6k2YNSx6SIY2q6jDACck=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0 h1:UFpxOpYPmMNUtOWhdb+nC1WELIgVf8z9higURrbzYU4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.47.0/go.mod h1:YBGjxe3lt0jtXoEXxGrGhv/jM7oUBUDVK7zhAvNyEjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak=
go.opentelemetry.io/otel/exporters/prometheus v0.66.0 h1:vkrK8PAznv2NKt2r+kdu252ccGzkEqLc2aSXbQIALYQ=
@@ -743,24 +746,24 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/B
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA=
go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0=
-go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU=
-go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4=
-go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk=
-go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc=
-go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo=
-go.opentelemetry.io/otel/metric/x v0.66.0 h1:YkCrx1zLOChi9ZcZ6euupOcsgzbVlec7D/xoEU1+cTA=
-go.opentelemetry.io/otel/metric/x v0.66.0/go.mod h1:d1+BDj9t96do0/1LoU1ayfCv79ZgNE41qbhBvnMOBZk=
-go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58=
-go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0 h1:lsA/S1bxgdbyFGkTj+3meEdJ6ADVU7QoFstV6MXgE68=
+go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.45.0/go.mod h1:L7u+MirGoB1bjeLH66+xDykF4RC8C3RN7lIFpBiewUo=
+go.opentelemetry.io/otel/log v1.47.0 h1:cOTS1CcLbSQeZKanGJ+0JpF/+t4PELi3O3bbl2lqCcI=
+go.opentelemetry.io/otel/log v1.47.0/go.mod h1:9byitSQ5pLC6PpqwGXjqdMKya6ZTswHRZh2vvXT33nw=
+go.opentelemetry.io/otel/metric v1.47.0 h1:4PptaldXx3Eat1XjMZ68pPJEs5wrhlemctZE9a3UdWY=
+go.opentelemetry.io/otel/metric v1.47.0/go.mod h1:ADGSXxRrXM6bjbvLo535EstVFlPpPYZm4LBKixjDHwU=
+go.opentelemetry.io/otel/metric/x v0.69.0 h1:DjRLr15H83v+hCW7JA9NoJvOkYTtmq5YoDRbe9deYpM=
+go.opentelemetry.io/otel/metric/x v0.69.0/go.mod h1:uVvsMPMFFyj/HUQfrUnH3JjnOQ1dwFDorgFLRBasM0k=
+go.opentelemetry.io/otel/sdk v1.47.0 h1:zWXEr4j2lFefG87TU6Yg8a7ngfohIKFZHKp0Hf5hC6I=
+go.opentelemetry.io/otel/sdk v1.47.0/go.mod h1:VUc24kiOeoGsxG8G9ULx3fWKvB7jMhnGE8Oi607lgR0=
go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko=
go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg=
-go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI=
-go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA=
-go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk=
-go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE=
-go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g=
-go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk=
+go.opentelemetry.io/otel/sdk/metric v1.47.0 h1:lfISg2j93VT6yqdk9OfUaZmw/GfcZqCCV3jdXtsPnKw=
+go.opentelemetry.io/otel/sdk/metric v1.47.0/go.mod h1:ypLp+mW1Nt2x+Szt3b5/i1syodyts49lMOwxpDI3VGw=
+go.opentelemetry.io/otel/trace v1.47.0 h1:JOjX/Oci8K94QHddo+bbfya/Ai/nf6/dt9ZfrFNWSrM=
+go.opentelemetry.io/otel/trace v1.47.0/go.mod h1:jNaSLa2PZEYFG6fRjJABAu+bw4FS08uDmPg28lTghu0=
+go.opentelemetry.io/proto/otlp v1.11.0 h1:5rrYs0Ykyj50sdU/JU0x8etU+LubXWb+gED6TbEdMIk=
+go.opentelemetry.io/proto/otlp v1.11.0/go.mod h1:SmVizdCOAm3XBtG1g1NnOdhW6jtddT72hLMhv8VwA8E=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
@@ -771,15 +774,16 @@ go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
-go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
+go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
-golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
+golang.org/x/crypto v0.58.0 h1:COkYLr4k7nDI4r6QtMML2AfzeIswWHAJthLS6K/M/54=
+golang.org/x/crypto v0.58.0/go.mod h1:Xh+kl5A+M0gMiWDytH2Hhr5Z7WbNCpv/eB/EFgfSk+w=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb h1:Iu0p/klM0SM7atONioa/bPhLS7cjhnip99x1OIGibwg=
golang.org/x/crypto/x509roots/fallback v0.0.0-20250406160420-959f8f3db0fb/go.mod h1:lxN5T34bK4Z/i6cMaU7frUU57VkDXFD4Kamfl/cp9oU=
golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM=
@@ -787,8 +791,8 @@ golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aI
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
-golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
+golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
+golang.org/x/mod v0.41.0/go.mod h1:Ek9pY8RKWXwsWvd3rQiHYtMqkjSUV+s1Rj7j4H5Ur6o=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -799,17 +803,17 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o=
-golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec=
-golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
-golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/net v0.61.0 h1:RR+6j/BTBCrggOLugxa+66R8FvoN6MHlzDlZIbN9YFw=
+golang.org/x/net v0.61.0/go.mod h1:WgDCOTH8iwtB66T4/GfI94AHo/hhdb7Hah9NDseQfiI=
+golang.org/x/oauth2 v0.37.0 h1:JUlcxA8oAtauLfiH8FX2/FkAWHAdi0QtGCGc+hofE98=
+golang.org/x/oauth2 v0.37.0/go.mod h1:IxwZNxUULJmpBFf9K/9NTMSIfZZuvuTy1gGxhigP/58=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
-golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sync v0.24.0 h1:iqiDrGoDgyuvAKrhLQthUEzU/R378N78gXbBjxed7oA=
+golang.org/x/sync v0.24.0/go.mod h1:sUUOizhqBxiL6pEWpqNLUiaJn1ShEbZ6BBqskPbjZm0=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -829,29 +833,27 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
-golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
+golang.org/x/sys v0.49.0 h1:XbzkgYJHdqh/8m2Uu0W/dQv8nktxx4BFHp1M0gROTXA=
+golang.org/x/sys v0.49.0/go.mod h1:hNLxWAXmnKAxqDtdwIYC4bM9oQPEecfsnNMuSxOs3og=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
-golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
+golang.org/x/term v0.47.0 h1:cpyOwQkr2Nzxe2p6KnrTG7Ei2wtzjsuSjhpXXepZ/BM=
+golang.org/x/term v0.47.0/go.mod h1:efAfAuG6RKABvg1ArGebRkvX6LXul14SM67o2e4mUCY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs=
-golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY=
-golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
-golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
+golang.org/x/text v0.43.0 h1:1QivrlhwAsnMOcqgOdl9my7s9OyZl8O5/UGUTJVXoWc=
+golang.org/x/text v0.43.0/go.mod h1:hbSIYA/amXcRXTFSZqh8tWHEoqRvvbKPJ2uz/KU+a+s=
+golang.org/x/time v0.16.0 h1:vMb6ptszcQMkcwiRTAuNNU50gom6++Q/6gY2hDM6VDE=
+golang.org/x/time v0.16.0/go.mod h1:rVKOqvZeKvrDKTQiAHJ7wmwP0RzleSphoEA9RcdLA0s=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.6-0.20210726203631-07bc1bf47fb2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q=
-golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA=
+golang.org/x/tools v0.51.0 h1:k4Xc/1Om9jwkBJBo4NVLMSARBoWtK10mx+W5BnXCeAI=
+golang.org/x/tools v0.51.0/go.mod h1:9eEncMayCV6zRMGhR5eZEC2iBx98qWcF1HZ9Z7wJOoA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -860,16 +862,16 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0
gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
-google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8=
-google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
-google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ=
-google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I=
+google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc h1:rZHRz0ogq4Pid1IKtA/ivHi8XONVF5tqUQXA2TtgpkE=
+google.golang.org/genproto/googleapis/api v0.0.0-20260928230214-8a89bd6388cc/go.mod h1:K/n0XLazCJHjD73YuMyNJsN38BPPy1kfFnKxUXaThFU=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8 h1:0SXBqli5dnuCKMjI/X3FM226zEaEa0GUcSCJtN1vL2o=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20261005182115-fad411399dd8/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc=
+google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0=
+google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
-google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
+google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
+google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20160105164936-4f90aeace3a2/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@@ -896,18 +898,18 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
helm.sh/helm/v3 v3.21.0 h1:9TRbaXQH+BIKLLDYlu++JsyWodS5kBBOLF7C7HY5+cs=
helm.sh/helm/v3 v3.21.0/go.mod h1:5IvU6Ae6ruB/vasVHhnC1IU5RvqFM349vLYS1BiHqeY=
-k8s.io/api v0.36.1 h1:XbL/EMj8K2aJpJtePmqUyQMsM0D4QI2pvl7YKJ20FTY=
-k8s.io/api v0.36.1/go.mod h1:KOWo4ey3TINlXjeHVuwB3i+tXXnu+UcwFBHlI/9dvEo=
+k8s.io/api v0.36.3 h1:NxB+05W2UGqXWFXcLO0RB5cnqnUPP5v5sVlaOH0Iz4w=
+k8s.io/api v0.36.3/go.mod h1:JzLQKqRHC5+I8RVj/lS3lCg0mg6nWI9Fo/Sk3ElxHzg=
k8s.io/apiextensions-apiserver v0.36.1 h1:6JfYmPUsuUIHuN+3QxutXYWj492RqF5fBSx67GYK5Ks=
k8s.io/apiextensions-apiserver v0.36.1/go.mod h1:pLzZin90riwisdzKwv/GoTwENooytoIx5zWJb4Hkby8=
-k8s.io/apimachinery v0.36.1 h1:G63Gjx2W+q0YD+72Vo8oY0nDnePVwnuzTmmy5ENrVSA=
-k8s.io/apimachinery v0.36.1/go.mod h1:ibYOR00vW/I1kzvi5SF0dRuJ52BvKtfvRdOn35GPQ+8=
+k8s.io/apimachinery v0.36.3 h1:PkzMRBRG8joFD8EhCuQAtNPvJlxb82FwplP26HIzvAM=
+k8s.io/apimachinery v0.36.3/go.mod h1:cTSjBWgPe/6CQyBKzY/hDIRWCQQQeK0mfLbml0UYFHE=
k8s.io/apiserver v0.36.1 h1:iMS5V+rPUertv5P9RaqJgmHHTuh4quWpoxchvMUY+JY=
k8s.io/apiserver v0.36.1/go.mod h1:Cby1PbLWztu0GDOxoO6iFOyyqIsziHNEW+w9zVQ22Kw=
k8s.io/cli-runtime v0.36.1 h1:yuC/BGnnj1YYPh6D1P+pZnzinCs6DvMq86yAeNqoqzM=
k8s.io/cli-runtime v0.36.1/go.mod h1:ZQWHGt8xAF7KnviB79vX0lYNyUUqKIpU+LQg7exuFAw=
-k8s.io/client-go v0.36.1 h1:FN/K8QIT2CEDt+2WB2HnWrUANZ50AP5GII43/SP2JR0=
-k8s.io/client-go v0.36.1/go.mod h1:s6rAnCtTGYDQnpNjEhSaISV+2O8jwruZ6m3QOYBFbtU=
+k8s.io/client-go v0.36.3 h1:M4JdVzXxYcZk4fGpfDdYnxSwhLKWCFoQsHW6t+z8Hfg=
+k8s.io/client-go v0.36.3/go.mod h1:gcPwr0c87vjjG6HB6pWEqOeuYVoXSsREjzux2j6GF30=
k8s.io/component-base v0.36.1 h1:iG6GsELftXqTNG9HG6kiVjatSgAw1sf5pJ6R5a6N0kA=
k8s.io/component-base v0.36.1/go.mod h1:nf9XPlntRdqO6WMeEWAA5F93Y4ICZQdeT9GeqLDB3JI=
k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc=
@@ -920,10 +922,10 @@ k8s.io/kubectl v0.36.1 h1:96HqS9twIdHM0MlJLTwbo14b9kUKPkOzZ4tlRDLv4qI=
k8s.io/kubectl v0.36.1/go.mod h1:/DGPAIewKsFWF9VFgGvkPhao2Ev4SNuE3BioZo8yPbk=
k8s.io/metrics v0.36.1 h1:MQPb+G4RhrKEpt8NETPssbW8QgGUc4Jbqu1jx+kPqGk=
k8s.io/metrics v0.36.1/go.mod h1:xqS8XcWLjDzo6E7DJm/GfjKpRKdN5/MtJAQFuV6nLUc=
-k8s.io/streaming v0.36.1 h1:L+K68n4Gg940BGNNYtUBvL1WTLL0YnKT3s+P1MNAmR4=
-k8s.io/streaming v0.36.1/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
-k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM=
-k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
+k8s.io/streaming v0.36.3 h1:9rAaqBk0C0Pc7+/fqGekj07NV+/Xrew58p647A0JT8w=
+k8s.io/streaming v0.36.3/go.mod h1:z6fV3D+NVkoeqRMtWwlUZK6U17SY/LqNzOxWL6GyR/s=
+k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3 h1:jVkFFVfXdXP74B/zbO3hM3hpSFD0xvhQ5U686DPurkE=
+k8s.io/utils v0.0.0-20260707023825-cf1189d6abe3/go.mod h1:M2s5JB1lIYP3jzZdorPLHXIPJzt9vv2muW5a6L9DtNM=
oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc=
oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o=
periph.io/x/host/v3 v3.8.5 h1:g4g5xE1XZtDiGl1UAJaUur1aT7uNiFLMkyMEiZ7IHII=
@@ -950,7 +952,7 @@ sigs.k8s.io/mcs-api v0.5.0 h1:UMgKuFFIRVEy4LR2LQovPnBkV9rgmZMT+sJO7MkaVj4=
sigs.k8s.io/mcs-api v0.5.0/go.mod h1:zZ5CK8uS6HaLkxY4HqsmcBHfzHuNMrY2uJy8T7jffK4=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
-sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8=
-sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.3 h1:u08YRbVUi59ri4YD6cg0UqNM4Dimn0sIl+wldcx5PYw=
+sigs.k8s.io/structured-merge-diff/v6 v6.3.3/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
diff --git a/internal/cmd/cell/cell.go b/internal/cmd/cell/cell.go
index a89dd94f..b4c490b3 100644
--- a/internal/cmd/cell/cell.go
+++ b/internal/cmd/cell/cell.go
@@ -7,6 +7,7 @@ import (
"fmt"
"os"
+ nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
"github.com/spf13/cobra"
"golang.org/x/sync/errgroup"
"k8s.io/apimachinery/pkg/runtime"
@@ -22,11 +23,13 @@ import (
ipamv1alpha1 "go.miloapis.com/ipam/pkg/apis/ipam/v1alpha1"
locationsv1alpha1 "go.miloapis.com/locations/api/v1alpha1"
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
networkingv1alpha1 "go.datum.net/network-services-operator/api/v1alpha1"
"go.datum.net/network-services-operator/internal/cmd/clusterdiscovery"
"go.datum.net/network-services-operator/internal/config"
"go.datum.net/network-services-operator/internal/controller"
+ galacticcontroller "go.datum.net/network-services-operator/internal/galactic/controller"
"go.datum.net/network-services-operator/internal/logging"
)
@@ -46,6 +49,8 @@ func init() {
utilruntime.Must(locationsv1alpha1.AddToScheme(scheme))
utilruntime.Must(networkingv1alpha1.AddToScheme(scheme))
utilruntime.Must(ipamv1alpha1.AddToScheme(scheme))
+ utilruntime.Must(cloudv1alpha1.AddToScheme(scheme))
+ utilruntime.Must(nadv1.AddToScheme(scheme))
}
// NewCommand builds the cell controller manager command.
@@ -285,7 +290,28 @@ func controllerRegistrations(
ipamClients controller.IPAMClientFactory,
hubCluster cluster.Cluster,
) []namedSetup {
- registrations := make([]namedSetup, 0, 5)
+ registrations := make([]namedSetup, 0, 8)
+ if serverConfig.Galactic.Enabled {
+ registrations = append(registrations,
+ namedSetup{"galactic-networkcontext", func() (bool, error) {
+ return true, (&galacticcontroller.NetworkContextReconciler{
+ Client: mgr.GetLocalManager().GetClient(), Scheme: mgr.GetLocalManager().GetScheme(),
+ }).SetupWithManager(mgr.GetLocalManager())
+ }},
+ namedSetup{"galactic-subnet", func() (bool, error) {
+ return true, (&galacticcontroller.SubnetReconciler{
+ Client: mgr.GetLocalManager().GetClient(), Scheme: mgr.GetLocalManager().GetScheme(),
+ }).SetupWithManager(mgr.GetLocalManager())
+ }},
+ namedSetup{"galactic-networkinterface", func() (bool, error) {
+ return true, (&galacticcontroller.NetworkInterfaceReconciler{
+ Client: mgr.GetLocalManager().GetClient(), Scheme: mgr.GetLocalManager().GetScheme(),
+ APIReader: mgr.GetLocalManager().GetAPIReader(),
+ AttachmentMode: cloudv1alpha1.VPCAttachmentInterfaceMode(serverConfig.Galactic.AttachmentMode),
+ }).SetupWithManager(mgr.GetLocalManager())
+ }},
+ )
+ }
registrations = append(registrations,
namedSetup{"networkinterfaceclaim", func() (bool, error) {
return true, (&controller.NetworkInterfaceClaimReconciler{
diff --git a/internal/cmd/cell/cell_test.go b/internal/cmd/cell/cell_test.go
new file mode 100644
index 00000000..5128657c
--- /dev/null
+++ b/internal/cmd/cell/cell_test.go
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: AGPL-3.0-only
+
+package cell
+
+import (
+ "slices"
+ "testing"
+
+ "go.datum.net/network-services-operator/internal/config"
+)
+
+func TestGalacticControllersRequireEnablement(t *testing.T) {
+ for _, tt := range []struct {
+ name string
+ enabled bool
+ }{
+ {name: "disabled"},
+ {name: "enabled", enabled: true},
+ } {
+ t.Run(tt.name, func(t *testing.T) {
+ cfg := config.CellControllerManager{
+ Galactic: config.GalacticCellConfig{Enabled: tt.enabled, AttachmentMode: "Hypervisor"},
+ }
+ registrations := controllerRegistrations(nil, cfg, nil, nil)
+ names := make([]string, 0, len(registrations))
+ for _, registration := range registrations {
+ names = append(names, registration.name)
+ }
+ for _, name := range []string{
+ "galactic-networkcontext", "galactic-subnet", "galactic-networkinterface",
+ } {
+ if got := slices.Contains(names, name); got != tt.enabled {
+ t.Errorf("controller %q registered = %t, want %t", name, got, tt.enabled)
+ }
+ }
+ })
+ }
+}
diff --git a/internal/config/cell.go b/internal/config/cell.go
index bf34e5af..20459b4e 100644
--- a/internal/config/cell.go
+++ b/internal/config/cell.go
@@ -58,6 +58,15 @@ type CellControllerManager struct {
// This field is temporary. It goes away once no cell reports Configured for
// nso_cell_location_identity_source.
Location LocationConfig `json:"location,omitempty"`
+
+ // Galactic enables this cell to reconcile downstream Galactic VPCs and
+ // attachments once the Galactic CRDs are installed.
+ Galactic GalacticCellConfig `json:"galactic,omitempty"`
+}
+
+type GalacticCellConfig struct {
+ Enabled bool `json:"enabled,omitempty"`
+ AttachmentMode string `json:"attachmentMode,omitempty"`
}
// Validate reports whether the cell configuration can serve a cell.
@@ -68,5 +77,12 @@ func (c *CellControllerManager) Validate() error {
if err := c.Federation.validate(); err != nil {
return fmt.Errorf("federation: %w", err)
}
+ if c.Galactic.Enabled {
+ switch c.Galactic.AttachmentMode {
+ case "Netns", "Hypervisor", "HypervisorDeclared":
+ default:
+ return fmt.Errorf("galactic.attachmentMode must be Netns, Hypervisor, or HypervisorDeclared")
+ }
+ }
return nil
}
diff --git a/internal/config/cell_test.go b/internal/config/cell_test.go
index d5007d1a..3943a9bd 100644
--- a/internal/config/cell_test.go
+++ b/internal/config/cell_test.go
@@ -76,6 +76,19 @@ func TestCellControllerManager_Validate(t *testing.T) {
mutate: func(c *CellControllerManager) { c.Federation.KubeconfigPath = "" },
wantSub: "federation: kubeconfigPath is required",
},
+ {
+ name: "galactic enabled for hypervisor cell",
+ mutate: func(c *CellControllerManager) {
+ c.Galactic = GalacticCellConfig{Enabled: true, AttachmentMode: "Hypervisor"}
+ },
+ },
+ {
+ name: "galactic enabled without attachment mode",
+ mutate: func(c *CellControllerManager) {
+ c.Galactic.Enabled = true
+ },
+ wantSub: "galactic.attachmentMode",
+ },
}
for _, tt := range tests {
diff --git a/internal/config/zz_generated.deepcopy.go b/internal/config/zz_generated.deepcopy.go
index 1ac184cc..6caf90ce 100644
--- a/internal/config/zz_generated.deepcopy.go
+++ b/internal/config/zz_generated.deepcopy.go
@@ -43,6 +43,7 @@ func (in *CellControllerManager) DeepCopyInto(out *CellControllerManager) {
out.IPAM = in.IPAM
out.Federation = in.Federation
out.Location = in.Location
+ out.Galactic = in.Galactic
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CellControllerManager.
diff --git a/internal/controller/networkinterfaceclaim_controller_test.go b/internal/controller/networkinterfaceclaim_controller_test.go
index 6e7a72e8..805d810f 100644
--- a/internal/controller/networkinterfaceclaim_controller_test.go
+++ b/internal/controller/networkinterfaceclaim_controller_test.go
@@ -1955,16 +1955,20 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) {
}
require.NoError(t, s.client.Status().Update(s.ctx, iface))
+ expectedGroup := "cloud.datumapis.com"
+ expectedProgrammed := metav1.ConditionTrue
requireAttachmentReported := func(stage string) {
t.Helper()
reported, err := s.getInterface("slot-0-eth0")
require.NoError(t, err)
- require.Equal(t, metav1.ConditionTrue,
+ require.Equal(t, expectedProgrammed,
apimeta.FindStatusCondition(reported.Status.Conditions,
networkingv1alpha.NetworkInterfaceProgrammed).Status,
"%s reverted the condition the data plane owns", stage)
require.Equal(t, "0000000ju", reported.Status.VPC, "%s cleared status.vpc", stage)
require.NotNil(t, reported.Status.AttachmentRef, "%s cleared status.attachmentRef", stage)
+ require.Equal(t, expectedGroup, reported.Status.AttachmentRef.APIGroup,
+ "%s changed the data plane's attachment API group", stage)
}
s.reconcile(s.getClaim("slot-0-eth0"))
@@ -1973,12 +1977,42 @@ func TestExternalAttachmentStatusSurvivesNSO(t *testing.T) {
s.reconcileInterface("slot-0-eth0")
requireAttachmentReported("an interface reconcile")
+ // Galactic takes over the attachment under its consolidated API group.
+ // NSO must preserve the replacement reference through release and rebind.
+ iface, err = s.getInterface("slot-0-eth0")
+ require.NoError(t, err)
+ iface.Status.AttachmentRef.APIGroup = "galactic.datumapis.com"
+ apimeta.SetStatusCondition(&iface.Status.Conditions, metav1.Condition{
+ Type: networkingv1alpha.NetworkInterfaceProgrammed,
+ Status: metav1.ConditionUnknown,
+ Reason: "APIGroupMigration",
+ Message: "waiting for the Galactic attachment to be programmed",
+ })
+ require.NoError(t, s.client.Status().Update(s.ctx, iface))
+ expectedGroup = "galactic.datumapis.com"
+ expectedProgrammed = metav1.ConditionUnknown
+ s.reconcile(s.getClaim("slot-0-eth0"))
+ requireAttachmentReported("a migration requeue")
+
// Retain unbinds the interface and a replacement claim adopts it.
s.deleteClaim(s.getClaim("slot-0-eth0"))
requireAttachmentReported("release under Retain")
s.reconcile(s.createClaim("slot-0-eth0", spec))
requireAttachmentReported("a rebind")
+
+ // Galactic reports success only after its new advertisement is live.
+ iface, err = s.getInterface("slot-0-eth0")
+ require.NoError(t, err)
+ apimeta.SetStatusCondition(&iface.Status.Conditions, metav1.Condition{
+ Type: networkingv1alpha.NetworkInterfaceProgrammed,
+ Status: metav1.ConditionTrue,
+ Reason: "Advertised",
+ })
+ require.NoError(t, s.client.Status().Update(s.ctx, iface))
+ expectedProgrammed = metav1.ConditionTrue
+ s.reconcile(s.getClaim("slot-0-eth0"))
+ requireAttachmentReported("new Galactic programming")
}
// A retained interface outlives the holder that reported it, and the next claim
diff --git a/internal/galactic/controller/networkcontext_controller.go b/internal/galactic/controller/networkcontext_controller.go
new file mode 100644
index 00000000..6877668a
--- /dev/null
+++ b/internal/galactic/controller/networkcontext_controller.go
@@ -0,0 +1,251 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package controller
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "time"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
+
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
+ networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
+ "go.datum.net/network-services-operator/internal/galactic/identifier"
+)
+
+const reasonAwaitingFabricIdentity = "AwaitingFabricIdentity"
+
+// NetworkContextReconciler gives a network's presence in one location its
+// data-plane identity: one VPC per NetworkContext, carrying the base62 VPC
+// identifier the whole galactic fabric keys on.
+type NetworkContextReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkcontexts,verbs=get;list;watch
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=networkfabricidentities,verbs=get;list;watch
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=subnets,verbs=get;list;watch
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs/status,verbs=get;update;patch
+
+func (r *NetworkContextReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ var networkContext networkingv1alpha.NetworkContext
+ if err := r.Get(ctx, req.NamespacedName, &networkContext); err != nil {
+ return ctrl.Result{}, client.IgnoreNotFound(err)
+ }
+ if !networkContext.DeletionTimestamp.IsZero() {
+ return ctrl.Result{}, nil
+ }
+
+ networks, err := r.networksForContext(ctx, &networkContext)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if len(networks) == 0 {
+ // The VPC address space comes from the Subnets IPAM allocated for this
+ // location, and VPCSpec is immutable once written.
+ return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
+ }
+
+ vpc := &cloudv1alpha1.VPC{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: networkContext.Name,
+ Namespace: networkContext.Namespace,
+ },
+ }
+ if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, vpc, func() error {
+ if vpc.CreationTimestamp.IsZero() {
+ vpc.Spec.Networks = networks
+ }
+ return controllerutil.SetControllerReference(&networkContext, vpc, r.Scheme)
+ }); err != nil {
+ return ctrl.Result{}, fmt.Errorf("reconcile VPC %s: %w", vpc.Name, err)
+ }
+
+ // Everything below is conditioned on the VPC not having an identifier yet. A
+ // VPC that already has one keeps it: the VRF device is named from it and the
+ // Route Target derives from it, so renumbering a live VPC would rename its
+ // interface and change its routes under running traffic.
+ if vpc.Status.VPC == "" {
+ identity, found, err := r.fabricIdentity(ctx, &networkContext)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if !found {
+ return ctrl.Result{}, r.markAwaitingFabricIdentity(ctx, vpc)
+ }
+ encoded, err := identifier.VPCBase62(uint64(identity))
+ if err != nil {
+ return ctrl.Result{}, fmt.Errorf("encode fabric identity %d for VPC %s: %w", identity, vpc.Name, err)
+ }
+ vpc.Status.VPC = encoded
+ }
+ vpc.Status.ObservedGeneration = vpc.Generation
+ meta.SetStatusCondition(&vpc.Status.Conditions, metav1.Condition{
+ Type: cloudv1alpha1.ConditionTypeReady,
+ Status: metav1.ConditionTrue,
+ Reason: "IdentifierAllocated",
+ Message: fmt.Sprintf("VPC identifier %s allocated", vpc.Status.VPC),
+ ObservedGeneration: vpc.Generation,
+ })
+ if err := r.Status().Update(ctx, vpc); err != nil {
+ return ctrl.Result{}, fmt.Errorf("update VPC %s status: %w", vpc.Name, err)
+ }
+
+ return ctrl.Result{}, nil
+}
+
+// fabricIdentity reads the identity carried to this cell for the context's
+// network. It is one object per network, named after the network, in the
+// network's namespace. A missing one is an ordinary answer, not a failure.
+//
+// The identity is allocated centrally, once for the whole network, and carried
+// to each cell the network reaches. Deriving the VPC identifier from it is what
+// makes two locations of one network the same network on the fabric; drawing a
+// random value per location, which is what this used to do, made them two.
+//
+// A VPC whose identity has not arrived waits, and there is deliberately nothing
+// else it can do. The identifier is immutable, so a random value taken while
+// waiting is permanent, and a network whose locations then disagree is worse
+// than one with no VPC at all: a NetworkService spanning them binds no VRF and
+// fails every request, including through healthy members. Waiting ends the
+// moment the identity lands. A random draw never ends.
+func (r *NetworkContextReconciler) fabricIdentity(
+ ctx context.Context, networkContext *networkingv1alpha.NetworkContext,
+) (int64, bool, error) {
+ networkName := networkContext.Spec.Network.Name
+ if networkName == "" {
+ return 0, false, nil
+ }
+
+ var identity cloudv1alpha1.NetworkFabricIdentity
+ key := client.ObjectKey{Namespace: networkContext.Namespace, Name: networkName}
+ if err := r.Get(ctx, key, &identity); err != nil {
+ if apierrors.IsNotFound(err) {
+ return 0, false, nil
+ }
+ return 0, false, fmt.Errorf("read the fabric identity for network %q: %w", networkName, err)
+ }
+ if identity.Spec.Identity == 0 {
+ return 0, false, nil
+ }
+ return identity.Spec.Identity, true, nil
+}
+
+// markAwaitingFabricIdentity says out loud that the VPC has no identifier yet
+// and why, so a network stuck waiting on a central allocation is visible as
+// that rather than as a VPC that silently never became ready.
+func (r *NetworkContextReconciler) markAwaitingFabricIdentity(
+ ctx context.Context, vpc *cloudv1alpha1.VPC,
+) error {
+ vpc.Status.ObservedGeneration = vpc.Generation
+ meta.SetStatusCondition(&vpc.Status.Conditions, metav1.Condition{
+ Type: cloudv1alpha1.ConditionTypeReady,
+ Status: metav1.ConditionFalse,
+ Reason: reasonAwaitingFabricIdentity,
+ Message: "Waiting for the identity the fabric knows this network by",
+ ObservedGeneration: vpc.Generation,
+ })
+ if err := r.Status().Update(ctx, vpc); err != nil {
+ return fmt.Errorf("update VPC %s status: %w", vpc.Name, err)
+ }
+ return nil
+}
+
+// networksForContext collects the CIDRs IPAM allocated for this location.
+func (r *NetworkContextReconciler) networksForContext(
+ ctx context.Context, networkContext *networkingv1alpha.NetworkContext,
+) ([]cloudv1alpha1.Network, error) {
+ var subnets networkingv1alpha.SubnetList
+ if err := r.List(ctx, &subnets, client.InNamespace(networkContext.Namespace)); err != nil {
+ return nil, fmt.Errorf("list subnets: %w", err)
+ }
+
+ networks := make([]cloudv1alpha1.Network, 0, len(subnets.Items))
+ for _, subnet := range subnets.Items {
+ if subnet.Spec.NetworkContext.Name != networkContext.Name {
+ continue
+ }
+ start, prefixLength, ok := subnetRange(&subnet)
+ if !ok {
+ continue
+ }
+ networks = append(networks, cloudv1alpha1.Network(
+ fmt.Sprintf("%s/%d", start, prefixLength)))
+ }
+ slices.Sort(networks)
+ return networks, nil
+}
+
+// subnetRange reads a subnet's allocated range, preferring status and falling
+// back to spec. A location's copy arrives by propagation, which carries spec
+// and never status.
+func subnetRange(subnet *networkingv1alpha.Subnet) (string, int32, bool) {
+ if subnet.Status.StartAddress != nil && subnet.Status.PrefixLength != nil {
+ return *subnet.Status.StartAddress, *subnet.Status.PrefixLength, true
+ }
+ if subnet.Spec.StartAddress != "" && subnet.Spec.PrefixLength != 0 {
+ return subnet.Spec.StartAddress, subnet.Spec.PrefixLength, true
+ }
+ return "", 0, false
+}
+
+// SetupWithManager registers the reconciler with the manager.
+func (r *NetworkContextReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&networkingv1alpha.NetworkContext{}).
+ Owns(&cloudv1alpha1.VPC{}).
+ Watches(&cloudv1alpha1.NetworkFabricIdentity{},
+ handler.EnqueueRequestsFromMapFunc(r.contextsForFabricIdentity)).
+ Named("networkcontext").
+ Complete(r)
+}
+
+// contextsForFabricIdentity wakes a network's presences the moment its identity
+// reaches this cell, so a VPC waiting on one takes it immediately instead of
+// sitting out its poll interval.
+func (r *NetworkContextReconciler) contextsForFabricIdentity(
+ ctx context.Context, object client.Object,
+) []reconcile.Request {
+ var contexts networkingv1alpha.NetworkContextList
+ if err := r.List(ctx, &contexts, client.InNamespace(object.GetNamespace())); err != nil {
+ return nil
+ }
+
+ requests := make([]reconcile.Request, 0, len(contexts.Items))
+ for i := range contexts.Items {
+ if contexts.Items[i].Spec.Network.Name != object.GetName() {
+ continue
+ }
+ requests = append(requests, reconcile.Request{
+ NamespacedName: client.ObjectKeyFromObject(&contexts.Items[i]),
+ })
+ }
+ return requests
+}
diff --git a/internal/galactic/controller/networkcontext_controller_test.go b/internal/galactic/controller/networkcontext_controller_test.go
new file mode 100644
index 00000000..c86139e4
--- /dev/null
+++ b/internal/galactic/controller/networkcontext_controller_test.go
@@ -0,0 +1,243 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package controller
+
+import (
+ "context"
+ "testing"
+
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
+ networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
+)
+
+// A location's Subnet arrives by propagation, which carries spec and never
+// status, so the allocated range has to be readable from either.
+func TestSubnetRangePrefersStatusAndFallsBackToSpec(t *testing.T) {
+ start := "fd00::"
+ var length int32 = 48
+
+ withStatus := &networkingv1alpha.Subnet{
+ Spec: networkingv1alpha.SubnetSpec{StartAddress: "fd20::", PrefixLength: 64},
+ Status: networkingv1alpha.SubnetStatus{StartAddress: &start, PrefixLength: &length},
+ }
+ gotStart, gotLength, ok := subnetRange(withStatus)
+ if !ok || gotStart != "fd00::" || gotLength != 48 {
+ t.Fatalf("status should win when present, got %q/%d ok=%v", gotStart, gotLength, ok)
+ }
+
+ specOnly := &networkingv1alpha.Subnet{
+ Spec: networkingv1alpha.SubnetSpec{StartAddress: "fd20::", PrefixLength: 64},
+ }
+ gotStart, gotLength, ok = subnetRange(specOnly)
+ if !ok || gotStart != "fd20::" || gotLength != 64 {
+ t.Fatalf("a propagated copy carries spec only, got %q/%d ok=%v", gotStart, gotLength, ok)
+ }
+
+ if _, _, ok := subnetRange(&networkingv1alpha.Subnet{}); ok {
+ t.Fatal("an unallocated subnet should yield nothing")
+ }
+}
+
+const (
+ vpcTestNamespace = "ns-project"
+ vpcTestNetwork = "prod"
+ vpcTestLocation = "us-central-1"
+
+ // The identity the fabric knows the test network by, and the identifier a
+ // VPC derives from it.
+ vpcTestIdentity = 16
+ vpcTestIdentifier = "g"
+)
+
+type vpcFixture struct {
+ t *testing.T
+ ctx context.Context
+ client client.Client
+ reconciler *NetworkContextReconciler
+}
+
+// newVPCFixture stands up one network present at one location, with the subnet
+// that gives the VPC an address space to be created for.
+func newVPCFixture(t *testing.T, objects ...client.Object) *vpcFixture {
+ t.Helper()
+
+ scheme := runtime.NewScheme()
+ if err := networkingv1alpha.AddToScheme(scheme); err != nil {
+ t.Fatalf("build the networking scheme: %v", err)
+ }
+ if err := cloudv1alpha1.AddToScheme(scheme); err != nil {
+ t.Fatalf("build the cloud scheme: %v", err)
+ }
+
+ presence := &networkingv1alpha.NetworkContext{}
+ presence.Namespace = vpcTestNamespace
+ presence.Name = vpcTestNetwork + "-" + vpcTestLocation
+ presence.Spec.Network = networkingv1alpha.LocalNetworkRef{Name: vpcTestNetwork}
+
+ subnet := &networkingv1alpha.Subnet{}
+ subnet.Namespace = vpcTestNamespace
+ subnet.Name = presence.Name
+ subnet.Spec.NetworkContext = networkingv1alpha.LocalNetworkContextRef{Name: presence.Name}
+ subnet.Spec.StartAddress = "fd00::"
+ subnet.Spec.PrefixLength = 48
+
+ all := append([]client.Object{presence, subnet}, objects...)
+ fakeClient := fake.NewClientBuilder().WithScheme(scheme).
+ WithStatusSubresource(&cloudv1alpha1.VPC{}).
+ WithObjects(all...).Build()
+
+ return &vpcFixture{
+ t: t,
+ ctx: context.Background(),
+ client: fakeClient,
+ reconciler: &NetworkContextReconciler{Client: fakeClient, Scheme: scheme},
+ }
+}
+
+func (f *vpcFixture) reconcile() ctrl.Result {
+ f.t.Helper()
+ result, err := f.reconciler.Reconcile(f.ctx, ctrl.Request{NamespacedName: types.NamespacedName{
+ Namespace: vpcTestNamespace,
+ Name: vpcTestNetwork + "-" + vpcTestLocation,
+ }})
+ if err != nil {
+ f.t.Fatalf("reconcile: %v", err)
+ }
+ return result
+}
+
+func (f *vpcFixture) vpc() *cloudv1alpha1.VPC {
+ f.t.Helper()
+ vpc := &cloudv1alpha1.VPC{}
+ key := types.NamespacedName{Namespace: vpcTestNamespace, Name: vpcTestNetwork + "-" + vpcTestLocation}
+ if err := f.client.Get(f.ctx, key, vpc); err != nil {
+ f.t.Fatalf("read the VPC: %v", err)
+ }
+ return vpc
+}
+
+func fabricIdentityObject() *cloudv1alpha1.NetworkFabricIdentity {
+ object := &cloudv1alpha1.NetworkFabricIdentity{}
+ object.Namespace = vpcTestNamespace
+ object.Name = vpcTestNetwork
+ object.Spec.Identity = vpcTestIdentity
+ object.Spec.NetworkRef = cloudv1alpha1.NetworkFabricIdentityNetworkRef{Name: vpcTestNetwork}
+ return object
+}
+
+// The whole point: every location of one network reaches the same identifier,
+// because every one of them reads the same centrally allocated identity.
+func TestVPCTakesItsIdentifierFromTheNetworksFabricIdentity(t *testing.T) {
+ fixture := newVPCFixture(t, fabricIdentityObject())
+
+ fixture.reconcile()
+
+ vpc := fixture.vpc()
+ if vpc.Status.VPC != vpcTestIdentifier {
+ t.Fatalf("VPC identifier: got %q, want %q", vpc.Status.VPC, vpcTestIdentifier)
+ }
+ if condition := meta.FindStatusCondition(vpc.Status.Conditions, cloudv1alpha1.ConditionTypeReady); condition == nil ||
+ condition.Status != metav1.ConditionTrue {
+ t.Fatalf("a VPC with an identifier should be ready, got %+v", condition)
+ }
+}
+
+// A network whose identity has not reached this cell yet waits, and writes
+// nothing. The identifier is immutable, so a value taken while waiting is
+// permanent, and locations that disagree bind no VRF at all.
+func TestVPCWaitsForAnIdentityThatHasNotArrived(t *testing.T) {
+ fixture := newVPCFixture(t)
+
+ if result := fixture.reconcile(); result.RequeueAfter != 0 {
+ t.Fatalf("the identity's arrival is the trigger, not a poll, got %v", result)
+ }
+
+ vpc := fixture.vpc()
+ if vpc.Status.VPC != "" {
+ t.Fatalf("no identifier should be written while waiting, got %q", vpc.Status.VPC)
+ }
+ condition := meta.FindStatusCondition(vpc.Status.Conditions, cloudv1alpha1.ConditionTypeReady)
+ if condition == nil || condition.Status != metav1.ConditionFalse ||
+ condition.Reason != "AwaitingFabricIdentity" {
+ t.Fatalf("the wait should be visible on the VPC, got %+v", condition)
+ }
+}
+
+// The identity landing is what ends the wait, and the identifier that follows
+// is the derived one rather than a random draw.
+func TestVPCTakesTheIdentityOnceItArrives(t *testing.T) {
+ fixture := newVPCFixture(t)
+
+ fixture.reconcile()
+ if got := fixture.vpc().Status.VPC; got != "" {
+ t.Fatalf("no identifier should be written while waiting, got %q", got)
+ }
+
+ if err := fixture.client.Create(fixture.ctx, fabricIdentityObject()); err != nil {
+ t.Fatalf("publish the identity: %v", err)
+ }
+ fixture.reconcile()
+
+ if got := fixture.vpc().Status.VPC; got != vpcTestIdentifier {
+ t.Fatalf("VPC identifier: got %q, want %q", got, vpcTestIdentifier)
+ }
+}
+
+// Renumbering a live VPC would rename its VRF device and change its Route
+// Target under running traffic, so an identifier already written stays written
+// even when it disagrees with the identity that later arrived.
+func TestVPCKeepsAnIdentifierItAlreadyHas(t *testing.T) {
+ existing := &cloudv1alpha1.VPC{}
+ existing.Namespace = vpcTestNamespace
+ existing.Name = vpcTestNetwork + "-" + vpcTestLocation
+ existing.Status.VPC = "R2POk4jT"
+
+ fixture := newVPCFixture(t, existing, fabricIdentityObject())
+
+ fixture.reconcile()
+
+ if got := fixture.vpc().Status.VPC; got != "R2POk4jT" {
+ t.Fatalf("an allocated VPC must keep its identifier, got %q", got)
+ }
+}
+
+// An identity reaching the cell has to wake the presences waiting on it, or a
+// VPC would sit out its poll interval for a value already there.
+func TestFabricIdentityWakesTheNetworksPresences(t *testing.T) {
+ fixture := newVPCFixture(t)
+
+ requests := fixture.reconciler.contextsForFabricIdentity(fixture.ctx, fabricIdentityObject())
+
+ if len(requests) != 1 || requests[0].Name != vpcTestNetwork+"-"+vpcTestLocation {
+ t.Fatalf("the network's presence should be enqueued, got %v", requests)
+ }
+
+ other := fabricIdentityObject()
+ other.Name = "unrelated"
+ if requests := fixture.reconciler.contextsForFabricIdentity(fixture.ctx, other); len(requests) != 0 {
+ t.Fatalf("another network's identity should enqueue nothing, got %v", requests)
+ }
+}
diff --git a/internal/galactic/controller/networkinterface_controller.go b/internal/galactic/controller/networkinterface_controller.go
new file mode 100644
index 00000000..416c4f2f
--- /dev/null
+++ b/internal/galactic/controller/networkinterface_controller.go
@@ -0,0 +1,459 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package controller
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
+ networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
+ "go.datum.net/network-services-operator/internal/galactic/galactic"
+ "go.datum.net/network-services-operator/internal/galactic/identifier"
+)
+
+const (
+ // LabelVPC records the base62 VPC identifier a NAD attaches to.
+ LabelVPC = "galactic.datumapis.com/vpc"
+
+ // LabelVPCAttachment records the base62 attachment identifier a NAD holds.
+ // The NAD is the allocation record for that identifier.
+ LabelVPCAttachment = "galactic.datumapis.com/vpc-attachment"
+ legacyLabelVPC = "cloud.datumapis.com/vpc"
+ legacyLabelVPCAttachment = "cloud.datumapis.com/vpc-attachment"
+ legacyAttachmentKind = "VPCAttachment"
+ reasonAPIGroupMigration = "APIGroupMigration"
+
+ // AnnotationHostInterface records the host device this attachment will get,
+ // on the NAD, at the moment the NAD is written. The name is derived from the
+ // VPC and the attachment identifier, so it is known here — well before the
+ // CNI ADD that creates the device. A runtime that must name the device when
+ // it asks for an interface, rather than learn it from the ADD result, has
+ // nowhere else to read it in time. galactic writes the same key during ADD
+ // from the same inputs, so the two always agree.
+ AnnotationHostInterface = "k8s.v1.cni.cncf.io/host-interface"
+
+ // ConditionTypePrepared reports that the data plane's pre-Pod artifacts exist.
+ // Unlike Programmed, which only becomes true at CNI ADD, it is safe to gate
+ // Pod creation on. network-services-operator is adding the type in parallel.
+ ConditionTypePrepared = "Prepared"
+)
+
+// maxIdentifierAttempts bounds the retry loop that draws an unused identifier.
+const maxIdentifierAttempts = 100
+
+// NetworkInterfaceReconciler realizes a fulfilled NetworkInterface claim on the
+// galactic data plane.
+//
+// It creates the VPCAttachment and the NetworkAttachmentDefinition in one pass,
+// so it already holds every render input and never has to look sideways. Both
+// objects are per-interface, which keeps the attachment identifier and the tap
+// device name stable across instance replacement, and it publishes the
+// annotations a workload must carry so no infrastructure provider has to know
+// what a NAD is.
+type NetworkInterfaceReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+
+ // APIReader bypasses the cache when listing allocated identifiers, so a NAD
+ // written moments ago cannot be missed and its identifier reissued.
+ APIReader client.Reader
+
+ // AttachmentMode is how guests in this cell consume an interface. It is
+ // required configuration standing in for a capability class that does not
+ // exist yet, so a cell states what it is rather than defaulting. It covers
+ // every interface that states no mode of its own.
+ AttachmentMode cloudv1alpha1.VPCAttachmentInterfaceMode
+}
+
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaces,verbs=get;list;watch
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaces/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaceclaims,verbs=get;list;watch
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=networkinterfaceclaims/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs,verbs=get;list;watch
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcattachments,verbs=get;list;watch;create;update;patch;delete
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcattachments/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=k8s.cni.cncf.io,resources=network-attachment-definitions,verbs=get;list;watch;create;update;patch;delete
+
+func (r *NetworkInterfaceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ var networkInterface networkingv1alpha.NetworkInterface
+ if err := r.Get(ctx, req.NamespacedName, &networkInterface); err != nil {
+ return ctrl.Result{}, client.IgnoreNotFound(err)
+ }
+ if !networkInterface.DeletionTimestamp.IsZero() {
+ return ctrl.Result{}, nil
+ }
+ if !claimFulfilled(&networkInterface) {
+ return ctrl.Result{}, nil
+ }
+
+ var vpc cloudv1alpha1.VPC
+ vpcKey := types.NamespacedName{
+ Namespace: networkInterface.Namespace,
+ Name: networkInterface.Status.NetworkContextRef.Name,
+ }
+ if err := r.Get(ctx, vpcKey, &vpc); err != nil {
+ if apierrors.IsNotFound(err) {
+ return ctrl.Result{RequeueAfter: 10 * time.Second}, r.markPrepared(ctx, &networkInterface,
+ metav1.ConditionFalse, "AwaitingVPC", fmt.Sprintf("VPC %s does not exist yet", vpcKey.Name))
+ }
+ return ctrl.Result{}, fmt.Errorf("get VPC %s: %w", vpcKey, err)
+ }
+ if vpc.Status.VPC == "" {
+ return ctrl.Result{RequeueAfter: 10 * time.Second}, r.markPrepared(ctx, &networkInterface,
+ metav1.ConditionFalse, "AwaitingVPCIdentifier",
+ fmt.Sprintf("VPC %s has no identifier yet", vpc.Name))
+ }
+
+ attachment, err := r.reconcileAttachment(ctx, &networkInterface, &vpc)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ nad, err := r.reconcileNAD(ctx, attachment, &vpc, &networkInterface)
+ if err != nil {
+ return ctrl.Result{}, err
+ }
+ if err := r.publishAttachmentStatus(ctx, attachment, &vpc, nad); err != nil {
+ return ctrl.Result{}, err
+ }
+ return ctrl.Result{}, r.publishToInterface(ctx, &networkInterface, attachment, &vpc)
+}
+
+// claimFulfilled reports whether an interface is bound to a claim and holds
+// every address it must carry. Nothing can be rendered before that.
+func claimFulfilled(networkInterface *networkingv1alpha.NetworkInterface) bool {
+ if networkInterface.Status.Phase != networkingv1alpha.NetworkInterfacePhaseBound {
+ return false
+ }
+ if networkInterface.Status.NetworkContextRef == nil {
+ return false
+ }
+ return meta.IsStatusConditionTrue(networkInterface.Status.Conditions,
+ networkingv1alpha.NetworkInterfaceAllocated)
+}
+
+// reconcileAttachment creates the VPCAttachment for an interface. The controller
+// owns this object, not the infrastructure provider: it is the only component
+// that speaks both the workload vocabulary and the data plane's.
+func (r *NetworkInterfaceReconciler) reconcileAttachment(
+ ctx context.Context, networkInterface *networkingv1alpha.NetworkInterface, vpc *cloudv1alpha1.VPC,
+) (*cloudv1alpha1.VPCAttachment, error) {
+ attachment := &cloudv1alpha1.VPCAttachment{
+ ObjectMeta: metav1.ObjectMeta{Name: networkInterface.Name, Namespace: networkInterface.Namespace},
+ }
+ if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, attachment, func() error {
+ attachment.Spec.VPC = cloudv1alpha1.VPCRef{Name: vpc.Name}
+ attachment.Spec.InterfaceRef = &cloudv1alpha1.NetworkInterfaceRef{Name: networkInterface.Name}
+ attachment.Spec.Interface.Name = networkInterface.Spec.InterfaceName
+ attachment.Spec.Interface.Mode = r.attachmentMode(networkInterface)
+ attachment.Spec.Interface.Addresses = interfaceAddresses(networkInterface)
+ return controllerutil.SetControllerReference(networkInterface, attachment, r.Scheme)
+ }); err != nil {
+ return nil, fmt.Errorf("reconcile VPC attachment %s: %w", attachment.Name, err)
+ }
+ return attachment, nil
+}
+
+// reconcileNAD creates or updates the NAD the attachment owns.
+func (r *NetworkInterfaceReconciler) reconcileNAD(
+ ctx context.Context,
+ attachment *cloudv1alpha1.VPCAttachment,
+ vpc *cloudv1alpha1.VPC,
+ networkInterface *networkingv1alpha.NetworkInterface,
+) (*nadv1.NetworkAttachmentDefinition, error) {
+ nad := &nadv1.NetworkAttachmentDefinition{
+ ObjectMeta: metav1.ObjectMeta{Name: attachment.Name, Namespace: attachment.Namespace},
+ }
+
+ attachmentID := ""
+ if err := r.Get(ctx, client.ObjectKeyFromObject(nad), nad); err == nil {
+ attachmentID = nad.Labels[LabelVPCAttachment]
+ if attachmentID == "" {
+ attachmentID = nad.Labels[legacyLabelVPCAttachment]
+ }
+ } else if !apierrors.IsNotFound(err) {
+ return nil, fmt.Errorf("get NetworkAttachmentDefinition %s: %w", nad.Name, err)
+ }
+ if attachmentID == "" {
+ allocated, err := r.allocateAttachmentIdentifier(ctx, vpc.Status.VPC)
+ if err != nil {
+ return nil, err
+ }
+ attachmentID = allocated
+ }
+
+ addresses := make([]galactic.Address, 0, len(networkInterface.Spec.Addresses))
+ for _, address := range networkInterface.Spec.Addresses {
+ addresses = append(addresses, galactic.Address{
+ Address: address.Address,
+ Gateway: address.Gateway,
+ })
+ }
+ config, err := galactic.ConflistJSON(attachment.Name, masterPlugin(attachment.Spec.Interface.Mode),
+ vpc.Status.VPC, attachmentID, networkInterface.Spec.MTU, addresses,
+ declaresDevice(attachment.Spec.Interface.Mode))
+ if err != nil {
+ return nil, err
+ }
+
+ if _, err := controllerutil.CreateOrUpdate(ctx, r.Client, nad, func() error {
+ if nad.Labels == nil {
+ nad.Labels = map[string]string{}
+ }
+ nad.Labels[LabelVPC] = vpc.Status.VPC
+ nad.Labels[LabelVPCAttachment] = attachmentID
+ if nad.Annotations == nil {
+ nad.Annotations = map[string]string{}
+ }
+ nad.Annotations[AnnotationHostInterface] = galactic.HostInterfaceName(vpc.Status.VPC, attachmentID)
+ nad.Spec.Config = config
+ // The former cloud-group attachment owned this NAD. Its controller
+ // must be stopped before rollout; transfer ownership in the same write
+ // as the group/label update while retaining its stable attachment ID.
+ refs := nad.OwnerReferences[:0]
+ for _, ref := range nad.OwnerReferences {
+ if ref.APIVersion == "cloud.datumapis.com/v1alpha1" && ref.Kind == legacyAttachmentKind && ref.Name == attachment.Name {
+ continue
+ }
+ refs = append(refs, ref)
+ }
+ nad.OwnerReferences = refs
+ return controllerutil.SetControllerReference(attachment, nad, r.Scheme)
+ }); err != nil {
+ return nil, fmt.Errorf("reconcile NetworkAttachmentDefinition %s: %w", nad.Name, err)
+ }
+ return nad, nil
+}
+
+// attachmentMode resolves how one guest consumes its interface. An interface
+// that states a mode carries the workload's own requirement, so it wins. The
+// cell-wide mode covers everything else, which is every interface written
+// before a workload could state one.
+func (r *NetworkInterfaceReconciler) attachmentMode(
+ networkInterface *networkingv1alpha.NetworkInterface,
+) cloudv1alpha1.VPCAttachmentInterfaceMode {
+ switch networkInterface.Spec.AttachmentMode {
+ case networkingv1alpha.NetworkInterfaceAttachmentModeNetns:
+ return cloudv1alpha1.VPCAttachmentInterfaceModeNetns
+ case networkingv1alpha.NetworkInterfaceAttachmentModeHypervisor:
+ return cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor
+ case networkingv1alpha.NetworkInterfaceAttachmentModeHypervisorDeclared:
+ return cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared
+ default:
+ return r.AttachmentMode
+ }
+}
+
+// masterPlugin translates how a guest consumes an interface into the galactic
+// binary that realizes it. This is the only place the two vocabularies meet.
+func masterPlugin(mode cloudv1alpha1.VPCAttachmentInterfaceMode) string {
+ switch mode {
+ case cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor,
+ cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared:
+ return galactic.PluginTap
+ default:
+ return galactic.PluginVeth
+ }
+}
+
+// declaresDevice reports whether the tap plugin describes the device to the
+// hypervisor instead of leaving the hypervisor to discover it.
+func declaresDevice(mode cloudv1alpha1.VPCAttachmentInterfaceMode) bool {
+ return mode == cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared
+}
+
+// interfaceAddresses copies the addresses NSO allocated onto the attachment, so
+// the attachment describes itself without a second read.
+func interfaceAddresses(networkInterface *networkingv1alpha.NetworkInterface) []cloudv1alpha1.IPAddress {
+ addresses := make([]cloudv1alpha1.IPAddress, 0, len(networkInterface.Spec.Addresses))
+ for _, address := range networkInterface.Spec.Addresses {
+ addresses = append(addresses, cloudv1alpha1.IPAddress(address.Address))
+ }
+ return addresses
+}
+
+// allocateAttachmentIdentifier draws a random identifier unused within the VPC.
+// Random rather than lowest-free, so a freed identifier is not immediately
+// reissued while its BGPAdvertisement is still being garbage collected.
+func (r *NetworkInterfaceReconciler) allocateAttachmentIdentifier(ctx context.Context, vpc string) (string, error) {
+ used := make(map[string]struct{})
+ for _, label := range []string{LabelVPC, legacyLabelVPC} {
+ var nads nadv1.NetworkAttachmentDefinitionList
+ if err := r.APIReader.List(ctx, &nads, client.MatchingLabels{label: vpc}); err != nil {
+ return "", fmt.Errorf("list NetworkAttachmentDefinitions for VPC %s: %w", vpc, err)
+ }
+ for _, nad := range nads.Items {
+ for _, key := range []string{LabelVPCAttachment, legacyLabelVPCAttachment} {
+ if id := nad.Labels[key]; id != "" {
+ used[id] = struct{}{}
+ }
+ }
+ }
+ }
+
+ for range maxIdentifierAttempts {
+ candidate, err := identifier.RandomVPCAttachmentBase62()
+ if err != nil {
+ return "", err
+ }
+ if _, taken := used[candidate]; !taken {
+ return candidate, nil
+ }
+ }
+ return "", fmt.Errorf("no unused attachment identifier found in VPC %s after %d attempts",
+ vpc, maxIdentifierAttempts)
+}
+
+// publishAttachmentStatus records the allocated identifiers on the attachment.
+func (r *NetworkInterfaceReconciler) publishAttachmentStatus(
+ ctx context.Context,
+ attachment *cloudv1alpha1.VPCAttachment,
+ vpc *cloudv1alpha1.VPC,
+ nad *nadv1.NetworkAttachmentDefinition,
+) error {
+ attachment.Status.VPC = vpc.Status.VPC
+ attachment.Status.VPCAttachment = nad.Labels[LabelVPCAttachment]
+ attachment.Status.NetworkAttachmentDefinition = nad.Name
+ attachment.Status.ObservedGeneration = attachment.Generation
+ meta.SetStatusCondition(&attachment.Status.Conditions, metav1.Condition{
+ Type: cloudv1alpha1.ConditionTypeReady,
+ Status: metav1.ConditionTrue,
+ Reason: "AttachmentDefinitionReady",
+ Message: fmt.Sprintf("NetworkAttachmentDefinition %s is ready for use", nad.Name),
+ ObservedGeneration: attachment.Generation,
+ })
+ if err := r.Status().Update(ctx, attachment); err != nil {
+ return fmt.Errorf("update VPC attachment %s status: %w", client.ObjectKeyFromObject(attachment), err)
+ }
+ return nil
+}
+
+// publishToInterface records what realizes the interface and which VPC it landed
+// in, then reports Prepared so compute can release the Pod.
+func (r *NetworkInterfaceReconciler) publishToInterface(
+ ctx context.Context,
+ networkInterface *networkingv1alpha.NetworkInterface,
+ attachment *cloudv1alpha1.VPCAttachment,
+ vpc *cloudv1alpha1.VPC,
+) error {
+ ref := &networkingv1alpha.NetworkInterfaceAttachmentRef{
+ APIGroup: cloudv1alpha1.GroupVersion.Group,
+ Kind: legacyAttachmentKind,
+ Name: attachment.Name,
+ }
+ if old := networkInterface.Status.AttachmentRef; old != nil && old.APIGroup != ref.APIGroup {
+ // NSO preserves the provider-owned Programmed condition. A condition
+ // earned by the former API group cannot certify this new attachment;
+ // the new BGPAdvertisement reconciler will report it again.
+ meta.SetStatusCondition(&networkInterface.Status.Conditions, metav1.Condition{
+ Type: networkingv1alpha.NetworkInterfaceProgrammed,
+ Status: metav1.ConditionUnknown,
+ Reason: reasonAPIGroupMigration,
+ Message: "waiting for the Galactic attachment to be programmed",
+ ObservedGeneration: networkInterface.Generation,
+ })
+ }
+ networkInterface.Status.AttachmentRef = ref
+ networkInterface.Status.VPC = vpc.Status.VPC
+ return r.markPrepared(ctx, networkInterface, metav1.ConditionTrue, "AttachmentReady",
+ fmt.Sprintf("VPCAttachment %s and its attachment definition exist", attachment.Name))
+}
+
+// markPrepared reports whether the pre-Pod artifacts exist, on the interface and
+// on the claim holding it, which is what compute gates Pod creation on.
+func (r *NetworkInterfaceReconciler) markPrepared(
+ ctx context.Context,
+ networkInterface *networkingv1alpha.NetworkInterface,
+ status metav1.ConditionStatus,
+ reason, message string,
+) error {
+ condition := metav1.Condition{
+ Type: ConditionTypePrepared,
+ Status: status,
+ Reason: reason,
+ Message: message,
+ ObservedGeneration: networkInterface.Generation,
+ }
+ meta.SetStatusCondition(&networkInterface.Status.Conditions, condition)
+ if err := r.Status().Update(ctx, networkInterface); err != nil {
+ return fmt.Errorf("update network interface %s status: %w",
+ client.ObjectKeyFromObject(networkInterface), err)
+ }
+
+ if networkInterface.Spec.ClaimRef == nil {
+ return nil
+ }
+ var claim networkingv1alpha.NetworkInterfaceClaim
+ key := types.NamespacedName{Namespace: networkInterface.Namespace, Name: networkInterface.Spec.ClaimRef.Name}
+ if err := r.Get(ctx, key, &claim); err != nil {
+ return client.IgnoreNotFound(err)
+ }
+ claimCondition := condition
+ claimCondition.ObservedGeneration = claim.Generation
+ meta.SetStatusCondition(&claim.Status.Conditions, claimCondition)
+ // A claim can have Programmed and Ready from the old attachment even after
+ // its interface points at Galactic. Invalidate both until the new group's
+ // advertisement reports that the replacement attachment is carrying traffic.
+ if programmed := meta.FindStatusCondition(networkInterface.Status.Conditions,
+ networkingv1alpha.NetworkInterfaceProgrammed); programmed != nil &&
+ programmed.Status == metav1.ConditionUnknown && programmed.Reason == reasonAPIGroupMigration {
+ claimProgrammed := *programmed
+ claimProgrammed.Type = networkingv1alpha.NetworkInterfaceClaimProgrammed
+ claimProgrammed.ObservedGeneration = claim.Generation
+ meta.SetStatusCondition(&claim.Status.Conditions, claimProgrammed)
+ meta.SetStatusCondition(&claim.Status.Conditions, metav1.Condition{
+ Type: networkingv1alpha.NetworkInterfaceClaimReady,
+ Status: metav1.ConditionUnknown,
+ Reason: "NotProgrammed",
+ Message: "waiting for the Galactic attachment to be programmed",
+ ObservedGeneration: claim.Generation,
+ })
+ }
+ if err := r.Status().Update(ctx, &claim); err != nil {
+ return fmt.Errorf("update network interface claim %s status: %w", key, err)
+ }
+ return nil
+}
+
+// SetupWithManager registers the reconciler with the manager.
+func (r *NetworkInterfaceReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ // A NAD is owned by the attachment rather than the interface, but all three
+ // share a name and namespace, so mapping one back is an identity.
+ nadToInterface := func(_ context.Context, obj client.Object) []ctrl.Request {
+ return []ctrl.Request{{NamespacedName: client.ObjectKeyFromObject(obj)}}
+ }
+
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&networkingv1alpha.NetworkInterface{}).
+ Owns(&cloudv1alpha1.VPCAttachment{}).
+ Watches(&nadv1.NetworkAttachmentDefinition{}, handler.EnqueueRequestsFromMapFunc(nadToInterface)).
+ Named("networkinterface").
+ Complete(r)
+}
diff --git a/internal/galactic/controller/networkinterface_controller_test.go b/internal/galactic/controller/networkinterface_controller_test.go
new file mode 100644
index 00000000..c8d35113
--- /dev/null
+++ b/internal/galactic/controller/networkinterface_controller_test.go
@@ -0,0 +1,235 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package controller
+
+import (
+ "context"
+ "testing"
+
+ nadv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
+ apimeta "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
+ networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
+ "go.datum.net/network-services-operator/internal/galactic/galactic"
+)
+
+func TestReconcileNADTakesOverLegacyAttachment(t *testing.T) {
+ scheme := runtime.NewScheme()
+ if err := cloudv1alpha1.AddToScheme(scheme); err != nil {
+ t.Fatal(err)
+ }
+ if err := nadv1.AddToScheme(scheme); err != nil {
+ t.Fatal(err)
+ }
+ controller := true
+ old := &nadv1.NetworkAttachmentDefinition{ObjectMeta: metav1.ObjectMeta{
+ Name: "nic", Namespace: "tenant",
+ Labels: map[string]string{legacyLabelVPC: "1", legacyLabelVPCAttachment: "2"},
+ OwnerReferences: []metav1.OwnerReference{{APIVersion: "cloud.datumapis.com/v1alpha1", Kind: "VPCAttachment", Name: "nic", UID: types.UID("old"), Controller: &controller}},
+ }}
+ cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(old).Build()
+ r := &NetworkInterfaceReconciler{Client: cl, APIReader: cl, Scheme: scheme}
+ attachment := &cloudv1alpha1.VPCAttachment{ObjectMeta: metav1.ObjectMeta{Name: "nic", Namespace: "tenant", UID: types.UID("new")}}
+ attachment.Spec.Interface.Mode = cloudv1alpha1.VPCAttachmentInterfaceModeNetns
+ vpc := &cloudv1alpha1.VPC{}
+ vpc.Status.VPC = "1"
+ iface := &networkingv1alpha.NetworkInterface{}
+ iface.Spec.MTU = 1500
+ got, err := r.reconcileNAD(context.Background(), attachment, vpc, iface)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if got.Labels[LabelVPCAttachment] != "2" {
+ t.Fatalf("attachment ID changed: %q", got.Labels[LabelVPCAttachment])
+ }
+ if len(got.OwnerReferences) != 1 || got.OwnerReferences[0].APIVersion != cloudv1alpha1.GroupVersion.String() || got.OwnerReferences[0].UID != attachment.UID {
+ t.Fatalf("owner references = %#v", got.OwnerReferences)
+ }
+}
+
+func TestPublishToInterfaceReprogramsNSOAttachmentGroup(t *testing.T) {
+ scheme := runtime.NewScheme()
+ if err := networkingv1alpha.AddToScheme(scheme); err != nil {
+ t.Fatal(err)
+ }
+ if err := cloudv1alpha1.AddToScheme(scheme); err != nil {
+ t.Fatal(err)
+ }
+ iface := &networkingv1alpha.NetworkInterface{ObjectMeta: metav1.ObjectMeta{Name: "nic", Namespace: "tenant"}}
+ iface.Spec.ClaimRef = &networkingv1alpha.NetworkInterfaceClaimRef{Name: "claim"}
+ claim := &networkingv1alpha.NetworkInterfaceClaim{ObjectMeta: metav1.ObjectMeta{Name: "claim", Namespace: "tenant"}}
+ claim.Status.Conditions = []metav1.Condition{
+ {Type: networkingv1alpha.NetworkInterfaceClaimProgrammed, Status: metav1.ConditionTrue, Reason: "OldGroupProgrammed", LastTransitionTime: metav1.Now()},
+ {Type: networkingv1alpha.NetworkInterfaceClaimReady, Status: metav1.ConditionTrue, Reason: "OldGroupReady", LastTransitionTime: metav1.Now()},
+ }
+ iface.Status.AttachmentRef = &networkingv1alpha.NetworkInterfaceAttachmentRef{
+ APIGroup: "cloud.datumapis.com", Kind: "VPCAttachment", Name: "nic",
+ }
+ iface.Status.Conditions = []metav1.Condition{{
+ Type: networkingv1alpha.NetworkInterfaceProgrammed,
+ Status: metav1.ConditionTrue, Reason: "OldGroupProgrammed", LastTransitionTime: metav1.Now(),
+ }}
+ cl := fake.NewClientBuilder().WithScheme(scheme).WithStatusSubresource(iface, claim).WithObjects(iface, claim).Build()
+ r := &NetworkInterfaceReconciler{Client: cl}
+ attachment := &cloudv1alpha1.VPCAttachment{ObjectMeta: metav1.ObjectMeta{Name: "nic", Namespace: "tenant"}}
+ vpc := &cloudv1alpha1.VPC{}
+ vpc.Status.VPC = "vpc-1"
+ if err := r.publishToInterface(context.Background(), iface, attachment, vpc); err != nil {
+ t.Fatal(err)
+ }
+ var got networkingv1alpha.NetworkInterface
+ if err := cl.Get(context.Background(), client.ObjectKeyFromObject(iface), &got); err != nil {
+ t.Fatal(err)
+ }
+ if got.Status.AttachmentRef == nil || got.Status.AttachmentRef.APIGroup != "galactic.datumapis.com" {
+ t.Fatalf("attachment reference = %#v, want Galactic API group", got.Status.AttachmentRef)
+ }
+ condition := apimeta.FindStatusCondition(got.Status.Conditions, networkingv1alpha.NetworkInterfaceProgrammed)
+ if condition == nil || condition.Status != metav1.ConditionUnknown || condition.Reason != "APIGroupMigration" {
+ t.Fatalf("Programmed condition = %#v, want Unknown/APIGroupMigration", condition)
+ }
+ var gotClaim networkingv1alpha.NetworkInterfaceClaim
+ if err := cl.Get(context.Background(), client.ObjectKeyFromObject(claim), &gotClaim); err != nil {
+ t.Fatal(err)
+ }
+ for _, conditionType := range []string{networkingv1alpha.NetworkInterfaceClaimProgrammed, networkingv1alpha.NetworkInterfaceClaimReady} {
+ condition := apimeta.FindStatusCondition(gotClaim.Status.Conditions, conditionType)
+ if condition == nil || condition.Status != metav1.ConditionUnknown {
+ t.Fatalf("claim %s condition = %#v, want Unknown during migration", conditionType, condition)
+ }
+ }
+}
+
+func TestMasterPlugin(t *testing.T) {
+ tests := []struct {
+ mode cloudv1alpha1.VPCAttachmentInterfaceMode
+ want string
+ }{
+ {cloudv1alpha1.VPCAttachmentInterfaceModeNetns, galactic.PluginVeth},
+ {cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor, galactic.PluginTap},
+ {cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared, galactic.PluginTap},
+ }
+ for _, test := range tests {
+ t.Run(string(test.mode), func(t *testing.T) {
+ if got := masterPlugin(test.mode); got != test.want {
+ t.Errorf("got %q, want %q", got, test.want)
+ }
+ })
+ }
+}
+
+func TestClaimFulfilled(t *testing.T) {
+ newInterface := func(phase networkingv1alpha.NetworkInterfacePhase, allocated metav1.ConditionStatus,
+ contextRef *networkingv1alpha.LocalNetworkContextRef) *networkingv1alpha.NetworkInterface {
+ return &networkingv1alpha.NetworkInterface{
+ Status: networkingv1alpha.NetworkInterfaceStatus{
+ Phase: phase,
+ NetworkContextRef: contextRef,
+ Conditions: []metav1.Condition{{
+ Type: networkingv1alpha.NetworkInterfaceAllocated,
+ Status: allocated,
+ Reason: "Test",
+ }},
+ },
+ }
+ }
+ contextRef := &networkingv1alpha.LocalNetworkContextRef{Name: "default-us-central-1"}
+
+ tests := []struct {
+ name string
+ networkInterface *networkingv1alpha.NetworkInterface
+ want bool
+ }{
+ {"bound and allocated", newInterface(
+ networkingv1alpha.NetworkInterfacePhaseBound, metav1.ConditionTrue, contextRef), true},
+ {"available", newInterface(
+ networkingv1alpha.NetworkInterfacePhaseAvailable, metav1.ConditionTrue, contextRef), false},
+ {"not allocated", newInterface(
+ networkingv1alpha.NetworkInterfacePhaseBound, metav1.ConditionFalse, contextRef), false},
+ {"no network context", newInterface(
+ networkingv1alpha.NetworkInterfacePhaseBound, metav1.ConditionTrue, nil), false},
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ if got := claimFulfilled(test.networkInterface); got != test.want {
+ t.Errorf("got %v, want %v", got, test.want)
+ }
+ })
+ }
+}
+
+// The cell-wide mode is the only signal every attachment written until now
+// carries, so it has to keep deciding for all of them. An interface that
+// states its own mode is the exception.
+func TestAttachmentModeFallsBackToTheCell(t *testing.T) {
+ r := &NetworkInterfaceReconciler{
+ AttachmentMode: cloudv1alpha1.VPCAttachmentInterfaceModeNetns,
+ }
+
+ tests := []struct {
+ name string
+ iface networkingv1alpha.NetworkInterfaceAttachmentMode
+ want cloudv1alpha1.VPCAttachmentInterfaceMode
+ }{
+ {"unset", "", cloudv1alpha1.VPCAttachmentInterfaceModeNetns},
+ {"netns", networkingv1alpha.NetworkInterfaceAttachmentModeNetns,
+ cloudv1alpha1.VPCAttachmentInterfaceModeNetns},
+ {"hypervisor", networkingv1alpha.NetworkInterfaceAttachmentModeHypervisor,
+ cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor},
+ {"declared", networkingv1alpha.NetworkInterfaceAttachmentModeHypervisorDeclared,
+ cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared},
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ networkInterface := &networkingv1alpha.NetworkInterface{
+ Spec: networkingv1alpha.NetworkInterfaceSpec{AttachmentMode: test.iface},
+ }
+ if got := r.attachmentMode(networkInterface); got != test.want {
+ t.Errorf("got %q, want %q", got, test.want)
+ }
+ })
+ }
+}
+
+// Only the declared mode asks the tap plugin to describe the device.
+// TestHostInterfaceAnnotationKey pins the annotation key this controller writes
+// on the NAD. The key is a contract with the data plane: galactic writes the
+// same one during CNI ADD, and a runtime reads it to learn its host device.
+// Renaming it here alone would leave that runtime with nothing to read, and
+// nothing else in this repository would fail.
+func TestHostInterfaceAnnotationKey(t *testing.T) {
+ const want = "k8s.v1.cni.cncf.io/host-interface"
+ if AnnotationHostInterface != want {
+ t.Errorf("AnnotationHostInterface = %q, want %q", AnnotationHostInterface, want)
+ }
+}
+
+func TestDeclaresDevice(t *testing.T) {
+ if declaresDevice(cloudv1alpha1.VPCAttachmentInterfaceModeHypervisor) {
+ t.Error("a discovered hypervisor attachment must not ask for a description")
+ }
+ if !declaresDevice(cloudv1alpha1.VPCAttachmentInterfaceModeHypervisorDeclared) {
+ t.Error("a declared hypervisor attachment must ask for a description")
+ }
+}
diff --git a/internal/galactic/controller/subnet_controller.go b/internal/galactic/controller/subnet_controller.go
new file mode 100644
index 00000000..46c735dd
--- /dev/null
+++ b/internal/galactic/controller/subnet_controller.go
@@ -0,0 +1,180 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package controller
+
+import (
+ "context"
+ "fmt"
+ "slices"
+ "strconv"
+
+ apierrors "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/handler"
+
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
+ networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
+)
+
+// upstreamGenerationAnnotation names the generation of the project's subnet
+// that the copy propagated here was taken from. The operator that owns the
+// subnet stamps it on the copy, and reads a report back only for the
+// generation it stamped. The key is the operator's; it is spelled out here
+// because the operator version this module pins does not export it yet.
+const upstreamGenerationAnnotation = "networking.datumapis.com/upstream-generation"
+
+// SubnetReconciler reports whether a location's address range is in place on
+// this cell: the VPC built for the subnet's network context carries the range
+// and is ready. It closes the Programmed condition the networking operator
+// leaves for the data plane, on the copy of the Subnet propagated to this cell,
+// and the federation carries the report back to the project.
+//
+// The range is in place when traffic can be routed in it. A VPC carries the
+// range the moment it is built, because its address space is fixed at
+// creation; what it waits for is the identity the fabric routes it by.
+type SubnetReconciler struct {
+ client.Client
+ Scheme *runtime.Scheme
+}
+
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=subnets,verbs=get;list;watch
+// +kubebuilder:rbac:groups=networking.datumapis.com,resources=subnets/status,verbs=get;update;patch
+// +kubebuilder:rbac:groups=galactic.datumapis.com,resources=vpcs,verbs=get;list;watch
+
+func (r *SubnetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
+ var subnet networkingv1alpha.Subnet
+ if err := r.Get(ctx, req.NamespacedName, &subnet); err != nil {
+ return ctrl.Result{}, client.IgnoreNotFound(err)
+ }
+ if !subnet.DeletionTimestamp.IsZero() {
+ return ctrl.Result{}, nil
+ }
+
+ var vpc *cloudv1alpha1.VPC
+ if subnet.Spec.NetworkContext.Name != "" {
+ vpc = &cloudv1alpha1.VPC{}
+ key := client.ObjectKey{Namespace: subnet.Namespace, Name: subnet.Spec.NetworkContext.Name}
+ if err := r.Get(ctx, key, vpc); err != nil {
+ if !apierrors.IsNotFound(err) {
+ return ctrl.Result{}, fmt.Errorf("read the VPC for subnet %s: %w", req.NamespacedName, err)
+ }
+ vpc = nil
+ }
+ }
+
+ condition := subnetProgrammedCondition(&subnet, vpc)
+ condition.ObservedGeneration = reportedGeneration(&subnet)
+ if !meta.SetStatusCondition(&subnet.Status.Conditions, condition) {
+ return ctrl.Result{}, nil
+ }
+ if err := r.Status().Update(ctx, &subnet); err != nil {
+ return ctrl.Result{}, fmt.Errorf("update subnet %s status: %w", req.NamespacedName, err)
+ }
+ return ctrl.Result{}, nil
+}
+
+// subnetProgrammedCondition says whether the subnet's range is in place on this
+// cell, given the VPC built for its network context, or nil for none yet.
+func subnetProgrammedCondition(subnet *networkingv1alpha.Subnet, vpc *cloudv1alpha1.VPC) metav1.Condition {
+ condition := metav1.Condition{
+ Type: networkingv1alpha.SubnetProgrammed,
+ Status: metav1.ConditionFalse,
+ }
+
+ start, prefixLength, ok := subnetRange(subnet)
+ if !ok {
+ condition.Reason = networkingv1alpha.SubnetProgrammedReasonNotProgrammed
+ condition.Message = "the subnet states no range to put in place"
+ return condition
+ }
+ network := cloudv1alpha1.Network(fmt.Sprintf("%s/%d", start, prefixLength))
+
+ if vpc == nil {
+ condition.Reason = networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress
+ condition.Message = fmt.Sprintf("waiting for the VPC for network context %q to be built", subnet.Spec.NetworkContext.Name)
+ return condition
+ }
+
+ // The address space is fixed when the VPC is built. A range that was not
+ // there then is not routed by it now, and no amount of waiting changes that.
+ if !slices.Contains(vpc.Spec.Networks, network) {
+ condition.Reason = networkingv1alpha.SubnetProgrammedReasonNotProgrammed
+ condition.Message = fmt.Sprintf("VPC %q was built without %s; its address space is fixed at creation", vpc.Name, network)
+ return condition
+ }
+
+ ready := meta.FindStatusCondition(vpc.Status.Conditions, cloudv1alpha1.ConditionTypeReady)
+ if ready == nil || ready.Status != metav1.ConditionTrue || vpc.Status.VPC == "" {
+ condition.Reason = networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress
+ condition.Message = fmt.Sprintf("VPC %q carries %s but is not ready yet", vpc.Name, network)
+ if ready != nil && ready.Message != "" {
+ condition.Message += ": " + ready.Message
+ }
+ return condition
+ }
+
+ condition.Status = metav1.ConditionTrue
+ condition.Reason = networkingv1alpha.SubnetProgrammedReasonProgrammed
+ condition.Message = fmt.Sprintf("%s is routed by VPC %s", network, vpc.Status.VPC)
+ return condition
+}
+
+// reportedGeneration is the generation the report is about. Each API server
+// counts generation on its own, so the copy's own generation says nothing to
+// the project; the report is made against the upstream generation stamped on
+// the copy, and falls back to the copy's generation for one that carries none.
+func reportedGeneration(subnet *networkingv1alpha.Subnet) int64 {
+ if generation, err := strconv.ParseInt(subnet.Annotations[upstreamGenerationAnnotation], 10, 64); err == nil {
+ return generation
+ }
+ return subnet.Generation
+}
+
+// SetupWithManager registers the reconciler with the manager.
+func (r *SubnetReconciler) SetupWithManager(mgr ctrl.Manager) error {
+ return ctrl.NewControllerManagedBy(mgr).
+ For(&networkingv1alpha.Subnet{}).
+ Watches(&cloudv1alpha1.VPC{}, handler.EnqueueRequestsFromMapFunc(r.subnetsForVPC)).
+ Named("subnet").
+ Complete(r)
+}
+
+// subnetsForVPC names the subnets a VPC was built for, so its becoming ready
+// is reported on each of them.
+func (r *SubnetReconciler) subnetsForVPC(ctx context.Context, obj client.Object) []ctrl.Request {
+ vpc, ok := obj.(*cloudv1alpha1.VPC)
+ if !ok {
+ return nil
+ }
+ var subnets networkingv1alpha.SubnetList
+ if err := r.List(ctx, &subnets, client.InNamespace(vpc.Namespace)); err != nil {
+ return nil
+ }
+ requests := make([]ctrl.Request, 0, len(subnets.Items))
+ for i := range subnets.Items {
+ if subnets.Items[i].Spec.NetworkContext.Name != vpc.Name {
+ continue
+ }
+ requests = append(requests, ctrl.Request{NamespacedName: client.ObjectKeyFromObject(&subnets.Items[i])})
+ }
+ return requests
+}
diff --git a/internal/galactic/controller/subnet_controller_test.go b/internal/galactic/controller/subnet_controller_test.go
new file mode 100644
index 00000000..384179d0
--- /dev/null
+++ b/internal/galactic/controller/subnet_controller_test.go
@@ -0,0 +1,163 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package controller
+
+import (
+ "context"
+ "testing"
+
+ "k8s.io/apimachinery/pkg/api/meta"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/apimachinery/pkg/types"
+ ctrl "sigs.k8s.io/controller-runtime"
+ "sigs.k8s.io/controller-runtime/pkg/client"
+ "sigs.k8s.io/controller-runtime/pkg/client/fake"
+
+ cloudv1alpha1 "go.datum.net/galactic/api/cloud/v1alpha1"
+ networkingv1alpha "go.datum.net/network-services-operator/api/v1alpha"
+)
+
+// propagatedSubnet is the copy of a project's subnet a cell is handed: spec
+// only, with the upstream generation the operator stamped on it.
+func propagatedSubnet(upstreamGeneration string) *networkingv1alpha.Subnet {
+ subnet := &networkingv1alpha.Subnet{}
+ subnet.Namespace = vpcTestNamespace
+ subnet.Name = vpcTestNetwork + "-" + vpcTestLocation + "-ipv6"
+ subnet.Generation = 7
+ if upstreamGeneration != "" {
+ subnet.Annotations = map[string]string{upstreamGenerationAnnotation: upstreamGeneration}
+ }
+ subnet.Spec.NetworkContext = networkingv1alpha.LocalNetworkContextRef{Name: vpcTestNetwork + "-" + vpcTestLocation}
+ subnet.Spec.StartAddress = "fd00::"
+ subnet.Spec.PrefixLength = 48
+ return subnet
+}
+
+func vpcFor(networks []cloudv1alpha1.Network, ready bool) *cloudv1alpha1.VPC {
+ vpc := &cloudv1alpha1.VPC{}
+ vpc.Namespace = vpcTestNamespace
+ vpc.Name = vpcTestNetwork + "-" + vpcTestLocation
+ vpc.Spec.Networks = networks
+ if ready {
+ vpc.Status.VPC = vpcTestIdentifier
+ vpc.Status.Conditions = []metav1.Condition{{
+ Type: cloudv1alpha1.ConditionTypeReady, Status: metav1.ConditionTrue, Reason: "IdentifierAllocated",
+ }}
+ } else {
+ vpc.Status.Conditions = []metav1.Condition{{
+ Type: cloudv1alpha1.ConditionTypeReady, Status: metav1.ConditionFalse, Reason: "AwaitingFabricIdentity",
+ Message: "Waiting for the identity the fabric knows this network by",
+ }}
+ }
+ return vpc
+}
+
+func reconcileSubnet(t *testing.T, objects ...client.Object) *networkingv1alpha.Subnet {
+ t.Helper()
+
+ scheme := runtime.NewScheme()
+ if err := networkingv1alpha.AddToScheme(scheme); err != nil {
+ t.Fatalf("build the networking scheme: %v", err)
+ }
+ if err := cloudv1alpha1.AddToScheme(scheme); err != nil {
+ t.Fatalf("build the cloud scheme: %v", err)
+ }
+ cl := fake.NewClientBuilder().WithScheme(scheme).
+ WithStatusSubresource(&networkingv1alpha.Subnet{}).
+ WithObjects(objects...).Build()
+
+ reconciler := &SubnetReconciler{Client: cl, Scheme: scheme}
+ key := types.NamespacedName{Namespace: vpcTestNamespace, Name: vpcTestNetwork + "-" + vpcTestLocation + "-ipv6"}
+ if _, err := reconciler.Reconcile(context.Background(), ctrl.Request{NamespacedName: key}); err != nil {
+ t.Fatalf("reconcile: %v", err)
+ }
+
+ subnet := &networkingv1alpha.Subnet{}
+ if err := cl.Get(context.Background(), key, subnet); err != nil {
+ t.Fatalf("read the subnet: %v", err)
+ }
+ return subnet
+}
+
+func programmedOf(t *testing.T, subnet *networkingv1alpha.Subnet) metav1.Condition {
+ t.Helper()
+ condition := meta.FindStatusCondition(subnet.Status.Conditions, networkingv1alpha.SubnetProgrammed)
+ if condition == nil {
+ t.Fatalf("the cell should have reported on the subnet, got %+v", subnet.Status.Conditions)
+ }
+ return *condition
+}
+
+// The signal: a range is in place once the VPC built for its network context
+// carries it and is routed by the fabric. The report is made against the
+// generation the project knows, not the copy's own.
+func TestSubnetIsProgrammedWhenAReadyVPCCarriesItsRange(t *testing.T) {
+ subnet := reconcileSubnet(t, propagatedSubnet("3"), vpcFor([]cloudv1alpha1.Network{"fd00::/48"}, true))
+
+ condition := programmedOf(t, subnet)
+ if condition.Status != metav1.ConditionTrue || condition.Reason != networkingv1alpha.SubnetProgrammedReasonProgrammed {
+ t.Fatalf("a ready VPC carrying the range should read as programmed, got %+v", condition)
+ }
+ if condition.ObservedGeneration != 3 {
+ t.Fatalf("the report is about the project's generation 3, got %d", condition.ObservedGeneration)
+ }
+}
+
+// A VPC that carries the range but has no identity yet is on its way, and
+// says so, rather than claiming the range is routed.
+func TestSubnetWaitsForTheVPCToBecomeReady(t *testing.T) {
+ subnet := reconcileSubnet(t, propagatedSubnet("3"), vpcFor([]cloudv1alpha1.Network{"fd00::/48"}, false))
+
+ condition := programmedOf(t, subnet)
+ if condition.Status != metav1.ConditionFalse || condition.Reason != networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress {
+ t.Fatalf("a VPC awaiting its identity should read as in progress, got %+v", condition)
+ }
+}
+
+// No VPC yet is the same wait: the network context reconciler builds it once
+// the subnet it reads from is there.
+func TestSubnetWaitsForTheVPCToBeBuilt(t *testing.T) {
+ subnet := reconcileSubnet(t, propagatedSubnet("3"))
+
+ condition := programmedOf(t, subnet)
+ if condition.Status != metav1.ConditionFalse || condition.Reason != networkingv1alpha.SubnetProgrammedReasonProgrammingInProgress {
+ t.Fatalf("a VPC not built yet should read as in progress, got %+v", condition)
+ }
+}
+
+// A VPC's address space is fixed when it is built. A range it was built
+// without is not routed by it, and that is a verdict, not a wait.
+func TestSubnetIsNotProgrammedWhenTheVPCWasBuiltWithoutIt(t *testing.T) {
+ subnet := reconcileSubnet(t, propagatedSubnet("3"), vpcFor([]cloudv1alpha1.Network{"fd00:1::/48"}, true))
+
+ condition := programmedOf(t, subnet)
+ if condition.Status != metav1.ConditionFalse || condition.Reason != networkingv1alpha.SubnetProgrammedReasonNotProgrammed {
+ t.Fatalf("a range the VPC was built without should read as not programmed, got %+v", condition)
+ }
+}
+
+// A copy with no upstream generation stamped on it reports against its own,
+// so a project running an older operator still sees a report.
+func TestSubnetReportFallsBackToTheCopysOwnGeneration(t *testing.T) {
+ subnet := reconcileSubnet(t, propagatedSubnet(""), vpcFor([]cloudv1alpha1.Network{"fd00::/48"}, true))
+
+ if got := programmedOf(t, subnet).ObservedGeneration; got != 7 {
+ t.Fatalf("an unstamped copy reports against its own generation 7, got %d", got)
+ }
+}
diff --git a/internal/galactic/galactic/galactic.go b/internal/galactic/galactic/galactic.go
new file mode 100644
index 00000000..9865ce51
--- /dev/null
+++ b/internal/galactic/galactic/galactic.go
@@ -0,0 +1,214 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+// Package galactic is the vocabulary this operator shares with the galactic
+// data plane: the CNI conflist a NetworkAttachmentDefinition carries, the
+// kernel interface names derived from a (vpc, attachment) pair, and the
+// BGPAdvertisement annotations galactic publishes back.
+package galactic
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "go.datum.net/network-services-operator/internal/galactic/identifier"
+)
+
+const (
+ // CNIVersion is the only conflist version galactic-bgp reconstructs a
+ // prevResult from; "1.1.0" also works, anything older fails every ADD.
+ CNIVersion = "1.0.0"
+
+ // PluginVeth is the master plugin for container attachments.
+ PluginVeth = "galactic-veth"
+
+ // PluginTap is the master plugin for virtual machine guests.
+ PluginTap = "galactic-tap"
+
+ // PluginBGP publishes the attachment into BGP and SRv6.
+ PluginBGP = "galactic-bgp"
+
+ // PluginIPAM is the delegated IPAM binary.
+ PluginIPAM = "galactic-ipam"
+
+ // SystemNamespace holds the BGP CRDs the chain reads and writes.
+ SystemNamespace = "galactic-system"
+)
+
+const (
+ // AnnotationNetNS is the BGPAdvertisement annotation key prefix carrying a
+ // container's netns path.
+ AnnotationNetNS = "galactic.datum.net/netns"
+
+ // AnnotationAllocatedSubnetIPv6 is the annotation key prefix carrying a
+ // container's allocated IPv6 pod subnet.
+ AnnotationAllocatedSubnetIPv6 = "galactic.datum.net/allocated-subnet-ipv6"
+
+ // AnnotationAllocatedSubnetIPv4 is the annotation key prefix carrying a
+ // container's allocated IPv4 pod address.
+ AnnotationAllocatedSubnetIPv4 = "galactic.datum.net/allocated-subnet-ipv4"
+
+ // AnnotationNoAddressing marks an advertisement whose guest manages its own
+ // addressing, so an empty prefix list is intentional.
+ AnnotationNoAddressing = "galactic.datum.net/no-addressing"
+
+ // ConditionAdvertised is set on a BGPAdvertisement from live GoBGP state.
+ ConditionAdvertised = "Advertised"
+)
+
+// NetConfList is a CNI conflist, the payload of a NAD's spec.config.
+type NetConfList struct {
+ CNIVersion string `json:"cniVersion"`
+ Name string `json:"name"`
+ Plugins []any `json:"plugins"`
+}
+
+// MasterPlugin is the galactic-veth or galactic-tap stanza.
+type MasterPlugin struct {
+ Type string `json:"type"`
+ VPC string `json:"vpc"`
+ VPCAttachment string `json:"vpcattachment"`
+ Namespace string `json:"namespace"`
+ MTU int32 `json:"mtu,omitempty"`
+ // DAN asks the tap plugin to describe the device to the hypervisor in a
+ // file the hypervisor reads, rather than leaving it to be discovered from
+ // the node. Only the tap plugin reads it, and it is omitted when false so
+ // every conflist rendered until now is byte-identical.
+ DAN bool `json:"dan,omitempty"`
+ IPAM *IPAM `json:"ipam,omitempty"`
+}
+
+// BGPPlugin is the galactic-bgp stanza. It is never optional: the master plugin
+// fetches its own NAD and fails ADD before creating kernel state without it.
+type BGPPlugin struct {
+ Type string `json:"type"`
+ VPC string `json:"vpc"`
+ VPCAttachment string `json:"vpcattachment"`
+ Namespace string `json:"namespace"`
+}
+
+// IPAM is the delegated IPAM block. Presence alone decides whether IPAM runs.
+type IPAM struct {
+ Type string `json:"type"`
+ Addresses []Address `json:"addresses,omitempty"`
+}
+
+// Address is one pre-decided address, in CIDR notation, with the next hop the
+// interface routes through for its family. Without the gateway the guest has an
+// address but no route off its own link, and the host installs neither the tap
+// gateway address nor the route to the attachment's prefix.
+type Address struct {
+ Address string `json:"address"`
+ Gateway string `json:"gateway,omitempty"`
+}
+
+// Conflist renders the conflist for one attachment. Addresses are the addresses
+// NSO already allocated; an empty list means the guest addresses itself and no
+// IPAM block is emitted. Set dan for a guest whose hypervisor is handed the
+// device rather than discovering it.
+func Conflist(name, plugin, vpc, vpcAttachment string, mtu int32, addresses []Address, dan bool) NetConfList {
+ master := MasterPlugin{
+ Type: plugin,
+ VPC: vpc,
+ VPCAttachment: vpcAttachment,
+ Namespace: SystemNamespace,
+ MTU: mtu,
+ DAN: dan,
+ }
+ if len(addresses) > 0 {
+ master.IPAM = &IPAM{Type: PluginIPAM, Addresses: addresses}
+ }
+ return NetConfList{
+ CNIVersion: CNIVersion,
+ Name: name,
+ Plugins: []any{
+ master,
+ BGPPlugin{Type: PluginBGP, VPC: vpc, VPCAttachment: vpcAttachment, Namespace: SystemNamespace},
+ },
+ }
+}
+
+// ConflistJSON renders the conflist as the string a NAD's spec.config holds.
+func ConflistJSON(name, plugin, vpc, vpcAttachment string, mtu int32, addresses []Address, dan bool) (string, error) {
+ raw, err := json.Marshal(Conflist(name, plugin, vpc, vpcAttachment, mtu, addresses, dan))
+ if err != nil {
+ return "", fmt.Errorf("marshal CNI conflist: %w", err)
+ }
+ return string(raw), nil
+}
+
+// HostInterfaceName returns the host-side veth or tap device name.
+func HostInterfaceName(vpc, vpcAttachment string) string {
+ return fmt.Sprintf("G%09s%03sH", vpc, vpcAttachment)
+}
+
+// GuestInterfaceName returns the guest-side veth device name.
+func GuestInterfaceName(vpc, vpcAttachment string) string {
+ return fmt.Sprintf("G%09s%03sG", vpc, vpcAttachment)
+}
+
+// VRFInterfaceName returns the VRF device name, which is per-VPC rather than
+// per-attachment.
+func VRFInterfaceName(vpc string) string {
+ return fmt.Sprintf("G%09sV", vpc)
+}
+
+// AdvertisementName returns the hex-encoded identity prefix shared by every
+// node-qualified BGPAdvertisement galactic derives from an attachment.
+func AdvertisementName(vpc, vpcAttachment string) string {
+ vpcHex, err := identifier.Base62ToHex(vpc)
+ if err != nil {
+ return ""
+ }
+ attachmentHex, err := identifier.Base62ToHex(vpcAttachment)
+ if err != nil {
+ return ""
+ }
+ return fmt.Sprintf("%s-%s", vpcHex, attachmentHex)
+}
+
+// SplitAdvertisementName recovers the base62 (vpc, attachment) pair from a
+// current node-qualified advertisement name.
+func SplitAdvertisementName(name string) (vpc, vpcAttachment string, ok bool) {
+ parts := strings.SplitN(name, "-", 3)
+ if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" {
+ return "", "", false
+ }
+ vpc, err := identifier.HexToBase62(parts[0])
+ if err != nil || vpc == "" {
+ return "", "", false
+ }
+ vpcAttachment, err = identifier.HexToBase62(parts[1])
+ if err != nil || vpcAttachment == "" {
+ return "", "", false
+ }
+ return vpc, vpcAttachment, true
+}
+
+// AllocatedSubnets returns every allocated subnet recorded on an advertisement,
+// across both families and every container ID.
+func AllocatedSubnets(annotations map[string]string) []string {
+ var subnets []string
+ for key, value := range annotations {
+ if strings.HasPrefix(key, AnnotationAllocatedSubnetIPv6+".") ||
+ strings.HasPrefix(key, AnnotationAllocatedSubnetIPv4+".") {
+ subnets = append(subnets, value)
+ }
+ }
+ return subnets
+}
diff --git a/internal/galactic/galactic/galactic_test.go b/internal/galactic/galactic/galactic_test.go
new file mode 100644
index 00000000..d718ff21
--- /dev/null
+++ b/internal/galactic/galactic/galactic_test.go
@@ -0,0 +1,171 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package galactic
+
+import (
+ "encoding/json"
+ "testing"
+)
+
+func TestConflistChainIsComplete(t *testing.T) {
+ conflist := Conflist("web-eth0", PluginTap, "0000000jU", "01a", 1400,
+ []Address{
+ {Address: "fd00:10:ff01:0:1::1/96", Gateway: "fd00:10:ff01::1"},
+ {Address: "172.20.1.7/32", Gateway: "172.20.1.1"},
+ }, false)
+
+ if conflist.CNIVersion != "1.0.0" {
+ t.Errorf("cniVersion: got %q, want %q", conflist.CNIVersion, "1.0.0")
+ }
+ if len(conflist.Plugins) != 2 {
+ t.Fatalf("plugin count: got %d, want 2", len(conflist.Plugins))
+ }
+
+ master, ok := conflist.Plugins[0].(MasterPlugin)
+ if !ok {
+ t.Fatalf("first plugin: got %T, want MasterPlugin", conflist.Plugins[0])
+ }
+ if master.Type != PluginTap {
+ t.Errorf("master plugin: got %q, want %q", master.Type, PluginTap)
+ }
+ if master.IPAM == nil || len(master.IPAM.Addresses) != 2 {
+ t.Fatalf("ipam addresses: got %v, want two entries", master.IPAM)
+ }
+ // Without the gateway the guest has an address it cannot route off.
+ for _, address := range master.IPAM.Addresses {
+ if address.Gateway == "" {
+ t.Errorf("address %q carries no gateway", address.Address)
+ }
+ }
+
+ // The master plugin fails ADD before creating kernel state without this.
+ bgp, ok := conflist.Plugins[1].(BGPPlugin)
+ if !ok {
+ t.Fatalf("second plugin: got %T, want BGPPlugin", conflist.Plugins[1])
+ }
+ if bgp.Type != PluginBGP {
+ t.Errorf("bgp plugin: got %q, want %q", bgp.Type, PluginBGP)
+ }
+}
+
+func TestConflistOmitsIPAMForSelfAddressingGuest(t *testing.T) {
+ raw, err := ConflistJSON("web-eth0", PluginTap, "0000000jU", "01a", 0, nil, false)
+ if err != nil {
+ t.Fatalf("ConflistJSON: %v", err)
+ }
+
+ var decoded struct {
+ Plugins []map[string]any `json:"plugins"`
+ }
+ if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
+ t.Fatalf("unmarshal conflist: %v", err)
+ }
+ if _, present := decoded.Plugins[0]["ipam"]; present {
+ t.Errorf("ipam block present for a guest managing its own addressing: %s", raw)
+ }
+ if _, present := decoded.Plugins[0]["mtu"]; present {
+ t.Errorf("mtu emitted when unset: %s", raw)
+ }
+}
+
+func TestInterfaceNames(t *testing.T) {
+ tests := []struct {
+ name string
+ got string
+ want string
+ }{
+ {"host", HostInterfaceName("jU", "1a"), "G0000000jU01aH"},
+ {"guest", GuestInterfaceName("jU", "1a"), "G0000000jU01aG"},
+ {"vrf", VRFInterfaceName("jU"), "G0000000jUV"},
+ }
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ if test.got != test.want {
+ t.Errorf("got %q, want %q", test.got, test.want)
+ }
+ if len(test.got) > 15 {
+ t.Errorf("interface name %q exceeds the 15 character kernel limit", test.got)
+ }
+ })
+ }
+}
+
+func TestSplitAdvertisementName(t *testing.T) {
+ tests := []struct {
+ input string
+ vpc string
+ vpcAttachment string
+ ok bool
+ }{
+ {"e008-d54-compute-c21d3c-us-east-1-staging-lab", "eV2", "T2", true},
+ {"e007-d53-edge-567e29-us-east-1", "eV1", "T1", true},
+ {"e008-d54", "", "", false},
+ {"-d54-worker", "", "", false},
+ {"e008--worker", "", "", false},
+ }
+ for _, test := range tests {
+ t.Run(test.input, func(t *testing.T) {
+ vpc, vpcAttachment, ok := SplitAdvertisementName(test.input)
+ if vpc != test.vpc || vpcAttachment != test.vpcAttachment || ok != test.ok {
+ t.Errorf("got (%q, %q, %v), want (%q, %q, %v)",
+ vpc, vpcAttachment, ok, test.vpc, test.vpcAttachment, test.ok)
+ }
+ })
+ }
+}
+
+func TestAdvertisementNameEncodesBase62IdentityAsHex(t *testing.T) {
+ if got, want := AdvertisementName("eV2", "T2"), "e008-d54"; got != want {
+ t.Errorf("AdvertisementName() = %q, want %q", got, want)
+ }
+ if got := AdvertisementName("not-valid!", "T2"); got != "" {
+ t.Errorf("AdvertisementName() with invalid VPC = %q, want empty", got)
+ }
+}
+
+// The tap plugin reads this one field to decide whether it describes the device
+// to the hypervisor. Every attachment rendered until now leaves it out, so its
+// absence has to stay the default.
+func TestConflistCarriesTheDeclaredDeviceRequest(t *testing.T) {
+ declared, err := ConflistJSON("vm-eth0", PluginTap, "0000000jU", "01a", 1400, nil, true)
+ if err != nil {
+ t.Fatalf("ConflistJSON: %v", err)
+ }
+ discovered, err := ConflistJSON("vm-eth0", PluginTap, "0000000jU", "01a", 1400, nil, false)
+ if err != nil {
+ t.Fatalf("ConflistJSON: %v", err)
+ }
+
+ if got := masterStanza(t, declared)["dan"]; got != true {
+ t.Errorf("declared attachment: got %v, want true", got)
+ }
+ if _, present := masterStanza(t, discovered)["dan"]; present {
+ t.Errorf("discovered attachment carries the field: %s", discovered)
+ }
+}
+
+func masterStanza(t *testing.T, raw string) map[string]any {
+ t.Helper()
+ var decoded struct {
+ Plugins []map[string]any `json:"plugins"`
+ }
+ if err := json.Unmarshal([]byte(raw), &decoded); err != nil {
+ t.Fatalf("unmarshal conflist: %v", err)
+ }
+ return decoded.Plugins[0]
+}
diff --git a/internal/galactic/identifier/identifier.go b/internal/galactic/identifier/identifier.go
new file mode 100644
index 00000000..657d22d5
--- /dev/null
+++ b/internal/galactic/identifier/identifier.go
@@ -0,0 +1,95 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+// Package identifier allocates the VPC and VPC attachment identifiers the
+// galactic data plane keys on. Identifiers are generated as hex and published
+// as base62, which is what keeps a kernel interface name inside 15 characters.
+package identifier
+
+import (
+ "crypto/rand"
+ "fmt"
+ "math/big"
+ "strconv"
+ "strings"
+
+ "github.com/kenshaw/baseconv"
+)
+
+const (
+ // MaxVPC is the largest VPC identifier: 48 bits, nine base62 characters.
+ MaxVPC uint64 = 0xFFFFFFFFFFFF
+
+ // MaxVPCAttachment is the largest attachment identifier: 16 bits, three
+ // base62 characters.
+ MaxVPCAttachment uint64 = 0xFFFF
+)
+
+// Hex renders value as a zero-padded hex string wide enough to hold max.
+// Zero and max are reserved.
+func Hex(value, max uint64) (string, error) {
+ if value == 0 || value == max {
+ return "", fmt.Errorf("%d is a reserved identifier value", value)
+ }
+ if value > max {
+ return "", fmt.Errorf("%d exceeds the maximum identifier value %d", value, max)
+ }
+ return fmt.Sprintf("%0*x", len(strconv.FormatUint(max, 16)), value), nil
+}
+
+// Random returns a random hex identifier in (0, max).
+func Random(max uint64) (string, error) {
+ n, err := rand.Int(rand.Reader, big.NewInt(int64(max-1)))
+ if err != nil {
+ return "", fmt.Errorf("draw random identifier: %w", err)
+ }
+ return Hex(n.Uint64()+1, max)
+}
+
+// RandomVPCAttachment returns a random 16-bit attachment identifier in hex.
+func RandomVPCAttachment() (string, error) {
+ return Random(MaxVPCAttachment)
+}
+
+// HexToBase62 converts a hex identifier to the base62 form the CNI chain reads.
+func HexToBase62(value string) (string, error) {
+ return baseconv.Convert(strings.ToLower(value), baseconv.DigitsHex, baseconv.Digits62)
+}
+
+// Base62ToHex converts a base62 identifier back to lowercase hex.
+func Base62ToHex(value string) (string, error) {
+ return baseconv.Convert(value, baseconv.Digits62, baseconv.DigitsHex)
+}
+
+// VPCBase62 renders a VPC identifier in base62, with the reserved-value guards
+// and the width that keeps a kernel interface name inside fifteen characters.
+func VPCBase62(value uint64) (string, error) {
+ hex, err := Hex(value, MaxVPC)
+ if err != nil {
+ return "", err
+ }
+ return HexToBase62(hex)
+}
+
+// RandomVPCAttachmentBase62 returns a random attachment identifier in base62.
+func RandomVPCAttachmentBase62() (string, error) {
+ hex, err := RandomVPCAttachment()
+ if err != nil {
+ return "", err
+ }
+ return HexToBase62(hex)
+}
diff --git a/internal/galactic/identifier/identifier_test.go b/internal/galactic/identifier/identifier_test.go
new file mode 100644
index 00000000..5e7cda33
--- /dev/null
+++ b/internal/galactic/identifier/identifier_test.go
@@ -0,0 +1,87 @@
+/*
+Copyright © 2026 Datum Technology, Inc. All rights reserved.
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see .
+*/
+
+package identifier
+
+import "testing"
+
+func TestHexRejectsReservedValues(t *testing.T) {
+ for _, value := range []uint64{0, MaxVPC, MaxVPC + 1} {
+ if _, err := Hex(value, MaxVPC); err == nil {
+ t.Errorf("Hex(%d): got no error, want one", value)
+ }
+ }
+}
+
+func TestRandomIdentifiersFitTheirInterfaceNameSegment(t *testing.T) {
+ for range 200 {
+ attachment, err := RandomVPCAttachmentBase62()
+ if err != nil {
+ t.Fatalf("RandomVPCAttachmentBase62: %v", err)
+ }
+ if len(attachment) > 3 {
+ t.Errorf("attachment identifier %q exceeds three base62 characters", attachment)
+ }
+ }
+}
+
+func TestBase62RoundTrip(t *testing.T) {
+ hex, err := Random(MaxVPC)
+ if err != nil {
+ t.Fatalf("Random(MaxVPC): %v", err)
+ }
+ base62, err := HexToBase62(hex)
+ if err != nil {
+ t.Fatalf("HexToBase62(%q): %v", hex, err)
+ }
+ back, err := Base62ToHex(base62)
+ if err != nil {
+ t.Fatalf("Base62ToHex(%q): %v", base62, err)
+ }
+ if want := trimLeadingZeros(hex); back != want {
+ t.Errorf("round trip: got %q, want %q", back, want)
+ }
+}
+
+func trimLeadingZeros(value string) string {
+ for i := range value {
+ if value[i] != '0' {
+ return value[i:]
+ }
+ }
+ return "0"
+}
+
+func TestVPCBase62RejectsReservedValuesAndFitsItsSegment(t *testing.T) {
+ for _, value := range []uint64{0, MaxVPC, MaxVPC + 1} {
+ if _, err := VPCBase62(value); err == nil {
+ t.Errorf("VPCBase62(%d): got no error, want one", value)
+ }
+ }
+
+ // The widest fabric identity is 32 bits, well inside the 48 the VPC
+ // identifier holds, so nothing an identity carries can overflow the segment.
+ for _, value := range []uint64{1, 16, 1 << 31, 1<<32 - 1} {
+ encoded, err := VPCBase62(value)
+ if err != nil {
+ t.Fatalf("VPCBase62(%d): %v", value, err)
+ }
+ if len(encoded) > 9 {
+ t.Errorf("VPCBase62(%d) = %q exceeds nine base62 characters", value, encoded)
+ }
+ }
+}
diff --git a/test/conformance/gatewayapi/roundtripper.go b/test/conformance/gatewayapi/roundtripper.go
index f10c284c..cfa11747 100644
--- a/test/conformance/gatewayapi/roundtripper.go
+++ b/test/conformance/gatewayapi/roundtripper.go
@@ -13,7 +13,6 @@ import (
"net/http/httputil"
"regexp"
- "golang.org/x/net/http2"
"sigs.k8s.io/gateway-api/conformance/utils/config"
"sigs.k8s.io/gateway-api/conformance/utils/roundtripper"
"sigs.k8s.io/gateway-api/conformance/utils/tlog"
@@ -57,11 +56,11 @@ func (d *NodePortRoundTripper) h2cPriorKnowledgeTransport(request roundtripper.R
return nil, errors.New("request has configured TLS but h2 prior knowledge is not encrypted")
}
- transport := &http2.Transport{
- AllowHTTP: true,
- DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
- return d.DialContext(ctx, network, addr)
- },
+ protocols := new(http.Protocols)
+ protocols.SetUnencryptedHTTP2(true)
+ transport := &http.Transport{
+ DialContext: d.DialContext,
+ Protocols: protocols,
}
return transport, nil