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.

resource.go 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. package resource
  2. import (
  3. "strings"
  4. jsonyaml "github.com/ghodss/yaml"
  5. "gopkg.in/yaml.v2"
  6. fluxerr "github.com/fluxcd/flux/errors"
  7. "github.com/fluxcd/flux/policy"
  8. "github.com/fluxcd/flux/resource"
  9. )
  10. const (
  11. PolicyPrefix = "fluxcd.io/"
  12. FilterPolicyPrefix = "filter.fluxcd.io/"
  13. // This is the previously-used prefix for annotations; many
  14. // manifests in the wild will still be using it, so it's included
  15. // here for backward-compatibility.
  16. AlternatePolicyPrefix = "flux.weave.works/"
  17. ClusterScope = "<cluster>"
  18. )
  19. // KubeManifest represents a manifest for a Kubernetes resource. For
  20. // some Kubernetes-specific purposes we need more information that can
  21. // be obtained from `resource.Resource`.
  22. type KubeManifest interface {
  23. resource.Resource
  24. GroupVersion() string
  25. GetKind() string
  26. GetName() string
  27. GetNamespace() string
  28. SetNamespace(string)
  29. PolicyAnnotationKey(string) (string, bool)
  30. }
  31. // -- unmarshaling code for specific object and field types
  32. // struct to embed in objects, to provide default implementation
  33. type baseObject struct {
  34. source string
  35. bytes []byte
  36. // these are present for unmarshalling into the struct
  37. APIVersion string `yaml:"apiVersion"`
  38. Kind string `yaml:"kind"`
  39. Meta struct {
  40. Namespace string `yaml:"namespace"`
  41. Name string `yaml:"name"`
  42. Annotations map[string]string `yaml:"annotations,omitempty"`
  43. } `yaml:"metadata"`
  44. }
  45. // GroupVersion implements KubeManifest.GroupVersion, so things with baseObject embedded are < KubeManifest
  46. func (o baseObject) GroupVersion() string {
  47. return o.APIVersion
  48. }
  49. // GetNamespace implements KubeManifest.GetNamespace, so things embedding baseObject are < KubeManifest
  50. func (o baseObject) GetNamespace() string {
  51. return o.Meta.Namespace
  52. }
  53. // GetKind implements KubeManifest.GetKind
  54. func (o baseObject) GetKind() string {
  55. return o.Kind
  56. }
  57. // GetName implements KubeManifest.GetName
  58. func (o baseObject) GetName() string {
  59. return o.Meta.Name
  60. }
  61. func (o baseObject) ResourceID() resource.ID {
  62. ns := o.Meta.Namespace
  63. if ns == "" {
  64. ns = ClusterScope
  65. }
  66. return resource.MakeID(ns, o.Kind, o.Meta.Name)
  67. }
  68. // SetNamespace implements KubeManifest.SetNamespace, so things with
  69. // *baseObject embedded are < KubeManifest. NB pointer receiver.
  70. func (o *baseObject) SetNamespace(ns string) {
  71. o.Meta.Namespace = ns
  72. }
  73. // It's useful for comparisons in tests to be able to remove the
  74. // record of bytes
  75. func (o *baseObject) debyte() {
  76. o.bytes = nil
  77. }
  78. func PoliciesFromAnnotations(annotations map[string]string) policy.Set {
  79. set := policy.Set{}
  80. for k, v := range annotations {
  81. var p string
  82. switch {
  83. case strings.HasPrefix(k, PolicyPrefix):
  84. p = strings.TrimPrefix(k, PolicyPrefix)
  85. case strings.HasPrefix(k, AlternatePolicyPrefix):
  86. p = strings.TrimPrefix(k, AlternatePolicyPrefix)
  87. case strings.HasPrefix(k, FilterPolicyPrefix):
  88. p = "tag." + strings.TrimPrefix(k, FilterPolicyPrefix)
  89. default:
  90. continue
  91. }
  92. if v == "true" {
  93. set = set.Add(policy.Policy(p))
  94. } else {
  95. set = set.Set(policy.Policy(p), v)
  96. }
  97. }
  98. return set
  99. }
  100. func (o baseObject) Policies() policy.Set {
  101. return PoliciesFromAnnotations(o.Meta.Annotations)
  102. }
  103. // PolicyAnnotationKey returns the key used in this resource to
  104. // indicate a particular policy; this is to aid in supporting more
  105. // than one way of using annotations for policy. If the policy is not
  106. // present, returns `"", false`.
  107. func (o baseObject) PolicyAnnotationKey(p string) (string, bool) {
  108. for _, prefix := range []string{PolicyPrefix, AlternatePolicyPrefix, FilterPolicyPrefix} {
  109. key := prefix + p
  110. if prefix == FilterPolicyPrefix {
  111. key = prefix + strings.TrimPrefix(p, "tag.")
  112. }
  113. if _, ok := o.Meta.Annotations[key]; ok {
  114. return key, true
  115. }
  116. }
  117. return "", false
  118. }
  119. func (o baseObject) Source() string {
  120. return o.source
  121. }
  122. func (o baseObject) Bytes() []byte {
  123. return o.bytes
  124. }
  125. func unmarshalObject(source string, bytes []byte) (KubeManifest, error) {
  126. var base = baseObject{source: source, bytes: bytes}
  127. if err := yaml.Unmarshal(bytes, &base); err != nil {
  128. return nil, err
  129. }
  130. r, err := unmarshalKind(base, bytes)
  131. if err != nil {
  132. return nil, makeUnmarshalObjectErr(source, err)
  133. }
  134. return r, nil
  135. }
  136. func unmarshalKind(base baseObject, bytes []byte) (KubeManifest, error) {
  137. switch {
  138. case base.Kind == "CronJob":
  139. var cj = CronJob{baseObject: base}
  140. if err := yaml.Unmarshal(bytes, &cj); err != nil {
  141. return nil, err
  142. }
  143. return &cj, nil
  144. case base.Kind == "DaemonSet":
  145. var ds = DaemonSet{baseObject: base}
  146. if err := yaml.Unmarshal(bytes, &ds); err != nil {
  147. return nil, err
  148. }
  149. return &ds, nil
  150. case base.Kind == "Deployment":
  151. var dep = Deployment{baseObject: base}
  152. if err := yaml.Unmarshal(bytes, &dep); err != nil {
  153. return nil, err
  154. }
  155. return &dep, nil
  156. case base.Kind == "Namespace":
  157. var ns = Namespace{baseObject: base}
  158. if err := yaml.Unmarshal(bytes, &ns); err != nil {
  159. return nil, err
  160. }
  161. return &ns, nil
  162. case base.Kind == "StatefulSet":
  163. var ss = StatefulSet{baseObject: base}
  164. if err := yaml.Unmarshal(bytes, &ss); err != nil {
  165. return nil, err
  166. }
  167. return &ss, nil
  168. case strings.HasSuffix(base.Kind, "List"):
  169. // All resource kinds ending with `List` are understood as
  170. // a list of resources. This is not bullet proof since
  171. // CustomResourceDefinitions can define a custom ListKind
  172. // (see CustomResourceDefinition.Spec.Names.ListKind) but
  173. // we cannot do better without involving API discovery during
  174. // parsing.
  175. var raw rawList
  176. if err := yaml.Unmarshal(bytes, &raw); err != nil {
  177. return nil, err
  178. }
  179. var list List
  180. unmarshalList(base, &raw, &list)
  181. return &list, nil
  182. case base.Kind == "FluxHelmRelease" || base.Kind == "HelmRelease":
  183. var hr = HelmRelease{baseObject: base}
  184. // NB: workaround for go-yaml/yaml/issues/139
  185. // By using github.com/ghodss/yaml to unmarshal HelmReleases.
  186. // This effectively results in all keys of `Value`s being strings
  187. // and not interface{}.
  188. if err := jsonyaml.Unmarshal(bytes, &hr); err != nil {
  189. return nil, err
  190. }
  191. return &hr, nil
  192. case base.Kind == "":
  193. // If there is an empty resource (due to eg an introduced comment),
  194. // we are returning nil for the resource and nil for an error
  195. // (as not really an error). We are not, at least at the moment,
  196. // reporting an error for invalid non-resource yamls on the
  197. // assumption it is unlikely to happen.
  198. return nil, nil
  199. // The remainder are things we have to care about, but not
  200. // treat specially
  201. default:
  202. return &base, nil
  203. }
  204. }
  205. type rawList struct {
  206. Items []map[string]interface{}
  207. }
  208. func unmarshalList(base baseObject, raw *rawList, list *List) error {
  209. list.baseObject = base
  210. list.Items = make([]KubeManifest, len(raw.Items), len(raw.Items))
  211. for i, item := range raw.Items {
  212. bytes, err := yaml.Marshal(item)
  213. if err != nil {
  214. return err
  215. }
  216. res, err := unmarshalObject(base.source, bytes)
  217. if err != nil {
  218. return err
  219. }
  220. list.Items[i] = res
  221. }
  222. return nil
  223. }
  224. func makeUnmarshalObjectErr(source string, err error) *fluxerr.Error {
  225. return &fluxerr.Error{
  226. Type: fluxerr.User,
  227. Err: err,
  228. Help: `Could not parse "` + source + `".
  229. This likely means it is malformed YAML.
  230. `,
  231. }
  232. }
  233. // For reference, the Kubernetes v1 types are in:
  234. // https://github.com/kubernetes/client-go/blob/master/pkg/api/v1/types.go