package cloud 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 region string configured bool } // NewOVHProvider creates a new OVH provider func NewOVHProvider() Provider { return &OVHProvider{ entry: logrus.WithField("provider", "ovh"), } } func init() { RegisterProvider("ovh", NewOVHProvider) } // Initialize sets up the OVH provider with credentials and configuration func (p *OVHProvider) Initialize(config map[string]string) error { // Check required configuration 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) } } // Create OVH client 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) } // 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 // Set region if provided if region, ok := config["region"]; ok { p.region = region } p.entry.Info("OVH provider initialized") return nil } // ListRegions lists all available OVH regions func (p *OVHProvider) ListRegions(ctx context.Context) ([]Region, error) { if !p.configured { return nil, errors.New("provider not configured") } 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) } fmt.Printf("OVH regions: %v\n", ovhRegions) // Convert OVHRegion to Region for _, ovhRegion := range ovhRegions { regions = append(regions, Region{ 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) { // TODO: Implement this method return nil, errors.New("not implemented") } // ListInstances lists all instances in OVH func (p *OVHProvider) ListInstances(ctx context.Context) ([]Instance, error) { if !p.configured { return nil, errors.New("provider not configured") } 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) } // 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) } 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, } } return instances, 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 { // 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 { // 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 { // TODO: Implement this method return errors.New("not implemented") } // 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") } // 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 nil, fmt.Errorf("failed to list instances: %w", 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 } // Mark this instance as in use if instance.Tags != nil { instance.Tags["byop-state"] = "in-use" } return &Instance{ ID: vps.Name, Name: vps.DisplayName, IPAddress: vps.Name, Region: vps.Zone, Size: strconv.Itoa(vps.VCore), Status: vps.State, }, nil } } // 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") }