eso_argocd_application.go 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /*
  2. Copyright © The ESO Authors
  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. "fmt"
  19. "net/http"
  20. "strings"
  21. "time"
  22. . "github.com/onsi/ginkgo/v2"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
  25. "k8s.io/apimachinery/pkg/runtime/schema"
  26. "k8s.io/apimachinery/pkg/util/wait"
  27. "k8s.io/client-go/dynamic"
  28. "sigs.k8s.io/yaml"
  29. )
  30. // HelmChart installs the specified Chart into the cluster.
  31. type ArgoCDApplication struct {
  32. Name string
  33. Namespace string
  34. DestinationNamespace string
  35. HelmChart string
  36. HelmRepo string
  37. HelmRevision string
  38. HelmParameters []string
  39. config *Config
  40. dc *dynamic.DynamicClient
  41. }
  42. var argoApp = schema.GroupVersionResource{
  43. Group: "argoproj.io",
  44. Version: "v1alpha1",
  45. Resource: "applications",
  46. }
  47. var argoAppResource = `apiVersion: argoproj.io/v1alpha1
  48. kind: Application
  49. metadata:
  50. name: %s
  51. namespace: %s
  52. annotations:
  53. argocd.argoproj.io/refresh: "hard"
  54. spec:
  55. project: default
  56. syncPolicy:
  57. automated:
  58. prune: true
  59. selfHeal: true
  60. syncOptions:
  61. - CreateNamespace=true
  62. - ServerSideApply=true
  63. source:
  64. chart: %s
  65. repoURL: %s
  66. targetRevision: %s
  67. helm:
  68. releaseName: %s
  69. parameters: %s
  70. destination:
  71. server: https://kubernetes.default.svc
  72. namespace: %s
  73. `
  74. const (
  75. // taken from: https://github.com/argoproj/argo-cd/blob/0a8a71e12c5010d5ada1fce37feb0d8add1c61d0/pkg/apis/application/v1alpha1/types.go#LL1351C41-L1351C47
  76. StatusSynced = "Synced"
  77. )
  78. // Setup stores the config in an internal field
  79. // to get access to the k8s api in orderto fetch logs.
  80. func (c *ArgoCDApplication) Setup(cfg *Config) error {
  81. c.config = cfg
  82. dc, err := dynamic.NewForConfig(cfg.KubeConfig)
  83. if err != nil {
  84. return err
  85. }
  86. c.dc = dc
  87. return nil
  88. }
  89. // Install adds the chart repo and installs the helm chart.
  90. func (c *ArgoCDApplication) Install() error {
  91. // construct helm parameters
  92. var helmParams string
  93. for _, param := range c.HelmParameters {
  94. args := strings.Split(param, "=")
  95. helmParams += fmt.Sprintf(`
  96. - name: "%s"
  97. value: %s`, args[0], args[1])
  98. }
  99. jsonBytes, err := yaml.YAMLToJSON([]byte(fmt.Sprintf(argoAppResource, c.Name, c.Namespace, c.HelmChart, c.HelmRepo, c.HelmRevision, c.Name, helmParams, c.DestinationNamespace)))
  100. if err != nil {
  101. return fmt.Errorf("unable to decode argo app yaml to json: %w", err)
  102. }
  103. us := &unstructured.Unstructured{}
  104. err = us.UnmarshalJSON(jsonBytes)
  105. if err != nil {
  106. return fmt.Errorf("unable to unmarshal json into unstructured: %w", err)
  107. }
  108. _, err = c.dc.Resource(argoApp).Namespace(c.Namespace).Create(GinkgoT().Context(), us, metav1.CreateOptions{})
  109. if err != nil {
  110. return fmt.Errorf("unable to create argo app: %w", err)
  111. }
  112. // wait for app to become ready
  113. err = wait.PollUntilContextTimeout(GinkgoT().Context(), time.Second*5, time.Minute*10, true, func(ctx context.Context) (bool, error) {
  114. us, err = c.dc.Resource(argoApp).Namespace(c.Namespace).Get(ctx, c.Name, metav1.GetOptions{})
  115. if err != nil {
  116. return false, err
  117. }
  118. syncStatus, _, err := unstructured.NestedString(us.Object, "status", "sync", "status")
  119. if err != nil {
  120. return false, nil
  121. }
  122. return syncStatus == StatusSynced, nil
  123. })
  124. if err != nil {
  125. return fmt.Errorf("failed waiting for argo app to become ready: %w", err)
  126. }
  127. // we have to wait for the webhook to become ready
  128. tr := &http.Transport{
  129. // nolint:gosec
  130. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  131. }
  132. client := &http.Client{Transport: tr}
  133. return wait.PollUntilContextTimeout(GinkgoT().Context(), time.Second, time.Minute*5, true, func(ctx context.Context) (bool, error) {
  134. const payload = `{"apiVersion": "admission.k8s.io/v1","kind": "AdmissionReview","request": {"uid": "test","kind": {"group": "external-secrets.io","version": "v1","kind": "ExternalSecret"}, "resource": {"group": "external-secrets.io","version": "v1","kind": "ExternalSecret"},"dryRun": true, "operation": "CREATE", "userInfo":{"username":"test","uid":"test","groups":[],"extra":{}}}}`
  135. res, err := client.Post("https://external-secrets-webhook.external-secrets.svc.cluster.local/validate-external-secrets-io-v1-externalsecret", "application/json", bytes.NewBufferString(payload))
  136. if err != nil {
  137. return false, nil
  138. }
  139. defer func() {
  140. _ = res.Body.Close()
  141. }()
  142. GinkgoWriter.Printf("webhook res: %d", res.StatusCode)
  143. return res.StatusCode == http.StatusOK, nil
  144. })
  145. }
  146. // Uninstall removes the chart aswell as the repo.
  147. func (c *ArgoCDApplication) Uninstall() error {
  148. err := c.dc.Resource(argoApp).Namespace(c.Namespace).Delete(GinkgoT().Context(), c.Name, metav1.DeleteOptions{})
  149. if err != nil {
  150. return err
  151. }
  152. err = uninstallCRDs(c.config)
  153. if err != nil {
  154. return err
  155. }
  156. return nil
  157. }
  158. func (c *ArgoCDApplication) Logs() error {
  159. return nil
  160. }