move manifest/hook partition code out of tiller and export

Signed-off-by: Mike Lundy <mike@fluffypenguin.org>
pull/5040/head
Mike Lundy 7 years ago
parent a840793ecc
commit edb007b1ba
No known key found for this signature in database
GPG Key ID: 9CC064ECB96D9325

@ -35,7 +35,6 @@ import (
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/renderutil" "k8s.io/helm/pkg/renderutil"
"k8s.io/helm/pkg/tiller"
"k8s.io/helm/pkg/timeconv" "k8s.io/helm/pkg/timeconv"
) )
@ -226,7 +225,7 @@ func (t *templateCmd) run(cmd *cobra.Command, args []string) error {
manifestsToRender = listManifests manifestsToRender = listManifests
} }
for _, m := range tiller.SortByKind(manifestsToRender) { for _, m := range manifest.SortByKind(manifestsToRender) {
data := m.Content data := m.Content
b := filepath.Base(m.Name) b := filepath.Base(m.Name)
if !t.showNotes && b == "NOTES.txt" { if !t.showNotes && b == "NOTES.txt" {

@ -0,0 +1,201 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package manifest
import (
"fmt"
"log"
"path"
"strconv"
"strings"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
)
type result struct {
hooks []*release.Hook
generic []Manifest
}
type manifestFile struct {
entries map[string]string
path string
apis chartutil.VersionSet
}
// Partition takes a map of filename/YAML contents, splits the file by manifest
// entries, and sorts the entries into hook types.
//
// The resulting hooks struct will be populated with all of the generated hooks.
// Any file that does not declare one of the hook types will be placed in the
// 'generic' bucket.
//
// Files that do not parse into the expected format are simply placed into a map and
// returned.
func Partition(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) {
result := &result{}
for filePath, c := range files {
// Skip partials. We could return these as a separate map, but there doesn't
// seem to be any need for that at this time.
if strings.HasPrefix(path.Base(filePath), "_") {
continue
}
// Skip empty files and log this.
if len(strings.TrimSpace(c)) == 0 {
log.Printf("info: manifest %q is empty. Skipping.", filePath)
continue
}
manifestFile := &manifestFile{
entries: util.SplitManifests(c),
path: filePath,
apis: apis,
}
if err := manifestFile.sort(result); err != nil {
return result.hooks, result.generic, err
}
}
return result.hooks, sortByKind(result.generic, sort), nil
}
// sort takes a manifestFile object which may contain multiple resource definition
// entries and sorts each entry by hook types, and saves the resulting hooks and
// generic manifests (or non-hooks) to the result struct.
//
// To determine hook type, it looks for a YAML structure like this:
//
// kind: SomeKind
// apiVersion: v1
// metadata:
// annotations:
// helm.sh/hook: pre-install
//
// To determine the policy to delete the hook, it looks for a YAML structure like this:
//
// kind: SomeKind
// apiVersion: v1
// metadata:
// annotations:
// helm.sh/hook-delete-policy: hook-succeeded
func (file *manifestFile) sort(result *result) error {
for _, m := range file.entries {
var entry util.SimpleHead
err := yaml.Unmarshal([]byte(m), &entry)
if err != nil {
e := fmt.Errorf("YAML parse error on %s: %s", file.path, err)
return e
}
if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
})
continue
}
hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
if !ok {
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
})
continue
}
hw := calculateHookWeight(entry)
h := &release.Hook{
Name: entry.Metadata.Name,
Kind: entry.Kind,
Path: file.path,
Manifest: m,
Events: []release.Hook_Event{},
Weight: hw,
DeletePolicies: []release.Hook_DeletePolicy{},
}
isUnknownHook := false
for _, hookType := range strings.Split(hookTypes, ",") {
hookType = strings.ToLower(strings.TrimSpace(hookType))
e, ok := hooks.Events[hookType]
if !ok {
isUnknownHook = true
break
}
h.Events = append(h.Events, e)
}
if isUnknownHook {
log.Printf("info: skipping unknown hook: %q", hookTypes)
continue
}
result.hooks = append(result.hooks, h)
operateAnnotationValues(entry, hooks.HookDeleteAnno, func(value string) {
policy, exist := hooks.DeletePolices[value]
if exist {
h.DeletePolicies = append(h.DeletePolicies, policy)
} else {
log.Printf("info: skipping unknown hook delete policy: %q", value)
}
})
}
return nil
}
func hasAnyAnnotation(entry util.SimpleHead) bool {
if entry.Metadata == nil ||
entry.Metadata.Annotations == nil ||
len(entry.Metadata.Annotations) == 0 {
return false
}
return true
}
func calculateHookWeight(entry util.SimpleHead) int32 {
hws := entry.Metadata.Annotations[hooks.HookWeightAnno]
hw, err := strconv.Atoi(hws)
if err != nil {
hw = 0
}
return int32(hw)
}
func operateAnnotationValues(entry util.SimpleHead, annotation string, operate func(p string)) {
if dps, ok := entry.Metadata.Annotations[annotation]; ok {
for _, dp := range strings.Split(dps, ",") {
dp = strings.ToLower(strings.TrimSpace(dp))
operate(dp)
}
}
}

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package tiller package manifest
import ( import (
"reflect" "reflect"
@ -140,7 +140,7 @@ metadata:
manifests[o.path] = o.manifest manifests[o.path] = o.manifest
} }
hs, generic, err := sortManifests(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder) hs, generic, err := Partition(manifests, chartutil.NewVersionSet("v1", "v1beta1"), InstallOrder)
if err != nil { if err != nil {
t.Fatalf("Unexpected error: %s", err) t.Fatalf("Unexpected error: %s", err)
} }

