statemanager.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. /*
  2. Licensed under the Apache License, Version 2.0 (the "License");
  3. you may not use this file except in compliance with the License.
  4. You may obtain a copy of the License at
  5. http://www.apache.org/licenses/LICENSE-2.0
  6. Unless required by applicable law or agreed to in writing, software
  7. distributed under the License is distributed on an "AS IS" BASIS,
  8. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  9. See the License for the specific language governing permissions and
  10. limitations under the License.
  11. */
  12. package statemanager
  13. import (
  14. "context"
  15. "errors"
  16. "fmt"
  17. "strings"
  18. "time"
  19. "github.com/spf13/pflag"
  20. apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
  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. genapi "github.com/external-secrets/external-secrets/apis/generators/v1alpha1"
  26. "github.com/external-secrets/external-secrets/pkg/feature"
  27. "github.com/external-secrets/external-secrets/pkg/utils"
  28. )
  29. // Manager takes care of maintaining the state of the generators.
  30. // It provides the ability to commit and rollback the state of the generators,
  31. // which is needed when we have multiple generators that need to be created or
  32. // other operations which can fail.
  33. type Manager struct {
  34. ctx context.Context
  35. scheme *runtime.Scheme
  36. client client.Client
  37. namespace string
  38. resource genapi.StatefulResource
  39. queue []QueueItem
  40. }
  41. type QueueItem struct {
  42. Rollback func() error
  43. Commit func() error
  44. }
  45. var gcGracePeriod time.Duration
  46. func init() {
  47. fs := pflag.NewFlagSet("gc", pflag.ExitOnError)
  48. fs.DurationVar(&gcGracePeriod, "generator-gc-grace-period", time.Minute*2, "Duration after which generated secrets are cleaned up after they have been flagged for gc.")
  49. feature.Register(feature.Feature{
  50. Flags: fs,
  51. })
  52. }
  53. func New(ctx context.Context, client client.Client, scheme *runtime.Scheme, namespace string,
  54. resource genapi.StatefulResource) *Manager {
  55. return &Manager{
  56. ctx: ctx,
  57. scheme: scheme,
  58. client: client,
  59. namespace: namespace,
  60. resource: resource,
  61. }
  62. }
  63. // Rollback will rollback the enqueued operations.
  64. func (m *Manager) Rollback() error {
  65. var errs []error
  66. for _, item := range m.queue {
  67. if item.Rollback == nil {
  68. continue
  69. }
  70. if err := item.Rollback(); err != nil {
  71. errs = append(errs, err)
  72. }
  73. }
  74. return errors.Join(errs...)
  75. }
  76. // Commit will apply the enqueued changes to the state of the generators.
  77. func (m *Manager) Commit() error {
  78. var errs []error
  79. for _, item := range m.queue {
  80. if item.Commit == nil {
  81. continue
  82. }
  83. if err := item.Commit(); err != nil {
  84. errs = append(errs, err)
  85. }
  86. }
  87. return errors.Join(errs...)
  88. }
  89. // EnqueueFlagLatestStateForGC will flag the latest state for garbage collection after Commit.
  90. // It will be cleaned up later by the garbage collector.
  91. func (m *Manager) EnqueueFlagLatestStateForGC(stateKey string) {
  92. m.queue = append(m.queue, QueueItem{
  93. Commit: func() error {
  94. return m.disposeState(stateKey)
  95. },
  96. })
  97. }
  98. // EnqueueMoveStateToGC will move the generator state to GC if Commit() is called.
  99. func (m *Manager) EnqueueMoveStateToGC(stateKey string) {
  100. m.queue = append(m.queue, QueueItem{
  101. Commit: func() error {
  102. return m.disposeState(stateKey)
  103. },
  104. })
  105. }
  106. // EnqueueSetLatest sets the latest state for the given key.
  107. // It will commit the state on success or move the state to GC on failure.
  108. func (m *Manager) EnqueueSetLatest(ctx context.Context, stateKey, namespace string, resource *apiextensions.JSON, gen genapi.Generator, state genapi.GeneratorProviderState) {
  109. if state == nil {
  110. return
  111. }
  112. m.queue = append(m.queue, QueueItem{
  113. // Stores the state in GeneratorState resource
  114. Commit: func() error {
  115. genState, err := m.createGeneratorState(resource, state, namespace, stateKey)
  116. if err != nil {
  117. return err
  118. }
  119. return m.client.Create(ctx, genState)
  120. },
  121. // Rollback by cleaning up the state.
  122. // In case of failure, create a new GeneratorState, so it will eventually be cleaned up.
  123. // If that also fails we're out of luck :(
  124. Rollback: func() error {
  125. err := gen.Cleanup(ctx, resource, state, m.client, namespace)
  126. if err == nil {
  127. return nil
  128. }
  129. genState, err := m.createGeneratorState(resource, state, namespace, stateKey)
  130. if err != nil {
  131. return err
  132. }
  133. genState.Spec.GarbageCollectionDeadline = &metav1.Time{
  134. Time: time.Now(),
  135. }
  136. return m.client.Create(ctx, genState)
  137. },
  138. })
  139. }
  140. func (m *Manager) createGeneratorState(resource *apiextensions.JSON, state genapi.GeneratorProviderState, namespace, stateKey string) (*genapi.GeneratorState, error) {
  141. genState := &genapi.GeneratorState{
  142. ObjectMeta: metav1.ObjectMeta{
  143. GenerateName: fmt.Sprintf("gen-%s-%s-", strings.ToLower(m.resource.GetObjectKind().GroupVersionKind().Kind), m.resource.GetName()),
  144. Namespace: namespace,
  145. Labels: map[string]string{
  146. genapi.GeneratorStateLabelOwnerKey: ownerKey(
  147. m.resource,
  148. stateKey,
  149. ),
  150. },
  151. },
  152. Spec: genapi.GeneratorStateSpec{
  153. Resource: resource,
  154. State: state,
  155. },
  156. }
  157. if err := controllerutil.SetOwnerReference(m.resource, genState, m.scheme); err != nil {
  158. return nil, err
  159. }
  160. return genState, nil
  161. }
  162. func ownerKey(resource genapi.StatefulResource, key string) string {
  163. return utils.ObjectHash(fmt.Sprintf("%s-%s-%s-%s",
  164. resource.GetObjectKind().GroupVersionKind().Kind,
  165. resource.GetNamespace(),
  166. resource.GetName(),
  167. key),
  168. )
  169. }
  170. func (m *Manager) disposeState(key string) error {
  171. allStates, err := m.GetAllStates(key)
  172. if err != nil {
  173. return err
  174. }
  175. latest := getLatest(allStates)
  176. if latest == nil {
  177. return nil
  178. }
  179. // flag all states for GC except the latest one
  180. // This is to ensure that all "old" states are eventually cleaned up.
  181. // This is needed due to fast reconciles and working with stale cache.
  182. var errs []error
  183. for _, state := range allStates {
  184. if state.Name == latest.Name {
  185. continue
  186. }
  187. if state.Spec.GarbageCollectionDeadline != nil {
  188. continue
  189. }
  190. state.Spec.GarbageCollectionDeadline = &metav1.Time{
  191. Time: time.Now().Add(gcGracePeriod),
  192. }
  193. if err := m.client.Update(m.ctx, &state); err != nil {
  194. errs = append(errs, err)
  195. }
  196. }
  197. return errors.Join(errs...)
  198. }
  199. // GetLatest returns the latest state for the given key.
  200. func (m *Manager) GetAllStates(key string) ([]genapi.GeneratorState, error) {
  201. var stateList genapi.GeneratorStateList
  202. if err := m.client.List(m.ctx, &stateList, &client.MatchingLabels{
  203. genapi.GeneratorStateLabelOwnerKey: ownerKey(
  204. m.resource,
  205. key,
  206. ),
  207. }, client.InNamespace(m.namespace)); err != nil {
  208. return nil, err
  209. }
  210. return stateList.Items, nil
  211. }
  212. // GetLatestState returns the latest state for the given key.
  213. func (m *Manager) GetLatestState(key string) (*genapi.GeneratorState, error) {
  214. var stateList genapi.GeneratorStateList
  215. if err := m.client.List(m.ctx, &stateList, &client.MatchingLabels{
  216. genapi.GeneratorStateLabelOwnerKey: ownerKey(
  217. m.resource,
  218. key,
  219. ),
  220. }, client.InNamespace(m.namespace)); err != nil {
  221. return nil, err
  222. }
  223. if latestState := getLatest(stateList.Items); latestState != nil {
  224. return latestState, nil
  225. }
  226. return nil, nil
  227. }
  228. func getLatest(stateList []genapi.GeneratorState) *genapi.GeneratorState {
  229. var latest *genapi.GeneratorState
  230. for _, state := range stateList {
  231. // if the state is already flagged for GC, skip it
  232. // It can happen that the latest based on creation timestamp is already flagged for GC.
  233. // That is the case when a rollback was performed.
  234. if state.Spec.GarbageCollectionDeadline != nil {
  235. continue
  236. }
  237. if latest == nil {
  238. latest = &state
  239. continue
  240. }
  241. if state.CreationTimestamp.After(latest.CreationTimestamp.Time) {
  242. latest = &state
  243. }
  244. }
  245. return latest
  246. }