eso_flux_helm.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. /*
  2. Copyright © 2025 ESO Maintainer Team
  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. https://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 addon
  14. import (
  15. "bytes"
  16. "context"
  17. "crypto/tls"
  18. "net/http"
  19. "time"
  20. fluxhelm "github.com/fluxcd/helm-controller/api/v2beta1"
  21. "github.com/fluxcd/pkg/apis/meta"
  22. fluxsrc "github.com/fluxcd/source-controller/api/v1beta2"
  23. . "github.com/onsi/ginkgo/v2"
  24. . "github.com/onsi/gomega"
  25. v1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  26. apierrors "k8s.io/apimachinery/pkg/api/errors"
  27. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  28. "k8s.io/apimachinery/pkg/types"
  29. "k8s.io/apimachinery/pkg/util/wait"
  30. )
  31. const fluxNamespace = "flux-system"
  32. // HelmChart installs the specified Chart into the cluster.
  33. type FluxHelmRelease struct {
  34. Name string
  35. Namespace string
  36. TargetNamespace string
  37. HelmChart string
  38. HelmRepo string
  39. HelmRevision string
  40. HelmValues string
  41. config *Config
  42. }
  43. // Setup stores the config in an internal field
  44. // to get access to the k8s api in orderto fetch logs.
  45. func (c *FluxHelmRelease) Setup(cfg *Config) error {
  46. c.config = cfg
  47. return nil
  48. }
  49. // Install adds the chart repo and installs the helm chart.
  50. func (c *FluxHelmRelease) Install() error {
  51. app := &fluxsrc.HelmRepository{
  52. ObjectMeta: metav1.ObjectMeta{
  53. Name: c.Name,
  54. Namespace: fluxNamespace,
  55. },
  56. Spec: fluxsrc.HelmRepositorySpec{
  57. URL: c.HelmRepo,
  58. },
  59. }
  60. err := c.config.CRClient.Create(GinkgoT().Context(), app)
  61. if err != nil {
  62. return err
  63. }
  64. hr := &fluxhelm.HelmRelease{
  65. ObjectMeta: metav1.ObjectMeta{
  66. Name: c.Name,
  67. Namespace: c.Namespace,
  68. },
  69. Spec: fluxhelm.HelmReleaseSpec{
  70. ReleaseName: c.Name,
  71. TargetNamespace: c.TargetNamespace,
  72. Values: &v1.JSON{
  73. Raw: []byte(c.HelmValues),
  74. },
  75. Install: &fluxhelm.Install{
  76. CreateNamespace: true,
  77. Remediation: &fluxhelm.InstallRemediation{
  78. Retries: -1,
  79. },
  80. },
  81. Chart: fluxhelm.HelmChartTemplate{
  82. Spec: fluxhelm.HelmChartTemplateSpec{
  83. Version: c.HelmRevision,
  84. Chart: c.HelmChart,
  85. SourceRef: fluxhelm.CrossNamespaceObjectReference{
  86. Kind: "HelmRepository",
  87. Name: c.Name,
  88. Namespace: fluxNamespace,
  89. },
  90. },
  91. },
  92. },
  93. }
  94. err = c.config.CRClient.Create(GinkgoT().Context(), hr)
  95. if err != nil {
  96. return err
  97. }
  98. // wait for app to become ready
  99. err = wait.PollUntilContextTimeout(GinkgoT().Context(), time.Second*5, time.Minute*3, true, func(ctx context.Context) (bool, error) {
  100. var hr fluxhelm.HelmRelease
  101. err := c.config.CRClient.Get(GinkgoT().Context(), types.NamespacedName{
  102. Name: c.Name,
  103. Namespace: c.Namespace,
  104. }, &hr)
  105. if err != nil {
  106. return false, nil
  107. }
  108. for _, cond := range hr.GetConditions() {
  109. GinkgoWriter.Printf("check condition: %s=%s: %s\n", cond.Type, cond.Status, cond.Message)
  110. if cond.Type == meta.ReadyCondition && cond.Status == metav1.ConditionTrue {
  111. return true, nil
  112. }
  113. }
  114. return false, nil
  115. })
  116. if err != nil {
  117. return err
  118. }
  119. // we have to wait for the webhook to become ready
  120. tr := &http.Transport{
  121. // nolint:gosec
  122. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  123. }
  124. client := &http.Client{Transport: tr}
  125. return wait.PollUntilContextTimeout(GinkgoT().Context(), time.Second, time.Minute*5, true, func(ctx context.Context) (bool, error) {
  126. const payload = `{"apiVersion": "admission.k8s.io/v1","kind": "AdmissionReview","request": {"uid": "test","kind": {"group": "external-secrets.io","version": "v1","kind": "ExternalSecret"}, "resource": "external-secrets.io/v1.externalsecrets","dryRun": true, "operation": "CREATE", "userInfo":{"username":"test","uid":"test","groups":[],"extra":{}}}}`
  127. res, err := client.Post("https://external-secrets-webhook.external-secrets.svc.cluster.local/validate-external-secrets-io-v1-externalsecret", "application/json", bytes.NewBufferString(payload))
  128. if err != nil {
  129. return false, nil
  130. }
  131. defer func() {
  132. _ = res.Body.Close()
  133. }()
  134. GinkgoWriter.Printf("webhook res: %d", res.StatusCode)
  135. return res.StatusCode == http.StatusOK, nil
  136. })
  137. }
  138. // Uninstall removes the chart aswell as the repo.
  139. func (c *FluxHelmRelease) Uninstall() error {
  140. err := uninstallCRDs(c.config)
  141. if err != nil {
  142. return err
  143. }
  144. err = c.config.CRClient.Delete(GinkgoT().Context(), &fluxhelm.HelmRelease{
  145. ObjectMeta: metav1.ObjectMeta{
  146. Name: c.Name,
  147. Namespace: c.Namespace,
  148. },
  149. })
  150. if err != nil && !apierrors.IsNotFound(err) {
  151. return err
  152. }
  153. Eventually(func() bool {
  154. var hr fluxhelm.HelmRelease
  155. err = c.config.CRClient.Get(GinkgoT().Context(), types.NamespacedName{
  156. Name: c.Name,
  157. Namespace: c.Namespace,
  158. }, &hr)
  159. if apierrors.IsNotFound(err) {
  160. return true
  161. }
  162. return false
  163. }).WithPolling(time.Second).WithTimeout(time.Second * 30).Should(BeTrue())
  164. if err := c.config.CRClient.Delete(GinkgoT().Context(), &fluxsrc.HelmRepository{
  165. ObjectMeta: metav1.ObjectMeta{
  166. Name: c.Name,
  167. Namespace: fluxNamespace,
  168. },
  169. }); err != nil && !apierrors.IsNotFound(err) {
  170. return err
  171. }
  172. return nil
  173. }
  174. func (c *FluxHelmRelease) Logs() error {
  175. return nil
  176. }