@ -0,0 +1,158 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package manifest
import (
"sort"
)
// SortOrder is an ordering of Kinds.
type SortOrder []string
// InstallOrder is the order in which manifests should be installed (by Kind).
//
// Those occurring earlier in the list get installed before those occurring later in the list.
var InstallOrder SortOrder = []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"PodDisruptionBudget",
"Secret",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}
// UninstallOrder is the order in which manifests should be uninstalled (by Kind).
//
// Those occurring earlier in the list get uninstalled before those occurring later in the list.
var UninstallOrder SortOrder = []string{
"APIService",
"Ingress",
"Service",
"CronJob",
"Job",
"StatefulSet",
"Deployment",
"ReplicaSet",
"ReplicationController",
"Pod",
"DaemonSet",
"RoleBinding",
"Role",
"ClusterRoleBinding",
"ClusterRole",
"CustomResourceDefinition",
"ServiceAccount",
"PersistentVolumeClaim",
"PersistentVolume",
"StorageClass",
"ConfigMap",
"Secret",
"PodDisruptionBudget",
"PodSecurityPolicy",
"LimitRange",
"ResourceQuota",
"Namespace",
}
// sortByKind does an in-place sort of manifests by Kind.
//
// Results are sorted by 'ordering'
func sortByKind(manifests []Manifest, ordering SortOrder) []Manifest {
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
}
type kindSorter struct {
ordering map[string]int
manifests []Manifest
}
func newKindSorter(m []Manifest, s SortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
}
return &kindSorter{
manifests: m,
ordering: o,
}
}
func (k *kindSorter) Len() int { return len(k.manifests) }
func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i]
b := k.manifests[j]
first, aok := k.ordering[a.Head.Kind]
second, bok := k.ordering[b.Head.Kind]
if !aok && !bok {
// if both are unknown then sort alphabetically by kind and name
if a.Head.Kind != b.Head.Kind {
return a.Head.Kind < b.Head.Kind
}
return a.Name < b.Name
}
// unknown kind is last
if !aok {
return false
}
if !bok {
return true
}
// if same kind sub sort alphanumeric
if first == second {
return a.Name < b.Name
}
// sort different kinds
return first < second
}
// SortByKind sorts manifests in InstallOrder
func SortByKind(manifests []Manifest) []Manifest {
ordering := InstallOrder
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
}

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package tiller package manifest
import ( import (
"bytes" "bytes"

@ -17,189 +17,8 @@ limitations under the License.
package tiller package tiller
import ( import (
"fmt"
"log"
"path"
"strconv"
"strings"
"github.com/ghodss/yaml"
"k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest" "k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/release"
util "k8s.io/helm/pkg/releaseutil"
) )
// Manifest represents a manifest file, which has a name and some content. // Manifest has been aliased to avoid breaking API
type Manifest = manifest.Manifest type Manifest = manifest.Manifest
type result struct {
hooks []*release.Hook
generic []Manifest
}
type manifestFile struct {
entries map[string]string
path string
apis chartutil.VersionSet
}
// sortManifests takes a map of filename/YAML contents, splits the file
// by manifest entries, and sorts the entries into hook types.
//
// The resulting hooks struct will be populated with all of the generated hooks.
// Any file that does not declare one of the hook types will be placed in the
// 'generic' bucket.
//
// Files that do not parse into the expected format are simply placed into a map and
// returned.
func sortManifests(files map[string]string, apis chartutil.VersionSet, sort SortOrder) ([]*release.Hook, []Manifest, error) {
result := &result{}
for filePath, c := range files {
// Skip partials. We could return these as a separate map, but there doesn't
// seem to be any need for that at this time.
if strings.HasPrefix(path.Base(filePath), "_") {
continue
}
// Skip empty files and log this.
if len(strings.TrimSpace(c)) == 0 {
log.Printf("info: manifest %q is empty. Skipping.", filePath)
continue
}
manifestFile := &manifestFile{
entries: util.SplitManifests(c),
path: filePath,
apis: apis,
}
if err := manifestFile.sort(result); err != nil {
return result.hooks, result.generic, err
}
}
return result.hooks, sortByKind(result.generic, sort), nil
}
// sort takes a manifestFile object which may contain multiple resource definition
// entries and sorts each entry by hook types, and saves the resulting hooks and
// generic manifests (or non-hooks) to the result struct.
//
// To determine hook type, it looks for a YAML structure like this:
//
// kind: SomeKind
// apiVersion: v1
// metadata:
// annotations:
// helm.sh/hook: pre-install
//
// To determine the policy to delete the hook, it looks for a YAML structure like this:
//
// kind: SomeKind
// apiVersion: v1
// metadata:
// annotations:
// helm.sh/hook-delete-policy: hook-succeeded
func (file *manifestFile) sort(result *result) error {
for _, m := range file.entries {
var entry util.SimpleHead
err := yaml.Unmarshal([]byte(m), &entry)
if err != nil {
e := fmt.Errorf("YAML parse error on %s: %s", file.path, err)
return e
}
if !hasAnyAnnotation(entry) {
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
})
continue
}
hookTypes, ok := entry.Metadata.Annotations[hooks.HookAnno]
if !ok {
result.generic = append(result.generic, Manifest{
Name: file.path,
Content: m,
Head: &entry,
})
continue
}
hw := calculateHookWeight(entry)
h := &release.Hook{
Name: entry.Metadata.Name,
Kind: entry.Kind,
Path: file.path,
Manifest: m,
Events: []release.Hook_Event{},
Weight: hw,
DeletePolicies: []release.Hook_DeletePolicy{},
}
isUnknownHook := false
for _, hookType := range strings.Split(hookTypes, ",") {
hookType = strings.ToLower(strings.TrimSpace(hookType))
e, ok := hooks.Events[hookType]
if !ok {
isUnknownHook = true
break
}
h.Events = append(h.Events, e)
}
if isUnknownHook {
log.Printf("info: skipping unknown hook: %q", hookTypes)
continue
}
result.hooks = append(result.hooks, h)
operateAnnotationValues(entry, hooks.HookDeleteAnno, func(value string) {
policy, exist := hooks.DeletePolices[value]
if exist {
h.DeletePolicies = append(h.DeletePolicies, policy)
} else {
log.Printf("info: skipping unknown hook delete policy: %q", value)
}
})
}
return nil
}
func hasAnyAnnotation(entry util.SimpleHead) bool {
if entry.Metadata == nil ||
entry.Metadata.Annotations == nil ||
len(entry.Metadata.Annotations) == 0 {
return false
}
return true
}
func calculateHookWeight(entry util.SimpleHead) int32 {
hws := entry.Metadata.Annotations[hooks.HookWeightAnno]
hw, err := strconv.Atoi(hws)
if err != nil {
hw = 0
}
return int32(hw)
}
func operateAnnotationValues(entry util.SimpleHead, annotation string, operate func(p string)) {
if dps, ok := entry.Metadata.Annotations[annotation]; ok {
for _, dp := range strings.Split(dps, ",") {
dp = strings.ToLower(strings.TrimSpace(dp))
operate(dp)
}
}
}

