ovh.go 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  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. IPAddress: vps.Name, // Assuming Name is the IP address
  141. }
  142. }
  143. return instances, nil
  144. }
  145. // ResetInstance resets an instance in OVH
  146. func (p *OVHProvider) ResetInstance(ctx context.Context, id string) error {
  147. // TODO: Implement this method
  148. return errors.New("not implemented")
  149. }
  150. // StartInstance starts an instance in OVH
  151. func (p *OVHProvider) StartInstance(ctx context.Context, id string) error {
  152. // TODO: Implement this method
  153. return errors.New("not implemented")
  154. }
  155. // StopInstance stops an instance in OVH
  156. func (p *OVHProvider) StopInstance(ctx context.Context, id string) error {
  157. // TODO: Implement this method
  158. return errors.New("not implemented")
  159. }
  160. // RestartInstance restarts an instance in OVH
  161. func (p *OVHProvider) RestartInstance(ctx context.Context, id string) error {
  162. // TODO: Implement this method
  163. return errors.New("not implemented")
  164. }
  165. // WaitForInstanceStatus waits for an instance to reach a specific status
  166. func (p *OVHProvider) WaitForInstanceStatus(ctx context.Context, id, status string, timeout time.Duration) error {
  167. // TODO: Implement this method
  168. return errors.New("not implemented")
  169. }
  170. // GetFirstFreeInstance retrieves the first available instance
  171. func (p *OVHProvider) GetFirstFreeInstance(ctx context.Context) (*Instance, error) {
  172. // List all instances
  173. instances, err := p.ListInstances(ctx)
  174. if err != nil {
  175. return nil, fmt.Errorf("failed to list instances: %w", err)
  176. }
  177. // Iterate through instances to find the first free one
  178. for _, instance := range instances {
  179. // This will be final optioons using tags
  180. // Check if instance has tags and if the instance is marked as available
  181. // if instance.DisplayName != nil && instance.Tags["byop-state"] == "available" {
  182. // // Get full details of the instance
  183. // vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
  184. // vps := &models.OVHVPS{}
  185. // err := p.client.Get(vpsPath, vps)
  186. // if err != nil {
  187. // // Log the error but continue with next instance
  188. // fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
  189. // continue
  190. // }
  191. // return vps, nil
  192. // }
  193. // Check if display name contains byom.fr or byop.fr
  194. if instance.Name != "" && (strings.Contains(instance.Name, "byom.fr") || strings.Contains(instance.Name, "byop.fr")) {
  195. // Get full details of the instance
  196. vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
  197. vps := &models.OVHVPS{}
  198. err := p.client.Get(vpsPath, vps)
  199. if err != nil {
  200. // Log the error but continue with next instance
  201. fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
  202. continue
  203. }
  204. // Mark this instance as in use
  205. if instance.Tags != nil {
  206. instance.Tags["byop-state"] = "in-use"
  207. }
  208. return &Instance{
  209. ID: vps.Name,
  210. Name: vps.DisplayName,
  211. IPAddress: vps.Name,
  212. Region: vps.Zone,
  213. Size: strconv.Itoa(vps.VCore),
  214. Status: vps.State,
  215. }, nil
  216. }
  217. }
  218. // If no instance with "available" tag is found, just return the first running instance
  219. // if len(instances) > 0 {
  220. // for _, instance := range instances {
  221. // if instance.Status == "Running" {
  222. // vpsPath := fmt.Sprintf("/vps/%s", instance.ID)
  223. // vps := &models.OVHVPS{}
  224. // err := p.client.Get(vpsPath, vps)
  225. // if err != nil {
  226. // // Log the error but continue
  227. // fmt.Printf("Error fetching details for VPS %s: %v\n", instance.ID, err)
  228. // continue
  229. // }
  230. // // Mark this instance as in use
  231. // if instance.Tags != nil {
  232. // instance.Tags["byop-state"] = "in-use"
  233. // }
  234. // return vps, nil
  235. // }
  236. // }
  237. // }
  238. // If no instances are found or none are available, return an error
  239. return nil, fmt.Errorf("no free instances found in OVH infrastructure")
  240. }
  241. // GetPreviewInstance retrieves the preview instance
  242. // preview instance has a specific display name format
  243. // displayname format: "preview.byop.fr"
  244. func (p *OVHProvider) GetPreviewInstance(ctx context.Context) (*Instance, error) {
  245. if !p.configured {
  246. return nil, errors.New("provider not configured")
  247. }
  248. // List all instances
  249. instances, err := p.ListInstances(ctx)
  250. if err != nil {
  251. return nil, fmt.Errorf("failed to list instances: %w", err)
  252. }
  253. // Iterate through instances to find the first preview instance
  254. for _, instance := range instances {
  255. // Check if display name matches the preview format
  256. if strings.Contains(instance.Name, "preview.byop.fr") {
  257. fmt.Printf("Found preview instance: %s\n", instance.Name)
  258. // print IP
  259. fmt.Printf("Preview instance IP: %s\n", instance.IPAddress)
  260. return &instance, nil
  261. }
  262. }
  263. return nil, fmt.Errorf("no preview instance found in OVH infrastructure")
  264. }