GitOps for k8s
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

manifests.go 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. package kubernetes
  2. import (
  3. "bytes"
  4. "fmt"
  5. "strings"
  6. "github.com/go-kit/kit/log"
  7. "gopkg.in/yaml.v2"
  8. "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
  9. "k8s.io/apimachinery/pkg/runtime/schema"
  10. kresource "github.com/fluxcd/flux/cluster/kubernetes/resource"
  11. "github.com/fluxcd/flux/image"
  12. "github.com/fluxcd/flux/resource"
  13. )
  14. // ResourceScopes maps resource definitions (GroupVersionKind) to whether they are namespaced or not
  15. type ResourceScopes map[schema.GroupVersionKind]v1beta1.ResourceScope
  16. // namespacer assigns namespaces to manifests that need it (or "" if
  17. // the manifest should not have a namespace.
  18. type namespacer interface {
  19. // EffectiveNamespace gives the namespace that would be used were
  20. // the manifest to be applied. This may be "", indicating that it
  21. // should not have a namespace (i.e., it's a cluster-level
  22. // resource).
  23. EffectiveNamespace(manifest kresource.KubeManifest, knownScopes ResourceScopes) (string, error)
  24. }
  25. // manifests is an implementation of cluster.Manifests, particular to
  26. // Kubernetes. Aside from loading manifests from files, it does some
  27. // "post-processing" to make sure the view of the manifests is what
  28. // would be applied; in particular, it fills in the namespace of
  29. // manifests that would be given a default namespace when applied.
  30. type manifests struct {
  31. namespacer namespacer
  32. logger log.Logger
  33. resourceWarnings map[string]struct{}
  34. }
  35. func NewManifests(ns namespacer, logger log.Logger) *manifests {
  36. return &manifests{
  37. namespacer: ns,
  38. logger: logger,
  39. resourceWarnings: map[string]struct{}{},
  40. }
  41. }
  42. func getCRDScopes(manifests map[string]kresource.KubeManifest) ResourceScopes {
  43. result := ResourceScopes{}
  44. for _, km := range manifests {
  45. if km.GetKind() == "CustomResourceDefinition" {
  46. var crd v1beta1.CustomResourceDefinition
  47. if err := yaml.Unmarshal(km.Bytes(), &crd); err != nil {
  48. // The CRD can't be parsed, so we (intentionally) ignore it and
  49. // just hope for EffectiveNamespace() to find its scope in the cluster if needed.
  50. continue
  51. }
  52. crdVersions := crd.Spec.Versions
  53. if len(crdVersions) == 0 {
  54. crdVersions = []v1beta1.CustomResourceDefinitionVersion{{Name: crd.Spec.Version}}
  55. }
  56. for _, crdVersion := range crdVersions {
  57. gvk := schema.GroupVersionKind{
  58. Group: crd.Spec.Group,
  59. Version: crdVersion.Name,
  60. Kind: crd.Spec.Names.Kind,
  61. }
  62. result[gvk] = crd.Spec.Scope
  63. }
  64. }
  65. }
  66. return result
  67. }
  68. func (m *manifests) setEffectiveNamespaces(manifests map[string]kresource.KubeManifest) (map[string]resource.Resource, error) {
  69. knownScopes := getCRDScopes(manifests)
  70. result := map[string]resource.Resource{}
  71. for _, km := range manifests {
  72. resID := km.ResourceID()
  73. resIDStr := resID.String()
  74. ns, err := m.namespacer.EffectiveNamespace(km, knownScopes)
  75. if err != nil {
  76. if strings.Contains(err.Error(), "not found") {
  77. // discard the resource and keep going after making sure we logged about it
  78. if _, warningLogged := m.resourceWarnings[resIDStr]; !warningLogged {
  79. _, kind, name := resID.Components()
  80. partialResIDStr := kind + "/" + name
  81. m.logger.Log(
  82. "warn", fmt.Sprintf("cannot find scope of resource %s: %s", partialResIDStr, err),
  83. "impact", fmt.Sprintf("resource %s will be excluded until its scope is available", partialResIDStr))
  84. m.resourceWarnings[resIDStr] = struct{}{}
  85. }
  86. continue
  87. }
  88. return nil, err
  89. }
  90. km.SetNamespace(ns)
  91. if _, warningLogged := m.resourceWarnings[resIDStr]; warningLogged {
  92. // indicate that we found the resource's scope and allow logging a warning again
  93. m.logger.Log("info", fmt.Sprintf("found scope of resource %s, back in business!", km.ResourceID().String()))
  94. delete(m.resourceWarnings, resIDStr)
  95. }
  96. result[km.ResourceID().String()] = km
  97. }
  98. return result, nil
  99. }
  100. func (m *manifests) LoadManifests(baseDir string, paths []string) (map[string]resource.Resource, error) {
  101. manifests, err := kresource.Load(baseDir, paths)
  102. if err != nil {
  103. return nil, err
  104. }
  105. return m.setEffectiveNamespaces(manifests)
  106. }
  107. func (m *manifests) ParseManifest(def []byte, source string) (map[string]resource.Resource, error) {
  108. resources, err := kresource.ParseMultidoc(def, source)
  109. if err != nil {
  110. return nil, err
  111. }
  112. // Note: setEffectiveNamespaces() won't work for CRD instances whose CRD is yet to be created
  113. // (due to the CRD not being present in kresources).
  114. // We could get out of our way to fix this (or give a better error) but:
  115. // 1. With the exception of HelmReleases CRD instances are not workloads anyways.
  116. // 2. The problem is eventually fixed by the first successful sync.
  117. result, err := m.setEffectiveNamespaces(resources)
  118. if err != nil {
  119. return nil, err
  120. }
  121. return result, nil
  122. }
  123. func (m *manifests) SetWorkloadContainerImage(def []byte, id resource.ID, container string, image image.Ref) ([]byte, error) {
  124. resources, err := m.ParseManifest(def, "stdin")
  125. if err != nil {
  126. return nil, err
  127. }
  128. res, ok := resources[id.String()]
  129. if !ok {
  130. return nil, fmt.Errorf("resource %s not found", id.String())
  131. }
  132. // Check if the workload is a HelmRelease, and try to resolve an image
  133. // map for the given container to perform an update based on mapped YAML
  134. // dot notation paths. If resolving the map fails (either because there
  135. // is no map for the given container, or the mapping does not resolve
  136. // in to a valid image ref), it falls through and attempts to update
  137. // using just the container name (as it must origin from an automated
  138. // detection).
  139. //
  140. // NB: we do this here and not in e.g. the `resource` package, to ensure
  141. // everything _outside_ this package only knows about Kubernetes native
  142. // containers, and not the dot notation YAML paths we invented for custom
  143. // Helm value structures.
  144. if hr, ok := res.(*kresource.HelmRelease); ok {
  145. if paths, err := hr.GetContainerImageMap(container); err == nil {
  146. return updateWorkloadImagePaths(def, id, paths, image)
  147. }
  148. }
  149. return updateWorkloadContainer(def, id, container, image)
  150. }
  151. func (m *manifests) CreateManifestPatch(originalManifests, modifiedManifests []byte, originalSource, modifiedSource string) ([]byte, error) {
  152. return createManifestPatch(originalManifests, modifiedManifests, originalSource, modifiedSource)
  153. }
  154. func (m *manifests) ApplyManifestPatch(originalManifests, patchManifests []byte, originalSource, patchSource string) ([]byte, error) {
  155. return applyManifestPatch(originalManifests, patchManifests, originalSource, patchSource)
  156. }
  157. func (m *manifests) AppendManifestToBuffer(manifest []byte, buffer *bytes.Buffer) error {
  158. return appendYAMLToBuffer(manifest, buffer)
  159. }
  160. func appendYAMLToBuffer(manifest []byte, buffer *bytes.Buffer) error {
  161. separator := "---\n"
  162. bytes := buffer.Bytes()
  163. if len(bytes) > 0 && bytes[len(bytes)-1] != '\n' {
  164. separator = "\n---\n"
  165. }
  166. if _, err := buffer.WriteString(separator); err != nil {
  167. return fmt.Errorf("cannot write to internal buffer: %s", err)
  168. }
  169. if _, err := buffer.Write(manifest); err != nil {
  170. return fmt.Errorf("cannot write to internal buffer: %s", err)
  171. }
  172. return nil
  173. }
  174. // UpdateWorkloadPolicies in policies.go