|
@@ -4,22 +4,28 @@ import (
|
|
|
"context"
|
|
|
"errors"
|
|
|
"fmt"
|
|
|
+ "strconv"
|
|
|
+ "strings"
|
|
|
"time"
|
|
|
|
|
|
+ "git.linuxforward.com/byop/byop-engine/models"
|
|
|
"github.com/ovh/go-ovh/ovh"
|
|
|
+ "github.com/sirupsen/logrus"
|
|
|
)
|
|
|
|
|
|
// OVHProvider implements the Provider interface for OVH Cloud
|
|
|
type OVHProvider struct {
|
|
|
+ entry *logrus.Entry
|
|
|
client *ovh.Client
|
|
|
- projectID string
|
|
|
region string
|
|
|
configured bool
|
|
|
}
|
|
|
|
|
|
// NewOVHProvider creates a new OVH provider
|
|
|
func NewOVHProvider() Provider {
|
|
|
- return &OVHProvider{}
|
|
|
+ return &OVHProvider{
|
|
|
+ entry: logrus.WithField("provider", "ovh"),
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
func init() {
|
|
@@ -29,7 +35,7 @@ func init() {
|
|
|
// Initialize sets up the OVH provider with credentials and configuration
|
|
|
func (p *OVHProvider) Initialize(config map[string]string) error {
|
|
|
// Check required configuration
|
|
|
- requiredKeys := []string{"application_key", "application_secret", "consumer_key", "project_id"}
|
|
|
+ requiredKeys := []string{"client_id", "client_secret", "endpoint"}
|
|
|
for _, key := range requiredKeys {
|
|
|
if _, ok := config[key]; !ok {
|
|
|
return fmt.Errorf("missing required configuration key: %s", key)
|
|
@@ -37,44 +43,32 @@ func (p *OVHProvider) Initialize(config map[string]string) error {
|
|
|
}
|
|
|
|
|
|
// Create OVH client
|
|
|
- client, err := ovh.NewClient(
|
|
|
- "ovh-eu", // Endpoint (can be configurable)
|
|
|
- config["application_key"],
|
|
|
- config["application_secret"],
|
|
|
- config["consumer_key"],
|
|
|
+ client, err := ovh.NewOAuth2Client(
|
|
|
+ config["endpoint"],
|
|
|
+ config["client_id"],
|
|
|
+ config["client_secret"],
|
|
|
)
|
|
|
if err != nil {
|
|
|
return fmt.Errorf("failed to create OVH client: %w", err)
|
|
|
}
|
|
|
|
|
|
- p.client = client
|
|
|
- p.projectID = config["project_id"]
|
|
|
-
|
|
|
- // Set default region if provided
|
|
|
- if region, ok := config["region"]; ok {
|
|
|
- p.region = region
|
|
|
+ // Test the client by trying to list VPS IDs
|
|
|
+ var vpsIDs []string
|
|
|
+ if err = client.Get("/vps", &vpsIDs); err != nil {
|
|
|
+ return fmt.Errorf("failed to connect to OVH API: %w", err)
|
|
|
}
|
|
|
|
|
|
+ // Set client before testing VPS connectivity
|
|
|
+ p.client = client
|
|
|
p.configured = true
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// Validate checks if the OVH provider credentials are valid
|
|
|
-func (p *OVHProvider) Validate(ctx context.Context) (bool, error) {
|
|
|
- if !p.configured {
|
|
|
- return false, errors.New("provider not configured")
|
|
|
+ // Set region if provided
|
|
|
+ if region, ok := config["region"]; ok {
|
|
|
+ p.region = region
|
|
|
}
|
|
|
|
|
|
- // Try to get project info to verify credentials
|
|
|
- path := fmt.Sprintf("/cloud/project/%s", p.projectID)
|
|
|
- var project map[string]interface{}
|
|
|
-
|
|
|
- err := p.client.Get(path, &project)
|
|
|
- if err != nil {
|
|
|
- return false, fmt.Errorf("validation failed: %w", err)
|
|
|
- }
|
|
|
+ p.entry.Info("OVH provider initialized")
|
|
|
|
|
|
- return true, nil
|
|
|
+ return nil
|
|
|
}
|
|
|
|
|
|
// ListRegions lists all available OVH regions
|
|
@@ -83,81 +77,51 @@ func (p *OVHProvider) ListRegions(ctx context.Context) ([]Region, error) {
|
|
|
return nil, errors.New("provider not configured")
|
|
|
}
|
|
|
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/region", p.projectID)
|
|
|
- var regionIDs []string
|
|
|
-
|
|
|
- err := p.client.Get(path, ®ionIDs)
|
|
|
+ type OVHRegion struct {
|
|
|
+ AvailabilityZones []string `json:"availabilityZones"`
|
|
|
+ CardinalPoint string `json:"cardinalPoint"`
|
|
|
+ CityCode string `json:"cityCode"`
|
|
|
+ CityLatitude float64 `json:"cityLatitude"`
|
|
|
+ CityLongitude float64 `json:"cityLongitude"`
|
|
|
+ CityName string `json:"cityName"`
|
|
|
+ Code string `json:"code"`
|
|
|
+ CountryCode string `json:"countryCode"`
|
|
|
+ CountryName string `json:"countryName"`
|
|
|
+ GeographyCode string `json:"geographyCode"`
|
|
|
+ GeographyName string `json:"geographyName"`
|
|
|
+ Location string `json:"location"`
|
|
|
+ Name string `json:"name"`
|
|
|
+ OpeningYear int `json:"openingYear"`
|
|
|
+ SpecificType string `json:"specificType"`
|
|
|
+ Type string `json:"type"`
|
|
|
+ }
|
|
|
+
|
|
|
+ // Get the list of regions from OVH
|
|
|
+ path := "/v2/location"
|
|
|
+ var regions []Region
|
|
|
+ var ovhRegions []OVHRegion
|
|
|
+ err := p.client.Get(path, &ovhRegions)
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("failed to list regions: %w", err)
|
|
|
}
|
|
|
|
|
|
- regions := make([]Region, 0, len(regionIDs))
|
|
|
- for _, id := range regionIDs {
|
|
|
- var regionDetails struct {
|
|
|
- Name string `json:"name"`
|
|
|
- ContinentCode string `json:"continentCode"`
|
|
|
- Status string `json:"status"`
|
|
|
- }
|
|
|
-
|
|
|
- regionPath := fmt.Sprintf("/cloud/project/%s/region/%s", p.projectID, id)
|
|
|
- err := p.client.Get(regionPath, ®ionDetails)
|
|
|
- if err != nil {
|
|
|
- continue // Skip this region if we can't get details
|
|
|
- }
|
|
|
+ fmt.Printf("OVH regions: %v\n", ovhRegions)
|
|
|
|
|
|
+ // Convert OVHRegion to Region
|
|
|
+ for _, ovhRegion := range ovhRegions {
|
|
|
regions = append(regions, Region{
|
|
|
- ID: id,
|
|
|
- Name: regionDetails.Name,
|
|
|
- Zone: regionDetails.ContinentCode,
|
|
|
+ ID: ovhRegion.Name,
|
|
|
+ Zone: strings.Join(ovhRegion.AvailabilityZones, ","),
|
|
|
+ // Add other fields as needed
|
|
|
})
|
|
|
}
|
|
|
-
|
|
|
return regions, nil
|
|
|
}
|
|
|
|
|
|
// ListInstanceSizes lists available VM sizes (flavors) in OVH
|
|
|
func (p *OVHProvider) ListInstanceSizes(ctx context.Context, region string) ([]InstanceSize, error) {
|
|
|
- if !p.configured {
|
|
|
- return nil, errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- if region == "" {
|
|
|
- region = p.region
|
|
|
- }
|
|
|
-
|
|
|
- if region == "" {
|
|
|
- return nil, errors.New("region must be specified")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/flavor?region=%s", p.projectID, region)
|
|
|
- var flavors []struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Name string `json:"name"`
|
|
|
- Vcpus int `json:"vcpus"`
|
|
|
- RAM int `json:"ram"` // in MB
|
|
|
- Disk int `json:"disk"` // in GB
|
|
|
- Type string `json:"type"`
|
|
|
- HourlyPrice float64 `json:"hourlyPrice"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Get(path, &flavors)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to list flavors: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- sizes := make([]InstanceSize, 0, len(flavors))
|
|
|
- for _, flavor := range flavors {
|
|
|
- sizes = append(sizes, InstanceSize{
|
|
|
- ID: flavor.ID,
|
|
|
- Name: flavor.Name,
|
|
|
- CPUCores: flavor.Vcpus,
|
|
|
- MemoryGB: flavor.RAM / 1024, // Convert MB to GB
|
|
|
- DiskGB: flavor.Disk,
|
|
|
- Price: flavor.HourlyPrice,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- return sizes, nil
|
|
|
+ // TODO: Implement this method
|
|
|
+ return nil, errors.New("not implemented")
|
|
|
}
|
|
|
|
|
|
// ListInstances lists all instances in OVH
|
|
@@ -166,407 +130,147 @@ func (p *OVHProvider) ListInstances(ctx context.Context) ([]Instance, error) {
|
|
|
return nil, errors.New("provider not configured")
|
|
|
}
|
|
|
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance", p.projectID)
|
|
|
- var ovhInstances []struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Name string `json:"name"`
|
|
|
- Status string `json:"status"`
|
|
|
- Created time.Time `json:"created"`
|
|
|
- Region string `json:"region"`
|
|
|
- FlavorID string `json:"flavorId"`
|
|
|
- ImageID string `json:"imageId"`
|
|
|
- IPAddresses []struct {
|
|
|
- IP string `json:"ip"`
|
|
|
- Type string `json:"type"` // public or private
|
|
|
- Version int `json:"version"` // 4 or 6
|
|
|
- } `json:"ipAddresses"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Get(path, &ovhInstances)
|
|
|
+ path := "/vps"
|
|
|
+ var vpsIDs []string
|
|
|
+ var vpsList []*models.OVHVPS
|
|
|
+ err := p.client.Get(path, &vpsIDs)
|
|
|
if err != nil {
|
|
|
return nil, fmt.Errorf("failed to list instances: %w", err)
|
|
|
}
|
|
|
|
|
|
- instances := make([]Instance, 0, len(ovhInstances))
|
|
|
- for _, ovhInstance := range ovhInstances {
|
|
|
- instance := Instance{
|
|
|
- ID: ovhInstance.ID,
|
|
|
- Name: ovhInstance.Name,
|
|
|
- Region: ovhInstance.Region,
|
|
|
- Size: ovhInstance.FlavorID,
|
|
|
- ImageID: ovhInstance.ImageID,
|
|
|
- Status: mapOVHStatus(ovhInstance.Status),
|
|
|
- CreatedAt: ovhInstance.Created,
|
|
|
+ // Get details for each VPS ID
|
|
|
+ for _, vpsID := range vpsIDs {
|
|
|
+ path := fmt.Sprintf("/vps/%s", vpsID)
|
|
|
+ vps := &models.OVHVPS{}
|
|
|
+ err := p.client.Get(path, vps)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to get instance %s: %w", vpsID, err)
|
|
|
}
|
|
|
-
|
|
|
- // Extract IP addresses
|
|
|
- for _, ip := range ovhInstance.IPAddresses {
|
|
|
- if ip.Version == 4 { // Only use IPv4 for now
|
|
|
- if ip.Type == "public" {
|
|
|
- instance.IPAddress = ip.IP
|
|
|
- } else {
|
|
|
- instance.PrivateIP = ip.IP
|
|
|
- }
|
|
|
- }
|
|
|
+ vpsList = append(vpsList, vps)
|
|
|
+ }
|
|
|
+
|
|
|
+ instances := make([]Instance, len(vpsList))
|
|
|
+ for i, vps := range vpsList {
|
|
|
+ // convert size
|
|
|
+ instances[i] = Instance{
|
|
|
+ ID: vps.Name,
|
|
|
+ Name: vps.DisplayName,
|
|
|
+ Region: vps.Zone,
|
|
|
+ Size: strconv.Itoa(vps.VCore),
|
|
|
+ Status: vps.State,
|
|
|
}
|
|
|
-
|
|
|
- instances = append(instances, instance)
|
|
|
}
|
|
|
|
|
|
return instances, nil
|
|
|
}
|
|
|
|
|
|
-// GetInstance gets a specific instance by ID
|
|
|
-func (p *OVHProvider) GetInstance(ctx context.Context, id string) (*Instance, error) {
|
|
|
- if !p.configured {
|
|
|
- return nil, errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance/%s", p.projectID, id)
|
|
|
- var ovhInstance struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Name string `json:"name"`
|
|
|
- Status string `json:"status"`
|
|
|
- Created time.Time `json:"created"`
|
|
|
- Region string `json:"region"`
|
|
|
- FlavorID string `json:"flavorId"`
|
|
|
- ImageID string `json:"imageId"`
|
|
|
- IPAddresses []struct {
|
|
|
- IP string `json:"ip"`
|
|
|
- Type string `json:"type"` // public or private
|
|
|
- Version int `json:"version"` // 4 or 6
|
|
|
- } `json:"ipAddresses"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Get(path, &ovhInstance)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to get instance: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- instance := &Instance{
|
|
|
- ID: ovhInstance.ID,
|
|
|
- Name: ovhInstance.Name,
|
|
|
- Region: ovhInstance.Region,
|
|
|
- Size: ovhInstance.FlavorID,
|
|
|
- ImageID: ovhInstance.ImageID,
|
|
|
- Status: mapOVHStatus(ovhInstance.Status),
|
|
|
- CreatedAt: ovhInstance.Created,
|
|
|
- }
|
|
|
-
|
|
|
- // Extract IP addresses
|
|
|
- for _, ip := range ovhInstance.IPAddresses {
|
|
|
- if ip.Version == 4 { // Only use IPv4 for now
|
|
|
- if ip.Type == "public" {
|
|
|
- instance.IPAddress = ip.IP
|
|
|
- } else {
|
|
|
- instance.PrivateIP = ip.IP
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- return instance, nil
|
|
|
-}
|
|
|
-
|
|
|
-// CreateInstance creates a new instance in OVH
|
|
|
-func (p *OVHProvider) CreateInstance(ctx context.Context, opts InstanceCreateOpts) (*Instance, error) {
|
|
|
- if !p.configured {
|
|
|
- return nil, errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- // Prepare create request
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance", p.projectID)
|
|
|
- request := struct {
|
|
|
- Name string `json:"name"`
|
|
|
- Region string `json:"region"`
|
|
|
- FlavorID string `json:"flavorId"`
|
|
|
- ImageID string `json:"imageId"`
|
|
|
- SSHKeyID []string `json:"sshKeyId,omitempty"`
|
|
|
- UserData string `json:"userData,omitempty"`
|
|
|
- Networks []string `json:"networks,omitempty"`
|
|
|
- }{
|
|
|
- Name: opts.Name,
|
|
|
- Region: opts.Region,
|
|
|
- FlavorID: opts.Size,
|
|
|
- ImageID: opts.ImageID,
|
|
|
- SSHKeyID: opts.SSHKeyIDs,
|
|
|
- UserData: opts.UserData,
|
|
|
- }
|
|
|
-
|
|
|
- var result struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Status string `json:"status"`
|
|
|
- Created string `json:"created"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Post(path, request, &result)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to create instance: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- // Fetch the full instance details
|
|
|
- createdInstance, err := p.GetInstance(ctx, result.ID)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("instance created but failed to retrieve details: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return createdInstance, nil
|
|
|
-}
|
|
|
-
|
|
|
-// DeleteInstance deletes an instance in OVH
|
|
|
-func (p *OVHProvider) DeleteInstance(ctx context.Context, id string) error {
|
|
|
- if !p.configured {
|
|
|
- return errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance/%s", p.projectID, id)
|
|
|
- err := p.client.Delete(path, nil)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to delete instance: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+// ResetInstance resets an instance in OVH
|
|
|
+func (p *OVHProvider) ResetInstance(ctx context.Context, id string) error {
|
|
|
+ // TODO: Implement this method
|
|
|
+ return errors.New("not implemented")
|
|
|
}
|
|
|
|
|
|
// StartInstance starts an instance in OVH
|
|
|
func (p *OVHProvider) StartInstance(ctx context.Context, id string) error {
|
|
|
- if !p.configured {
|
|
|
- return errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance/%s/start", p.projectID, id)
|
|
|
- err := p.client.Post(path, nil, nil)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to start instance: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+ // TODO: Implement this method
|
|
|
+ return errors.New("not implemented")
|
|
|
}
|
|
|
|
|
|
// StopInstance stops an instance in OVH
|
|
|
func (p *OVHProvider) StopInstance(ctx context.Context, id string) error {
|
|
|
- if !p.configured {
|
|
|
- return errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance/%s/stop", p.projectID, id)
|
|
|
- err := p.client.Post(path, nil, nil)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to stop instance: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+ // TODO: Implement this method
|
|
|
+ return errors.New("not implemented")
|
|
|
}
|
|
|
|
|
|
// RestartInstance restarts an instance in OVH
|
|
|
func (p *OVHProvider) RestartInstance(ctx context.Context, id string) error {
|
|
|
- if !p.configured {
|
|
|
- return errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/instance/%s/reboot", p.projectID, id)
|
|
|
- err := p.client.Post(path, nil, nil)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to restart instance: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
-}
|
|
|
-
|
|
|
-// ListImages lists available OS images in OVH
|
|
|
-func (p *OVHProvider) ListImages(ctx context.Context) ([]Image, error) {
|
|
|
- if !p.configured {
|
|
|
- return nil, errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- // Get all images
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/image", p.projectID)
|
|
|
- var ovhImages []struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Name string `json:"name"`
|
|
|
- Region string `json:"region"`
|
|
|
- Visibility string `json:"visibility"`
|
|
|
- Type string `json:"type"`
|
|
|
- Status string `json:"status"`
|
|
|
- CreationDate time.Time `json:"creationDate"`
|
|
|
- MinDisk int `json:"minDisk"`
|
|
|
- Size int `json:"size"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Get(path, &ovhImages)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to list images: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- images := make([]Image, 0, len(ovhImages))
|
|
|
- for _, ovhImage := range ovhImages {
|
|
|
- images = append(images, Image{
|
|
|
- ID: ovhImage.ID,
|
|
|
- Name: ovhImage.Name,
|
|
|
- Description: ovhImage.Type,
|
|
|
- Type: ovhImage.Type,
|
|
|
- Status: ovhImage.Status,
|
|
|
- CreatedAt: ovhImage.CreationDate,
|
|
|
- MinDiskGB: ovhImage.MinDisk,
|
|
|
- SizeGB: ovhImage.Size / (1024 * 1024 * 1024), // Convert bytes to GB
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- return images, nil
|
|
|
-}
|
|
|
-
|
|
|
-// ListSSHKeys lists SSH keys in OVH
|
|
|
-func (p *OVHProvider) ListSSHKeys(ctx context.Context) ([]SSHKey, error) {
|
|
|
- if !p.configured {
|
|
|
- return nil, errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/sshkey", p.projectID)
|
|
|
- var ovhKeys []struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Name string `json:"name"`
|
|
|
- PublicKey string `json:"publicKey"`
|
|
|
- Fingerprint string `json:"fingerprint"`
|
|
|
- CreatedAt time.Time `json:"creationDate"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Get(path, &ovhKeys)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to list SSH keys: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- keys := make([]SSHKey, 0, len(ovhKeys))
|
|
|
- for _, ovhKey := range ovhKeys {
|
|
|
- keys = append(keys, SSHKey{
|
|
|
- ID: ovhKey.ID,
|
|
|
- Name: ovhKey.Name,
|
|
|
- PublicKey: ovhKey.PublicKey,
|
|
|
- Fingerprint: ovhKey.Fingerprint,
|
|
|
- CreatedAt: ovhKey.CreatedAt,
|
|
|
- })
|
|
|
- }
|
|
|
-
|
|
|
- return keys, nil
|
|
|
+ // TODO: Implement this method
|
|
|
+ return errors.New("not implemented")
|
|
|
}
|
|
|
|
|
|
-// CreateSSHKey creates a new SSH key in OVH
|
|
|
-func (p *OVHProvider) CreateSSHKey(ctx context.Context, name, publicKey string) (*SSHKey, error) {
|
|
|
- if !p.configured {
|
|
|
- return nil, errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/sshkey", p.projectID)
|
|
|
- request := struct {
|
|
|
- Name string `json:"name"`
|
|
|
- PublicKey string `json:"publicKey"`
|
|
|
- Region string `json:"region,omitempty"`
|
|
|
- }{
|
|
|
- Name: name,
|
|
|
- PublicKey: publicKey,
|
|
|
- Region: p.region, // Optional region
|
|
|
- }
|
|
|
-
|
|
|
- var result struct {
|
|
|
- ID string `json:"id"`
|
|
|
- Name string `json:"name"`
|
|
|
- PublicKey string `json:"publicKey"`
|
|
|
- Fingerprint string `json:"fingerprint"`
|
|
|
- CreatedAt time.Time `json:"creationDate"`
|
|
|
- }
|
|
|
-
|
|
|
- err := p.client.Post(path, request, &result)
|
|
|
- if err != nil {
|
|
|
- return nil, fmt.Errorf("failed to create SSH key: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return &SSHKey{
|
|
|
- ID: result.ID,
|
|
|
- Name: result.Name,
|
|
|
- PublicKey: result.PublicKey,
|
|
|
- Fingerprint: result.Fingerprint,
|
|
|
- CreatedAt: result.CreatedAt,
|
|
|
- }, nil
|
|
|
-}
|
|
|
-
|
|
|
-// DeleteSSHKey deletes an SSH key in OVH
|
|
|
-func (p *OVHProvider) DeleteSSHKey(ctx context.Context, id string) error {
|
|
|
- if !p.configured {
|
|
|
- return errors.New("provider not configured")
|
|
|
- }
|
|
|
-
|
|
|
- path := fmt.Sprintf("/cloud/project/%s/sshkey/%s", p.projectID, id)
|
|
|
- err := p.client.Delete(path, nil)
|
|
|
- if err != nil {
|
|
|
- return fmt.Errorf("failed to delete SSH key: %w", err)
|
|
|
- }
|
|
|
-
|
|
|
- return nil
|
|
|
+// WaitForInstanceStatus waits for an instance to reach a specific status
|
|
|
+func (p *OVHProvider) WaitForInstanceStatus(ctx context.Context, id, status string, timeout time.Duration) error {
|
|
|
+ // TODO: Implement this method
|
|
|
+ return errors.New("not implemented")
|
|
|
}
|
|
|
|
|
|
-// GetInstanceStatus gets the current status of an instance in OVH
|
|
|
-func (p *OVHProvider) GetInstanceStatus(ctx context.Context, id string) (string, error) {
|
|
|
- instance, err := p.GetInstance(ctx, id)
|
|
|
+// GetFirstFreeInstance retrieves the first available instance
|
|
|
+func (p *OVHProvider) GetFirstFreeInstance(ctx context.Context) (*Instance, error) {
|
|
|
+ // List all instances
|
|
|
+ instances, err := p.ListInstances(ctx)
|
|
|
if err != nil {
|
|
|
- return "", err
|
|
|
+ return nil, fmt.Errorf("failed to list instances: %w", err)
|
|
|
}
|
|
|
|
|
|
- return instance.Status, nil
|
|
|
-}
|
|
|
-
|
|
|
-// WaitForInstanceStatus waits for an instance to reach a specific status
|
|
|
-func (p *OVHProvider) WaitForInstanceStatus(ctx context.Context, id, status string, timeout time.Duration) error {
|
|
|
- deadline := time.Now().Add(timeout)
|
|
|
-
|
|
|
- for time.Now().Before(deadline) {
|
|
|
- currentStatus, err := p.GetInstanceStatus(ctx, id)
|
|
|
- if err != nil {
|
|
|
- return err
|
|
|
- }
|
|
|
+ // Iterate through instances to find the first free one
|
|
|
+ for _, instance := range instances {
|
|
|
+ // This will be final optioons using tags
|
|
|
+ // Check if instance has tags and if the instance is marked as available
|
|
|
+ // if instance.DisplayName != nil && instance.Tags["byop-state"] == "available" {
|
|
|
+ // // Get full details of the instance
|
|
|
+ // vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
|
|
|
+ // vps := &models.OVHVPS{}
|
|
|
+ // err := p.client.Get(vpsPath, vps)
|
|
|
+ // if err != nil {
|
|
|
+ // // Log the error but continue with next instance
|
|
|
+ // fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
|
|
|
+ // continue
|
|
|
+ // }
|
|
|
+
|
|
|
+ // return vps, nil
|
|
|
+ // }
|
|
|
+
|
|
|
+ // Check if display name contains byom.fr or byop.fr
|
|
|
+ if instance.Name != "" && (strings.Contains(instance.Name, "byom.fr") || strings.Contains(instance.Name, "byop.fr")) {
|
|
|
+ // Get full details of the instance
|
|
|
+ vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
|
|
|
+ vps := &models.OVHVPS{}
|
|
|
+ err := p.client.Get(vpsPath, vps)
|
|
|
+ if err != nil {
|
|
|
+ // Log the error but continue with next instance
|
|
|
+ fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
|
|
|
- if currentStatus == status {
|
|
|
- return nil
|
|
|
- }
|
|
|
+ // Mark this instance as in use
|
|
|
+ if instance.Tags != nil {
|
|
|
+ instance.Tags["byop-state"] = "in-use"
|
|
|
+ }
|
|
|
|
|
|
- select {
|
|
|
- case <-ctx.Done():
|
|
|
- return ctx.Err()
|
|
|
- case <-time.After(5 * time.Second):
|
|
|
- // Wait 5 seconds before next check
|
|
|
+ return &Instance{
|
|
|
+ ID: vps.Name,
|
|
|
+ Name: vps.DisplayName,
|
|
|
+ IPAddress: vps.Name,
|
|
|
+ Region: vps.Zone,
|
|
|
+ Size: strconv.Itoa(vps.VCore),
|
|
|
+ Status: vps.State,
|
|
|
+ }, nil
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- return fmt.Errorf("timeout waiting for instance %s to reach status %s", id, status)
|
|
|
-}
|
|
|
-
|
|
|
-// mapOVHStatus maps OVH instance status to standardized status
|
|
|
-func mapOVHStatus(ovhStatus string) string {
|
|
|
- switch ovhStatus {
|
|
|
- case "ACTIVE":
|
|
|
- return "Running"
|
|
|
- case "BUILD":
|
|
|
- return "Creating"
|
|
|
- case "BUILDING":
|
|
|
- return "Creating"
|
|
|
- case "SHUTOFF":
|
|
|
- return "Stopped"
|
|
|
- case "DELETED":
|
|
|
- return "Terminated"
|
|
|
- case "SOFT_DELETED":
|
|
|
- return "Terminated"
|
|
|
- case "HARD_REBOOT":
|
|
|
- return "Restarting"
|
|
|
- case "REBOOT":
|
|
|
- return "Restarting"
|
|
|
- case "RESCUE":
|
|
|
- return "Running"
|
|
|
- case "ERROR":
|
|
|
- return "Error"
|
|
|
- case "PAUSED":
|
|
|
- return "Stopped"
|
|
|
- case "SUSPENDED":
|
|
|
- return "Stopped"
|
|
|
- case "STOPPING":
|
|
|
- return "Stopping"
|
|
|
- default:
|
|
|
- return ovhStatus
|
|
|
- }
|
|
|
+ // If no instance with "available" tag is found, just return the first running instance
|
|
|
+ // if len(instances) > 0 {
|
|
|
+ // for _, instance := range instances {
|
|
|
+ // if instance.Status == "Running" {
|
|
|
+ // vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
|
|
|
+ // vps := &models.OVHVPS{}
|
|
|
+ // err := p.client.Get(vpsPath, vps)
|
|
|
+ // if err != nil {
|
|
|
+ // // Log the error but continue
|
|
|
+ // fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
|
|
|
+ // continue
|
|
|
+ // }
|
|
|
+
|
|
|
+ // // Mark this instance as in use
|
|
|
+ // if instance.Tags != nil {
|
|
|
+ // instance.Tags["byop-state"] = "in-use"
|
|
|
+ // }
|
|
|
+
|
|
|
+ // return vps, nil
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ // If no instances are found or none are available, return an error
|
|
|
+ return nil, fmt.Errorf("no free instances found in OVH infrastructure")
|
|
|
}
|