@ -17,142 +17,19 @@ limitations under the License.
package tiller package tiller
import ( import (
"sort" "k8s.io/helm/pkg/manifest"
) )
// SortOrder is an ordering of Kinds. // SortOrder has been aliased to avoid breaking API
type SortOrder []string type SortOrder = manifest.SortOrder
// InstallOrder is the order in which manifests should be installed (by Kind). var (
// // InstallOrder has been aliased to avoid breaking API
// Those occurring earlier in the list get installed before those occurring later in the list. InstallOrder SortOrder = manifest.InstallOrder
var InstallOrder SortOrder = []string{
"Namespace",
"ResourceQuota",
"LimitRange",
"PodSecurityPolicy",
"PodDisruptionBudget",
"Secret",
"ConfigMap",
"StorageClass",
"PersistentVolume",
"PersistentVolumeClaim",
"ServiceAccount",
"CustomResourceDefinition",
"ClusterRole",
"ClusterRoleBinding",
"Role",
"RoleBinding",
"Service",
"DaemonSet",
"Pod",
"ReplicationController",
"ReplicaSet",
"Deployment",
"StatefulSet",
"Job",
"CronJob",
"Ingress",
"APIService",
}
// UninstallOrder is the order in which manifests should be uninstalled (by Kind). // UninstallOrder has been aliased to avoid breaking API
// UninstallOrder SortOrder = manifest.UninstallOrder
// Those occurring earlier in the list get uninstalled before those occurring later in the list.
var UninstallOrder SortOrder = []string{
"APIService",
"Ingress",
"Service",
"CronJob",
"Job",
"StatefulSet",
"Deployment",
"ReplicaSet",
"ReplicationController",
"Pod",
"DaemonSet",
"RoleBinding",
"Role",
"ClusterRoleBinding",
"ClusterRole",
"CustomResourceDefinition",
"ServiceAccount",
"PersistentVolumeClaim",
"PersistentVolume",
"StorageClass",
"ConfigMap",
"Secret",
"PodDisruptionBudget",
"PodSecurityPolicy",
"LimitRange",
"ResourceQuota",
"Namespace",
}
// sortByKind does an in-place sort of manifests by Kind. // SortByKind has been aliased to avoid breaking API
// SortByKind func([]manifest.Manifest) []manifest.Manifest = manifest.SortByKind
// Results are sorted by 'ordering' )
func sortByKind(manifests []Manifest, ordering SortOrder) []Manifest {
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
}
type kindSorter struct {
ordering map[string]int
manifests []Manifest
}
func newKindSorter(m []Manifest, s SortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
}
return &kindSorter{
manifests: m,
ordering: o,
}
}
func (k *kindSorter) Len() int { return len(k.manifests) }
func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i]
b := k.manifests[j]
first, aok := k.ordering[a.Head.Kind]
second, bok := k.ordering[b.Head.Kind]
if !aok && !bok {
// if both are unknown then sort alphabetically by kind and name
if a.Head.Kind != b.Head.Kind {
return a.Head.Kind < b.Head.Kind
}
return a.Name < b.Name
}
// unknown kind is last
if !aok {
return false
}
if !bok {
return true
}
// if same kind sub sort alphanumeric
if first == second {
return a.Name < b.Name
}
// sort different kinds
return first < second
}
// SortByKind sorts manifests in InstallOrder
func SortByKind(manifests []Manifest) []Manifest {
ordering := InstallOrder
ks := newKindSorter(manifests, ordering)
sort.Sort(ks)
return ks.manifests
}

