diff --git a/go.mod b/go.mod index fa93826..220cebc 100644 --- a/go.mod +++ b/go.mod @@ -9,13 +9,28 @@ require ( github.com/bojanz/currency v1.3.1 github.com/brevdev/compute v0.0.0-20250805004716-bc4fe363e0ea github.com/google/uuid v1.6.0 + github.com/jarcoal/httpmock v1.4.0 + github.com/nebius/gosdk v0.0.0-20250731090238-d96c0d4a5930 github.com/stretchr/testify v1.9.0 ) require ( + buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231030212536-12f9cba37c9d.2 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cockroachdb/apd/v3 v3.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/jarcoal/httpmock v1.4.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/gofrs/flock v0.12.1 // indirect + github.com/golang-jwt/jwt/v4 v4.5.1 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/text v0.21.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + google.golang.org/grpc v1.64.1 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 080cadf..d523a8d 100644 --- a/go.sum +++ b/go.sum @@ -1,22 +1,48 @@ +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231030212536-12f9cba37c9d.2 h1:m8rKyv88R8ZIR1549RMXckZ4FZJGxrq/7aRYl6U3WHc= +buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.31.0-20231030212536-12f9cba37c9d.2/go.mod h1:xafc+XIsTxTy76GJQ1TKgvJWsSugFBqMaN27WhUblew= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/bojanz/currency v1.3.1 h1:3BUAvy/5hU/Pzqg5nrQslVihV50QG+A2xKPoQw1RKH4= github.com/bojanz/currency v1.3.1/go.mod h1:jNoZiJyRTqoU5DFoa+n+9lputxPUDa8Fz8BdDrW06Go= github.com/brevdev/compute v0.0.0-20250805004716-bc4fe363e0ea h1:U+mj2Q4lYMMkCuflMzFeIzf0tiASimf8/juGhcAT3DY= github.com/brevdev/compute v0.0.0-20250805004716-bc4fe363e0ea/go.mod h1:rxhy3+lWmdnVABBys6l+Z+rVDeKa5nyy0avoQkTmTFw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg= github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= +github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= +github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 h1:pRhl55Yx1eC7BZ1N+BBWwnKaMyD8uC+34TLdndZMAKk= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0/go.mod h1:XKMd7iuf/RGPSMJ/U4HP0zS2Z9Fh8Ps9a+6X26m/tmI= github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k= github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/maxatome/go-testdeep v1.14.0 h1:rRlLv1+kI8eOI3OaBXZwb3O7xY3exRzdW5QyX48g9wI= +github.com/maxatome/go-testdeep v1.14.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/nebius/gosdk v0.0.0-20250731090238-d96c0d4a5930 h1:B8Gsp4/Ad5ZlIK+yXdjTWdqKk5UgFRbT8Ze5U3uca9o= +github.com/nebius/gosdk v0.0.0-20250731090238-d96c0d4a5930/go.mod h1:eVbm4Qc4GPzBn3EL4rLvy1WS9zqJDw+giksOA2NZERY= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/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= @@ -26,8 +52,28 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= +google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= +google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/nebius/CONTRIBUTE.md b/internal/nebius/CONTRIBUTE.md index c114ee5..c6898e7 100644 --- a/internal/nebius/CONTRIBUTE.md +++ b/internal/nebius/CONTRIBUTE.md @@ -6,7 +6,72 @@ Get started by reading the [Nebius API documentation](https://github.com/nebius/ ## Local Development -Place a credential file in your home directory and run the provider tests. +### Prerequisites +1. **Nebius Account**: Create an account at [Nebius AI Cloud](https://nebius.com) +2. **Service Account**: Create a service account in Nebius IAM +3. **Service Account Key**: Generate and download a JSON service account key -## Prompts +### Setup + +1. **Install Dependencies**: + ```bash + go mod download + ``` + +2. **Configure Credentials**: + Place your service account JSON key file in your home directory: + ```bash + cp /path/to/your/service-account-key.json ~/.nebius-credentials.json + ``` + +3. **Set Environment Variables**: + ```bash + export NEBIUS_SERVICE_ACCOUNT_KEY_FILE=~/.nebius-credentials.json + export NEBIUS_PROJECT_ID=your-project-id + ``` + +### Running Tests + +```bash +# Run all tests +make test + +# Run Nebius-specific tests +go test ./internal/nebius/v1/... + +# Run with verbose output +go test -v ./internal/nebius/v1/... +``` + +### Development Workflow + +1. **Code Changes**: Make changes to the Nebius provider implementation +2. **Lint**: Run `make lint` to ensure code quality +3. **Test**: Run `make test` to verify functionality +4. **Commit**: Follow conventional commit messages + +### Implementation Status + +The current implementation provides boilerplate stubs for all CloudClient interface methods: + +**Implemented (Stubs)**: +- Instance management (Create, Get, List, Terminate, Stop, Start, Reboot) +- Instance types and quotas +- Image management +- Location management +- Firewall/Security Group management +- Volume management +- Tag management + +**Next Steps**: +- Replace stub implementations with actual Nebius API calls +- Add comprehensive error handling +- Implement proper resource mapping between Brev and Nebius models +- Add integration tests with real Nebius resources + +### API Reference + +- **Nebius Go SDK**: https://github.com/nebius/gosdk +- **Nebius API Documentation**: https://github.com/nebius/api +- **Compute Service**: Focus on `services/nebius/compute/v1/` for instance management diff --git a/internal/nebius/SECURITY.md b/internal/nebius/SECURITY.md index 8b83569..914e779 100644 --- a/internal/nebius/SECURITY.md +++ b/internal/nebius/SECURITY.md @@ -48,13 +48,41 @@ This document explains how Nebius VMs meet Brev Cloud SDK’s security requireme ## Implementation Checklist -* [x] Default deny-all inbound using custom Nebius Security Group -* [x] Allow-all outbound via security group egress rule -* [x] `FirewallRule` maps to explicit Nebius SG ingress rule -* [x] Instances in the same cluster can talk via shared SG "self" rule -* [x] Different clusters are isolated using separate SGs or VPCs -* [x] Disk encryption enabled by default -* [x] TLS used for all API and external communication +* [ ] Default deny-all inbound using custom Nebius Security Group +* [ ] Allow-all outbound via security group egress rule +* [ ] `FirewallRule` maps to explicit Nebius SG ingress rule +* [ ] Instances in the same cluster can talk via shared SG "self" rule +* [ ] Different clusters are isolated using separate SGs or VPCs +* [x] Disk encryption enabled by default (Nebius default) +* [x] TLS used for all API and external communication (Nebius SDK default) + +## Authentication Implementation + +### Service Account Setup + +Nebius uses JWT-based service account authentication: + +1. **Service Account Creation**: Create a service account in Nebius IAM +2. **Key Generation**: Generate a JSON service account key file +3. **JWT Token Exchange**: SDK automatically handles JWT signing and token exchange +4. **API Authentication**: All API calls use Bearer token authentication + +### Authentication Flow + +``` +1. Load service account JSON key +2. Generate JWT with RS256 signing (kid, iss, sub, exp claims) +3. Exchange JWT for IAM token via TokenExchangeService +4. Use IAM token in Authorization header for compute API calls +``` + +### Implementation Details + +The `NebiusClient` uses the official Nebius Go SDK which handles: +- Automatic JWT token generation and refresh +- gRPC connection management with TLS 1.2+ +- Service discovery for Nebius API endpoints +- Retry logic and error handling --- diff --git a/internal/nebius/v1/README.md b/internal/nebius/v1/README.md new file mode 100644 index 0000000..55c8ddc --- /dev/null +++ b/internal/nebius/v1/README.md @@ -0,0 +1,92 @@ +# Nebius Provider + +This directory contains the Nebius provider implementation for the compute package. + +## Overview + +The Nebius provider implements the CloudClient interface defined in `pkg/v1` to provide access to Nebius AI Cloud infrastructure. This implementation is based on the official Nebius API documentation at https://github.com/nebius/api and uses the Nebius Go SDK. + +## Supported Features + +Based on the Nebius API documentation, the following features are **SUPPORTED**: + +### Instance Management +- ✅ **Create Instance**: `InstanceService.Create` in compute/v1/instance_service.proto +- ✅ **Get Instance**: `InstanceService.Get` and `InstanceService.GetByName` +- ✅ **List Instances**: `InstanceService.List` with pagination support +- ✅ **Terminate Instance**: `InstanceService.Delete` +- ✅ **Stop Instance**: `InstanceService.Stop` +- ✅ **Start Instance**: `InstanceService.Start` + +### Instance Updates +- ✅ **Update Instance Tags**: Maps to `UpdateInstanceTags` in CloudClient interface +- ✅ **Change Instance Type**: Maps to `ChangeInstanceType` in CloudClient interface via `ResourcesSpec.preset` field in `InstanceService.Update` + +### GPU Cluster Management +- ✅ **Create GPU Cluster**: `GpuClusterService.Create` in compute/v1/gpu_cluster_service.proto +- ✅ **Get GPU Cluster**: `GpuClusterService.Get` and `GpuClusterService.GetByName` +- ✅ **List GPU Clusters**: `GpuClusterService.List` with pagination support +- ✅ **Delete GPU Cluster**: `GpuClusterService.Delete` +- ✅ **Update GPU Cluster**: `GpuClusterService.Update` + +### Machine Images +- ✅ **Get Images**: `ImageService.Get`, `ImageService.GetByName`, `ImageService.GetLatestByFamily` +- ✅ **List Images**: `ImageService.List` with filtering support + +### Quota Management +- ✅ **Get Quotas**: `QuotaAllowanceService` in quotas/v1/quota_allowance_service.proto + +## Unsupported Features + +The following features are **NOT SUPPORTED** (no clear API endpoints found): + +### Instance Operations +- ❌ **Reboot Instance**: No reboot endpoint found in instance_service.proto +- ❌ **General Instance Updates**: Nebius InstanceService.Update exists but most InstanceSpec fields are immutable; only specific updates like tags and instance type are supported through dedicated CloudClient methods + +### Volume Management +- ❌ **Resize Instance Volume**: Volume resizing not clearly documented + +### Location Management +- ❌ **Get Locations**: No location listing service found + +### Firewall Management +- ❌ **Firewall Rules**: Network security handled through VPC service, not instance-level firewall rules + +## Implementation Approach + +This implementation uses the `NotImplCloudClient` pattern for unsupported features: +- Supported features have TODO implementations with API service references +- Unsupported features return `ErrNotImplemented` (handled by embedded NotImplCloudClient) +- Full CloudClient interface compliance is maintained + +## Nebius API + +The provider integrates with the Nebius AI Cloud API: +- Base URL: `{service-name}.api.nebius.cloud:443` (gRPC) +- Authentication: Service account based (JWT tokens) +- SDK: `github.com/nebius/gosdk` +- Documentation: https://github.com/nebius/api +- API Type: Locational (location-specific endpoints) + +## Key Features + +Nebius AI Cloud is known for: +- GPU instances and GPU clusters for AI/ML workloads +- Comprehensive compute, storage, and networking services +- gRPC-based API with strong typing +- Service account authentication with JWT tokens +- Location-specific API endpoints +- Advanced operations tracking and idempotency +- Integration with VPC, IAM, billing, and quota services +- Container registry and managed services + +## TODO + +- [ ] Implement actual API integration for supported features +- [ ] Add proper service account authentication handling +- [ ] Add comprehensive error handling and retry logic +- [ ] Add logging and monitoring +- [ ] Add comprehensive testing +- [ ] Investigate VPC integration for networking features +- [ ] Verify instance type changes work correctly via ResourcesSpec.preset field diff --git a/internal/nebius/v1/capabilities.go b/internal/nebius/v1/capabilities.go new file mode 100644 index 0000000..8d449c9 --- /dev/null +++ b/internal/nebius/v1/capabilities.go @@ -0,0 +1,37 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func getNebiusCapabilities() v1.Capabilities { + return v1.Capabilities{ + // SUPPORTED FEATURES (with API evidence): + + // Instance Management + v1.CapabilityCreateInstance, // Nebius compute API supports instance creation + v1.CapabilityTerminateInstance, // Nebius compute API supports instance deletion + v1.CapabilityCreateTerminateInstance, // Combined create/terminate capability + v1.CapabilityRebootInstance, // Nebius supports instance restart operations + v1.CapabilityStopStartInstance, // Nebius supports instance stop/start operations + + v1.CapabilityModifyFirewall, // Nebius has Security Groups for firewall management + v1.CapabilityMachineImage, // Nebius supports custom machine images + v1.CapabilityResizeInstanceVolume, // Nebius supports disk resizing + v1.CapabilityTags, // Nebius supports resource tagging + v1.CapabilityInstanceUserData, // Nebius supports user data in instance creation + + } +} + +// GetCapabilities returns the capabilities of Nebius client +func (c *NebiusClient) GetCapabilities(_ context.Context) (v1.Capabilities, error) { + return getNebiusCapabilities(), nil +} + +// GetCapabilities returns the capabilities for Nebius credential +func (c *NebiusCredential) GetCapabilities(_ context.Context) (v1.Capabilities, error) { + return getNebiusCapabilities(), nil +} diff --git a/internal/nebius/v1/client.go b/internal/nebius/v1/client.go new file mode 100644 index 0000000..9166671 --- /dev/null +++ b/internal/nebius/v1/client.go @@ -0,0 +1,106 @@ +package v1 + +import ( + "context" + "fmt" + + v1 "github.com/brevdev/compute/pkg/v1" + "github.com/nebius/gosdk" +) + +type NebiusCredential struct { + RefID string + ServiceAccountKey string // JSON service account key + ProjectID string +} + +var _ v1.CloudCredential = &NebiusCredential{} + +func NewNebiusCredential(refID, serviceAccountKey, projectID string) *NebiusCredential { + return &NebiusCredential{ + RefID: refID, + ServiceAccountKey: serviceAccountKey, + ProjectID: projectID, + } +} + +// GetReferenceID returns the reference ID for this credential +func (c *NebiusCredential) GetReferenceID() string { + return c.RefID +} + +// GetAPIType returns the API type for Nebius +func (c *NebiusCredential) GetAPIType() v1.APIType { + return v1.APITypeLocational // Nebius uses location-specific endpoints +} + +// GetCloudProviderID returns the cloud provider ID for Nebius +func (c *NebiusCredential) GetCloudProviderID() v1.CloudProviderID { + return "nebius" +} + +// GetTenantID returns the tenant ID for Nebius (project ID) +func (c *NebiusCredential) GetTenantID() (string, error) { + if c.ProjectID == "" { + return "", fmt.Errorf("project ID is required for Nebius") + } + return c.ProjectID, nil +} + +func (c *NebiusCredential) MakeClient(ctx context.Context, location string) (v1.CloudClient, error) { + return NewNebiusClient(ctx, c.RefID, c.ServiceAccountKey, c.ProjectID, location) +} + +// It embeds NotImplCloudClient to handle unsupported features +type NebiusClient struct { + v1.NotImplCloudClient + refID string + serviceAccountKey string + projectID string + location string + sdk *gosdk.SDK +} + +var _ v1.CloudClient = &NebiusClient{} + +func NewNebiusClient(ctx context.Context, refID, serviceAccountKey, projectID, location string) (*NebiusClient, error) { + sdk, err := gosdk.New(ctx, gosdk.WithCredentials( + gosdk.IAMToken(serviceAccountKey), // For now, treat as IAM token - will need proper service account handling later + )) + if err != nil { + return nil, fmt.Errorf("failed to initialize Nebius SDK: %w", err) + } + + return &NebiusClient{ + refID: refID, + serviceAccountKey: serviceAccountKey, + projectID: projectID, + location: location, + sdk: sdk, + }, nil +} + +// GetAPIType returns the API type for Nebius +func (c *NebiusClient) GetAPIType() v1.APIType { + return v1.APITypeLocational +} + +// GetCloudProviderID returns the cloud provider ID for Nebius +func (c *NebiusClient) GetCloudProviderID() v1.CloudProviderID { + return "nebius" +} + +// MakeClient creates a new client instance for a different location +func (c *NebiusClient) MakeClient(ctx context.Context, location string) (v1.CloudClient, error) { + return NewNebiusClient(ctx, c.refID, c.serviceAccountKey, c.projectID, location) +} + +// GetTenantID returns the tenant ID for Nebius +func (c *NebiusClient) GetTenantID() (string, error) { + return c.projectID, nil +} + +// GetReferenceID returns the reference ID for this client +func (c *NebiusClient) GetReferenceID() string { + return c.refID +} diff --git a/internal/nebius/v1/image.go b/internal/nebius/v1/image.go new file mode 100644 index 0000000..3d46386 --- /dev/null +++ b/internal/nebius/v1/image.go @@ -0,0 +1,11 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) GetImages(_ context.Context, _ v1.GetImageArgs) ([]v1.Image, error) { + return nil, v1.ErrNotImplemented +} diff --git a/internal/nebius/v1/instance.go b/internal/nebius/v1/instance.go new file mode 100644 index 0000000..d1a4688 --- /dev/null +++ b/internal/nebius/v1/instance.go @@ -0,0 +1,49 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) CreateInstance(_ context.Context, _ v1.CreateInstanceAttrs) (*v1.Instance, error) { + return nil, v1.ErrNotImplemented +} + +func (c *NebiusClient) GetInstance(_ context.Context, _ v1.CloudProviderInstanceID) (*v1.Instance, error) { + return nil, v1.ErrNotImplemented +} + +func (c *NebiusClient) TerminateInstance(_ context.Context, _ v1.CloudProviderInstanceID) error { + return v1.ErrNotImplemented +} + +func (c *NebiusClient) ListInstances(_ context.Context, _ v1.ListInstancesArgs) ([]v1.Instance, error) { + return nil, v1.ErrNotImplemented +} + +func (c *NebiusClient) StopInstance(_ context.Context, _ v1.CloudProviderInstanceID) error { + return v1.ErrNotImplemented +} + +func (c *NebiusClient) StartInstance(_ context.Context, _ v1.CloudProviderInstanceID) error { + return v1.ErrNotImplemented +} + +func (c *NebiusClient) RebootInstance(_ context.Context, _ v1.CloudProviderInstanceID) error { + return v1.ErrNotImplemented +} + +func (c *NebiusClient) MergeInstanceForUpdate(currInst v1.Instance, newInst v1.Instance) v1.Instance { + merged := newInst + + merged.Name = currInst.Name + merged.RefID = currInst.RefID + merged.CloudCredRefID = currInst.CloudCredRefID + merged.CreatedAt = currInst.CreatedAt + merged.CloudID = currInst.CloudID + merged.Location = currInst.Location + merged.SubLocation = currInst.SubLocation + + return merged +} diff --git a/internal/nebius/v1/instancetype.go b/internal/nebius/v1/instancetype.go new file mode 100644 index 0000000..509b670 --- /dev/null +++ b/internal/nebius/v1/instancetype.go @@ -0,0 +1,24 @@ +package v1 + +import ( + "context" + "time" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) GetInstanceTypes(_ context.Context, _ v1.GetInstanceTypeArgs) ([]v1.InstanceType, error) { + return nil, v1.ErrNotImplemented +} + +func (c *NebiusClient) GetInstanceTypePollTime() time.Duration { + return 5 * time.Minute +} + +func (c *NebiusClient) MergeInstanceTypeForUpdate(currIt v1.InstanceType, newIt v1.InstanceType) v1.InstanceType { + merged := newIt + + merged.ID = currIt.ID + + return merged +} diff --git a/internal/nebius/v1/location.go b/internal/nebius/v1/location.go new file mode 100644 index 0000000..8a1c17b --- /dev/null +++ b/internal/nebius/v1/location.go @@ -0,0 +1,11 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) GetLocations(_ context.Context, _ v1.GetLocationsArgs) ([]v1.Location, error) { + return nil, v1.ErrNotImplemented +} diff --git a/internal/nebius/v1/networking.go b/internal/nebius/v1/networking.go new file mode 100644 index 0000000..f912b74 --- /dev/null +++ b/internal/nebius/v1/networking.go @@ -0,0 +1,15 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) AddFirewallRulesToInstance(_ context.Context, _ v1.AddFirewallRulesToInstanceArgs) error { + return v1.ErrNotImplemented +} + +func (c *NebiusClient) RevokeSecurityGroupRules(_ context.Context, _ v1.RevokeSecurityGroupRuleArgs) error { + return v1.ErrNotImplemented +} diff --git a/internal/nebius/v1/quota.go b/internal/nebius/v1/quota.go new file mode 100644 index 0000000..9920f67 --- /dev/null +++ b/internal/nebius/v1/quota.go @@ -0,0 +1,11 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) GetInstanceTypeQuotas(_ context.Context, _ v1.GetInstanceTypeQuotasArgs) (v1.Quota, error) { + return v1.Quota{}, v1.ErrNotImplemented +} diff --git a/internal/nebius/v1/storage.go b/internal/nebius/v1/storage.go new file mode 100644 index 0000000..3d71df8 --- /dev/null +++ b/internal/nebius/v1/storage.go @@ -0,0 +1,11 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) ResizeInstanceVolume(_ context.Context, _ v1.ResizeInstanceVolumeArgs) error { + return v1.ErrNotImplemented +} diff --git a/internal/nebius/v1/tags.go b/internal/nebius/v1/tags.go new file mode 100644 index 0000000..e186b1b --- /dev/null +++ b/internal/nebius/v1/tags.go @@ -0,0 +1,11 @@ +package v1 + +import ( + "context" + + v1 "github.com/brevdev/compute/pkg/v1" +) + +func (c *NebiusClient) UpdateInstanceTags(_ context.Context, _ v1.UpdateInstanceTagsArgs) error { + return v1.ErrNotImplemented +}