ovh.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. package cloud
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "strconv"
  7. "strings"
  8. "time"
  9. "git.linuxforward.com/byop/byop-engine/models"
  10. "github.com/ovh/go-ovh/ovh"
  11. "github.com/sirupsen/logrus"
  12. )
  13. // OVHProvider implements the Provider interface for OVH Cloud
  14. type OVHProvider struct {
  15. entry *logrus.Entry
  16. client *ovh.Client
  17. region string
  18. configured bool
  19. }
  20. // NewOVHProvider creates a new OVH provider
  21. func NewOVHProvider() Provider {
  22. return &OVHProvider{
  23. entry: logrus.WithField("provider", "ovh"),
  24. }
  25. }
  26. func init() {
  27. RegisterProvider("ovh", NewOVHProvider)
  28. }
  29. // Initialize sets up the OVH provider with credentials and configuration
  30. func (p *OVHProvider) Initialize(config map[string]string) error {
  31. // Check required configuration
  32. requiredKeys := []string{"client_id", "client_secret", "endpoint"}
  33. for _, key := range requiredKeys {
  34. if _, ok := config[key]; !ok {
  35. return fmt.Errorf("missing required configuration key: %s", key)
  36. }
  37. }
  38. // Create OVH client
  39. client, err := ovh.NewOAuth2Client(
  40. config["endpoint"],
  41. config["client_id"],
  42. config["client_secret"],
  43. )
  44. if err != nil {
  45. return fmt.Errorf("failed to create OVH client: %w", err)
  46. }
  47. // Test the client by trying to list VPS IDs
  48. var vpsIDs []string
  49. if err = client.Get("/vps", &vpsIDs); err != nil {
  50. return fmt.Errorf("failed to connect to OVH API: %w", err)
  51. }
  52. // Set client before testing VPS connectivity
  53. p.client = client
  54. p.configured = true
  55. // Set region if provided
  56. if region, ok := config["region"]; ok {
  57. p.region = region
  58. }
  59. p.entry.Info("OVH provider initialized")
  60. return nil
  61. }
  62. // ListRegions lists all available OVH regions
  63. func (p *OVHProvider) ListRegions(ctx context.Context) ([]Region, error) {
  64. if !p.configured {
  65. return nil, errors.New("provider not configured")
  66. }
  67. type OVHRegion struct {
  68. AvailabilityZones []string `json:"availabilityZones"`
  69. CardinalPoint string `json:"cardinalPoint"`
  70. CityCode string `json:"cityCode"`
  71. CityLatitude float64 `json:"cityLatitude"`
  72. CityLongitude float64 `json:"cityLongitude"`
  73. CityName string `json:"cityName"`
  74. Code string `json:"code"`
  75. CountryCode string `json:"countryCode"`
  76. CountryName string `json:"countryName"`
  77. GeographyCode string `json:"geographyCode"`
  78. GeographyName string `json:"geographyName"`
  79. Location string `json:"location"`
  80. Name string `json:"name"`
  81. OpeningYear int `json:"openingYear"`
  82. SpecificType string `json:"specificType"`
  83. Type string `json:"type"`
  84. }
  85. // Get the list of regions from OVH
  86. path := "/v2/location"
  87. var regions []Region
  88. var ovhRegions []OVHRegion
  89. err := p.client.Get(path, &ovhRegions)
  90. if err != nil {
  91. return nil, fmt.Errorf("failed to list regions: %w", err)
  92. }
  93. fmt.Printf("OVH regions: %v\n", ovhRegions)
  94. // Convert OVHRegion to Region
  95. for _, ovhRegion := range ovhRegions {
  96. regions = append(regions, Region{
  97. ID: ovhRegion.Name,
  98. Zone: strings.Join(ovhRegion.AvailabilityZones, ","),
  99. // Add other fields as needed
  100. })
  101. }
  102. return regions, nil
  103. }
  104. // ListInstanceSizes lists available VM sizes (flavors) in OVH
  105. func (p *OVHProvider) ListInstanceSizes(ctx context.Context, region string) ([]InstanceSize, error) {
  106. // TODO: Implement this method
  107. return nil, errors.New("not implemented")
  108. }
  109. // ListInstances lists all instances in OVH
  110. func (p *OVHProvider) ListInstances(ctx context.Context) ([]Instance, error) {
  111. if !p.configured {
  112. return nil, errors.New("provider not configured")
  113. }
  114. path := "/vps"
  115. var vpsIDs []string
  116. var vpsList []*models.OVHVPS
  117. err := p.client.Get(path, &vpsIDs)
  118. if err != nil {
  119. return nil, fmt.Errorf("failed to list instances: %w", err)
  120. }
  121. // Get details for each VPS ID
  122. for _, vpsID := range vpsIDs {
  123. path := fmt.Sprintf("/vps/%s", vpsID)
  124. vps := &models.OVHVPS{}
  125. err := p.client.Get(path, vps)
  126. if err != nil {
  127. return nil, fmt.Errorf("failed to get instance %s: %w", vpsID, err)
  128. }
  129. vpsList = append(vpsList, vps)
  130. }
  131. instances := make([]Instance, len(vpsList))
  132. for i, vps := range vpsList {
  133. // convert size
  134. instances[i] = Instance{
  135. ID: vps.Name,
  136. Name: vps.DisplayName,
  137. Region: vps.Zone,
  138. Size: strconv.Itoa(vps.VCore),
  139. Status: vps.State,
  140. }
  141. }
  142. return instances, nil
  143. }
  144. // ResetInstance resets an instance in OVH
  145. func (p *OVHProvider) ResetInstance(ctx context.Context, id string) error {
  146. // TODO: Implement this method
  147. return errors.New("not implemented")
  148. }
  149. // StartInstance starts an instance in OVH
  150. func (p *OVHProvider) StartInstance(ctx context.Context, id string) error {
  151. // TODO: Implement this method
  152. return errors.New("not implemented")
  153. }
  154. // StopInstance stops an instance in OVH
  155. func (p *OVHProvider) StopInstance(ctx context.Context, id string) error {
  156. // TODO: Implement this method
  157. return errors.New("not implemented")
  158. }
  159. // RestartInstance restarts an instance in OVH
  160. func (p *OVHProvider) RestartInstance(ctx context.Context, id string) error {
  161. // TODO: Implement this method
  162. return errors.New("not implemented")
  163. }
  164. // WaitForInstanceStatus waits for an instance to reach a specific status
  165. func (p *OVHProvider) WaitForInstanceStatus(ctx context.Context, id, status string, timeout time.Duration) error {
  166. // TODO: Implement this method
  167. return errors.New("not implemented")
  168. }
  169. // GetFirstFreeInstance retrieves the first available instance
  170. func (p *OVHProvider) GetFirstFreeInstance(ctx context.Context) (*Instance, error) {
  171. // List all instances
  172. instances, err := p.ListInstances(ctx)
  173. if err != nil {
  174. return nil, fmt.Errorf("failed to list instances: %w", err)
  175. }
  176. // Iterate through instances to find the first free one
  177. for _, instance := range instances {
  178. // This will be final optioons using tags
  179. // Check if instance has tags and if the instance is marked as available
  180. // if instance.DisplayName != nil && instance.Tags["byop-state"] == "available" {
  181. // // Get full details of the instance
  182. // vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
  183. // vps := &models.OVHVPS{}
  184. // err := p.client.Get(vpsPath, vps)
  185. // if err != nil {
  186. // // Log the error but continue with next instance
  187. // fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
  188. // continue
  189. // }
  190. // return vps, nil
  191. // }
  192. // Check if display name contains byom.fr or byop.fr
  193. if instance.Name != "" && (strings.Contains(instance.Name, "byom.fr") || strings.Contains(instance.Name, "byop.fr")) {
  194. // Get full details of the instance
  195. vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
  196. vps := &models.OVHVPS{}
  197. err := p.client.Get(vpsPath, vps)
  198. if err != nil {
  199. // Log the error but continue with next instance
  200. fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
  201. continue
  202. }
  203. // Mark this instance as in use
  204. if instance.Tags != nil {
  205. instance.Tags["byop-state"] = "in-use"
  206. }
  207. return &Instance{
  208. ID: vps.Name,
  209. Name: vps.DisplayName,
  210. IPAddress: vps.Name,
  211. Region: vps.Zone,
  212. Size: strconv.Itoa(vps.VCore),
  213. Status: vps.State,
  214. }, nil
  215. }
  216. }
  217. // If no instance with "available" tag is found, just return the first running instance
  218. // if len(instances) > 0 {
  219. // for _, instance := range instances {
  220. // if instance.Status == "Running" {
  221. // vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
  222. // vps := &models.OVHVPS{}
  223. // err := p.client.Get(vpsPath, vps)
  224. // if err != nil {
  225. // // Log the error but continue
  226. // fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
  227. // continue
  228. // }
  229. // // Mark this instance as in use
  230. // if instance.Tags != nil {
  231. // instance.Tags["byop-state"] = "in-use"
  232. // }
  233. // return vps, nil
  234. // }
  235. // }
  236. // }
  237. // If no instances are found or none are available, return an error
  238. return nil, fmt.Errorf("no free instances found in OVH infrastructure")
  239. }