@ -26,6 +26,7 @@ import (
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/kube" "k8s.io/helm/pkg/kube"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
rudderAPI "k8s.io/helm/pkg/proto/hapi/rudder" rudderAPI "k8s.io/helm/pkg/proto/hapi/rudder"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
@ -161,7 +162,7 @@ func (m *RemoteReleaseModule) Delete(r *release.Release, req *services.Uninstall
// DeleteRelease is a helper that allows Rudder to delete a release without exposing most of Tiller inner functions // DeleteRelease is a helper that allows Rudder to delete a release without exposing most of Tiller inner functions
func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient environment.KubeClient) (kept string, errs []error) { func DeleteRelease(rel *release.Release, vs chartutil.VersionSet, kubeClient environment.KubeClient) (kept string, errs []error) {
manifests := relutil.SplitManifests(rel.Manifest) manifests := relutil.SplitManifests(rel.Manifest)
_, files, err := sortManifests(manifests, vs, UninstallOrder) _, files, err := manifest.Partition(manifests, vs, manifest.UninstallOrder)
if err != nil { if err != nil {
// We could instead just delete everything in no particular order. // We could instead just delete everything in no particular order.
// FIXME: One way to delete at this point would be to try a label-based // FIXME: One way to delete at this point would be to try a label-based

@ -32,6 +32,7 @@ import (
"k8s.io/helm/pkg/chartutil" "k8s.io/helm/pkg/chartutil"
"k8s.io/helm/pkg/hooks" "k8s.io/helm/pkg/hooks"
"k8s.io/helm/pkg/manifest"
"k8s.io/helm/pkg/proto/hapi/chart" "k8s.io/helm/pkg/proto/hapi/chart"
"k8s.io/helm/pkg/proto/hapi/release" "k8s.io/helm/pkg/proto/hapi/release"
"k8s.io/helm/pkg/proto/hapi/services" "k8s.io/helm/pkg/proto/hapi/services"
@ -330,7 +331,7 @@ func (s *ReleaseServer) renderResources(ch *chart.Chart, values chartutil.Values
// Sort hooks, manifests, and partials. Only hooks and manifests are returned, // Sort hooks, manifests, and partials. Only hooks and manifests are returned,
// as partials are not used after renderer.Render. Empty manifests are also // as partials are not used after renderer.Render. Empty manifests are also
// removed here. // removed here.
hooks, manifests, err := sortManifests(files, vs, InstallOrder) hooks, manifests, err := manifest.Partition(files, vs, manifest.InstallOrder)
if err != nil { if err != nil {
// By catching parse errors here, we can prevent bogus releases from going // By catching parse errors here, we can prevent bogus releases from going
// to Kubernetes. // to Kubernetes.

Loading…
Cancel
Save