artifact.go 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. /*
  2. Copyright 2026 LocoStack.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package reconciler
  14. import (
  15. "context"
  16. "fmt"
  17. "github.com/LocoStack/loco-operator/api/v1alpha1"
  18. "github.com/LocoStack/loco-operator/pkg/templates/downloader"
  19. corev1 "k8s.io/api/core/v1"
  20. "k8s.io/apimachinery/pkg/api/resource"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/runtime"
  23. "sigs.k8s.io/controller-runtime/pkg/client"
  24. "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
  25. )
  26. type ArtifactReconciler struct {
  27. *JobReconciler
  28. client client.Client
  29. scheme *runtime.Scheme
  30. stack *v1alpha1.Stack
  31. owner metav1.Object
  32. artifactsSpec *v1alpha1.ArtifactSpec
  33. }
  34. func NewArtifactReconciler(client client.Client, scheme *runtime.Scheme, stack *v1alpha1.Stack, component string, owner metav1.Object, artifactsSpec *v1alpha1.ArtifactSpec) *ArtifactReconciler {
  35. return &ArtifactReconciler{
  36. JobReconciler: NewJobReconciler(
  37. client, scheme, stack, "Artifact", owner.GetName(), owner,
  38. ),
  39. client: client,
  40. scheme: scheme,
  41. stack: stack,
  42. owner: owner,
  43. artifactsSpec: artifactsSpec,
  44. }
  45. }
  46. func (r *ArtifactReconciler) ReconcileArtifact(ctx context.Context) (*corev1.PersistentVolumeClaim, bool, bool, error) {
  47. var pvcName string
  48. if r.artifactsSpec.PVC != nil {
  49. pvcName = r.artifactsSpec.PVC.ClaimName
  50. } else {
  51. pvcName = fmt.Sprintf("%s-artifacts", r.owner.GetName())
  52. }
  53. pvc, err := r.reconcilePVC(ctx, pvcName)
  54. if err != nil {
  55. return nil, false, true, err
  56. }
  57. if r.artifactsSpec.HuggingFace != nil || r.artifactsSpec.URL != nil {
  58. variables := map[string]string{
  59. "spec.pvcName": pvcName,
  60. }
  61. var tmpl *v1alpha1.Template
  62. if r.artifactsSpec.HuggingFace != nil {
  63. hf := r.artifactsSpec.HuggingFace
  64. tmpl = downloader.HFDownloaderTemplate.DeepCopy()
  65. variables["spec.hfRepo"] = hf.Repo
  66. variables["spec.hfFilename"] = hf.FileName
  67. if hf.Revision == "" {
  68. variables["spec.hfRevision"] = "main"
  69. } else {
  70. variables["spec.hfRevision"] = hf.Revision
  71. }
  72. if hf.Endpoint != "" {
  73. variables["spec.hfEndpoint"] = hf.Endpoint
  74. } else {
  75. variables["spec.hfEndpoint"] = "https://huggingface.co"
  76. }
  77. if hf.TokenSecretRef != nil {
  78. variables["spec.hfToken.name"] = hf.TokenSecretRef.Name
  79. variables["spec.hfToken.key"] = hf.TokenSecretRef.Key
  80. } else {
  81. variables["spec.hfToken.name"] = "not-set"
  82. variables["spec.hfToken.key"] = "not-set"
  83. }
  84. } else if r.artifactsSpec.URL != nil {
  85. tmpl = downloader.URLDownloaderTemplate.DeepCopy()
  86. variables["spec.download"] = r.artifactsSpec.URL.URL
  87. variables["spec.fileName"] = r.artifactsSpec.URL.FileName
  88. }
  89. completed, failed, err := r.JobReconciler.ReconcileJob(ctx, tmpl, variables)
  90. if err != nil {
  91. return nil, false, true, err
  92. }
  93. return pvc, completed, failed, nil
  94. }
  95. return pvc, true, false, nil
  96. }
  97. func (r *ArtifactReconciler) reconcilePVC(ctx context.Context, pvcName string) (*corev1.PersistentVolumeClaim, error) {
  98. pvc := &corev1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{
  99. Name: pvcName,
  100. Namespace: r.owner.GetNamespace(),
  101. }}
  102. pvcNotFound := false
  103. if err := r.client.Get(ctx, client.ObjectKeyFromObject(pvc), pvc); err != nil {
  104. if client.IgnoreNotFound(err) != nil {
  105. return nil, fmt.Errorf("Failed to get PVC: %w", err)
  106. }
  107. pvcNotFound = true
  108. }
  109. if pvcNotFound {
  110. _, err := controllerutil.CreateOrUpdate(ctx, r.client, pvc, func() error {
  111. if pvc.CreationTimestamp.IsZero() {
  112. pvc.Labels = ResourceLabels(r.stack.Name, "Artifact", pvcName, "")
  113. pvc.Spec = corev1.PersistentVolumeClaimSpec{
  114. AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce},
  115. Resources: corev1.VolumeResourceRequirements{
  116. Requests: corev1.ResourceList{
  117. corev1.ResourceStorage: resource.MustParse("30Gi"),
  118. },
  119. },
  120. }
  121. }
  122. return controllerutil.SetControllerReference(r.owner, pvc, r.scheme)
  123. })
  124. if err != nil {
  125. return nil, fmt.Errorf("Failed to create or update PVC: %w", err)
  126. }
  127. }
  128. return pvc, nil
  129. }