eso_argocd_application.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. /*
  2. Copyright 2020 The cert-manager 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. 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 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. source:
  63. chart: %s
  64. repoURL: %s
  65. targetRevision: %s
  66. helm:
  67. releaseName: %s
  68. parameters: %s
  69. destination:
  70. server: https://kubernetes.default.svc
  71. namespace: %s
  72. `
  73. const (
  74. // taken from: https://github.com/argoproj/argo-cd/blob/0a8a71e12c5010d5ada1fce37feb0d8add1c61d0/pkg/apis/application/v1alpha1/types.go#LL1351C41-L1351C47
  75. StatusSynced = "Synced"
  76. )
  77. // Setup stores the config in an internal field
  78. // to get access to the k8s api in orderto fetch logs.
  79. func (c *ArgoCDApplication) Setup(cfg *Config) error {
  80. c.config = cfg
  81. dc, err := dynamic.NewForConfig(cfg.KubeConfig)
  82. if err != nil {
  83. return err
  84. }
  85. c.dc = dc
  86. return nil
  87. }
  88. // Install adds the chart repo and installs the helm chart.
  89. func (c *ArgoCDApplication) Install() error {
  90. // construct helm parameters
  91. var helmParams string
  92. for _, param := range c.HelmParameters {
  93. args := strings.Split(param, "=")
  94. helmParams += fmt.Sprintf(`
  95. - name: "%s"
  96. value: %s`, args[0], args[1])
  97. }
  98. jsonBytes, err := yaml.YAMLToJSON([]byte(fmt.Sprintf(argoAppResource, c.Name, c.Namespace, c.HelmChart, c.HelmRepo, c.HelmRevision, c.Name, helmParams, c.DestinationNamespace)))
  99. if err != nil {
  100. return fmt.Errorf("unable to decode argo app yaml to json: %w", err)
  101. }
  102. us := &unstructured.Unstructured{}
  103. err = us.UnmarshalJSON(jsonBytes)
  104. if err != nil {
  105. return fmt.Errorf("unable to unmarshal json into unstructured: %w", err)
  106. }
  107. _, err = c.dc.Resource(argoApp).Namespace(c.Namespace).Create(context.Background(), us, metav1.CreateOptions{})
  108. if err != nil {
  109. return fmt.Errorf("unable to create argo app: %w", err)
  110. }
  111. // wait for app to become ready
  112. err = wait.PollImmediate(time.Second*5, time.Minute*10, func() (bool, error) {
  113. us, err = c.dc.Resource(argoApp).Namespace(c.Namespace).Get(context.Background(), c.Name, metav1.GetOptions{})
  114. if err != nil {
  115. return false, err
  116. }
  117. syncStatus, _, err := unstructured.NestedString(us.Object, "status", "sync", "status")
  118. if err != nil {
  119. return false, nil
  120. }
  121. return syncStatus == StatusSynced, nil
  122. })
  123. if err != nil {
  124. return fmt.Errorf("failed waiting for argo app to become ready: %w", err)
  125. }
  126. // we have to wait for the webhook to become ready
  127. tr := &http.Transport{
  128. // nolint:gosec
  129. TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
  130. }
  131. client := &http.Client{Transport: tr}
  132. return wait.PollImmediate(time.Second, time.Minute*5, func() (bool, error) {
  133. const payload = `{"apiVersion": "apiextensions.k8s.io/v1","kind": "ConversionReview","request": {}}`
  134. res, err := client.Post("https://external-secrets-webhook.external-secrets.svc.cluster.local/convert", "application/json", bytes.NewBufferString(payload))
  135. if err != nil {
  136. return false, nil
  137. }
  138. defer res.Body.Close()
  139. ginkgo.GinkgoWriter.Printf("conversion res: %d", res.StatusCode)
  140. return res.StatusCode == http.StatusOK, nil
  141. })
  142. }
  143. // Uninstall removes the chart aswell as the repo.
  144. func (c *ArgoCDApplication) Uninstall() error {
  145. err := c.dc.Resource(argoApp).Namespace(c.Namespace).Delete(context.Background(), c.Name, metav1.DeleteOptions{})
  146. if err != nil {
  147. return err
  148. }
  149. err = uninstallCRDs(c.config)
  150. if err != nil {
  151. return err
  152. }
  153. return nil
  154. }
  155. func (c *ArgoCDApplication) Logs() error {
  156. return nil
  157. }