grafana.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  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 grafana
  14. import (
  15. "context"
  16. "encoding/json"
  17. "fmt"
  18. "net/url"
  19. "strings"
  20. "github.com/google/uuid"
  21. grafanaclient "github.com/grafana/grafana-openapi-client-go/client"
  22. grafanasa "github.com/grafana/grafana-openapi-client-go/client/service_accounts"
  23. "github.com/grafana/grafana-openapi-client-go/models"
  24. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  25. "k8s.io/utils/ptr"
  26. "sigs.k8s.io/controller-runtime/pkg/client"
  27. genv1alpha1 "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  28. esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
  29. "github.com/external-secrets/external-secrets/pkg/utils/resolvers"
  30. )
  31. type Grafana struct{}
  32. func (w *Grafana) Generate(ctx context.Context, jsonSpec *apiextensions.JSON, kclient client.Client, ns string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  33. gen, err := parseSpec(jsonSpec.Raw)
  34. if err != nil {
  35. return nil, nil, err
  36. }
  37. cl, err := newClient(ctx, gen, kclient, ns)
  38. if err != nil {
  39. return nil, nil, err
  40. }
  41. state, err := createOrGetServiceAccount(cl, gen)
  42. if err != nil {
  43. return nil, nil, err
  44. }
  45. // create new token
  46. res, err := cl.ServiceAccounts.CreateToken(&grafanasa.CreateTokenParams{
  47. ServiceAccountID: *state.ServiceAccount.ServiceAccountID,
  48. Body: &models.AddServiceAccountTokenCommand{
  49. Name: uuid.New().String(),
  50. },
  51. }, nil)
  52. if err != nil {
  53. return nil, nil, err
  54. }
  55. state.ServiceAccount.ServiceAccountTokenID = ptr.To(res.Payload.ID)
  56. return tokenResponse(state, res.Payload.Key)
  57. }
  58. func (w *Grafana) Cleanup(ctx context.Context, jsonSpec *apiextensions.JSON, previousStatus genv1alpha1.GeneratorProviderState, kclient client.Client, ns string) error {
  59. if previousStatus == nil {
  60. return fmt.Errorf("missing previous status")
  61. }
  62. status, err := parseStatus(previousStatus.Raw)
  63. if err != nil {
  64. return err
  65. }
  66. gen, err := parseSpec(jsonSpec.Raw)
  67. if err != nil {
  68. return err
  69. }
  70. cl, err := newClient(ctx, gen, kclient, ns)
  71. if err != nil {
  72. return err
  73. }
  74. _, err = cl.ServiceAccounts.DeleteToken(*status.ServiceAccount.ServiceAccountTokenID, *status.ServiceAccount.ServiceAccountID)
  75. if err != nil && !strings.Contains(err.Error(), "service account token not found") {
  76. return err
  77. }
  78. return nil
  79. }
  80. func newClient(ctx context.Context, gen *genv1alpha1.Grafana, kclient client.Client, ns string) (*grafanaclient.GrafanaHTTPAPI, error) {
  81. parsedURL, err := url.Parse(gen.Spec.URL)
  82. if err != nil {
  83. return nil, err
  84. }
  85. cfg := &grafanaclient.TransportConfig{
  86. Host: parsedURL.Host,
  87. BasePath: parsedURL.JoinPath("/api").Path,
  88. Schemes: []string{parsedURL.Scheme},
  89. }
  90. if err := setGrafanaClientCredentials(ctx, gen, kclient, ns, cfg); err != nil {
  91. return nil, err
  92. }
  93. return grafanaclient.NewHTTPClientWithConfig(nil, cfg), nil
  94. }
  95. func setGrafanaClientCredentials(ctx context.Context, gen *genv1alpha1.Grafana, kclient client.Client, ns string, cfg *grafanaclient.TransportConfig) error {
  96. // First try to use service account auth
  97. if gen.Spec.Auth.Token != nil {
  98. serviceAccountAPIKey, err := resolvers.SecretKeyRef(ctx, kclient, resolvers.EmptyStoreKind, ns, &esmeta.SecretKeySelector{
  99. Namespace: &ns,
  100. Name: gen.Spec.Auth.Token.Name,
  101. Key: gen.Spec.Auth.Token.Key,
  102. })
  103. if err != nil {
  104. return err
  105. }
  106. cfg.APIKey = serviceAccountAPIKey
  107. return nil
  108. }
  109. // Next try to use basic auth
  110. if gen.Spec.Auth.Basic != nil {
  111. basicAuthPassword, err := resolvers.SecretKeyRef(ctx, kclient, resolvers.EmptyStoreKind, ns, &esmeta.SecretKeySelector{
  112. Namespace: &ns,
  113. Name: gen.Spec.Auth.Basic.Password.Name,
  114. Key: gen.Spec.Auth.Basic.Password.Key,
  115. })
  116. if err != nil {
  117. return err
  118. }
  119. cfg.BasicAuth = url.UserPassword(gen.Spec.Auth.Basic.Username, basicAuthPassword)
  120. return nil
  121. }
  122. // No auth found, fail
  123. return fmt.Errorf("no auth configuration found")
  124. }
  125. func createOrGetServiceAccount(cl *grafanaclient.GrafanaHTTPAPI, gen *genv1alpha1.Grafana) (*genv1alpha1.GrafanaServiceAccountTokenState, error) {
  126. saList, err := cl.ServiceAccounts.SearchOrgServiceAccountsWithPaging(&grafanasa.SearchOrgServiceAccountsWithPagingParams{
  127. Query: ptr.To(gen.Spec.ServiceAccount.Name),
  128. })
  129. if err != nil {
  130. return nil, err
  131. }
  132. for _, sa := range saList.Payload.ServiceAccounts {
  133. if sa.Name == gen.Spec.ServiceAccount.Name {
  134. return &genv1alpha1.GrafanaServiceAccountTokenState{
  135. ServiceAccount: genv1alpha1.GrafanaStateServiceAccount{
  136. ServiceAccountID: &sa.ID,
  137. ServiceAccountLogin: &sa.Login,
  138. },
  139. }, nil
  140. }
  141. }
  142. res, err := cl.ServiceAccounts.CreateServiceAccount(&grafanasa.CreateServiceAccountParams{
  143. Body: &models.CreateServiceAccountForm{
  144. Name: gen.Spec.ServiceAccount.Name,
  145. Role: gen.Spec.ServiceAccount.Role,
  146. },
  147. }, nil)
  148. if err != nil {
  149. return nil, err
  150. }
  151. return &genv1alpha1.GrafanaServiceAccountTokenState{
  152. ServiceAccount: genv1alpha1.GrafanaStateServiceAccount{
  153. ServiceAccountID: ptr.To(res.Payload.ID),
  154. ServiceAccountLogin: &res.Payload.Login,
  155. },
  156. }, nil
  157. }
  158. func tokenResponse(state *genv1alpha1.GrafanaServiceAccountTokenState, token string) (map[string][]byte, genv1alpha1.GeneratorProviderState, error) {
  159. newStateJSON, err := json.Marshal(state)
  160. if err != nil {
  161. return nil, nil, err
  162. }
  163. return map[string][]byte{
  164. "login": []byte(*state.ServiceAccount.ServiceAccountLogin),
  165. "token": []byte(token),
  166. }, &apiextensions.JSON{Raw: newStateJSON}, nil
  167. }
  168. func parseSpec(data []byte) (*genv1alpha1.Grafana, error) {
  169. var spec genv1alpha1.Grafana
  170. err := json.Unmarshal(data, &spec)
  171. return &spec, err
  172. }
  173. func parseStatus(data []byte) (*genv1alpha1.GrafanaServiceAccountTokenState, error) {
  174. var state genv1alpha1.GrafanaServiceAccountTokenState
  175. err := json.Unmarshal(data, &state)
  176. if err != nil {
  177. return nil, err
  178. }
  179. return &state, err
  180. }
  181. func init() {
  182. genv1alpha1.Register(genv1alpha1.GrafanaKind, &Grafana{})
  183. }