Skip to content
Permalink
Browse files

Replace `--only` with `--include`, add `--exclude`

- generalise `NodeGroupFilter`
- rename `CheckEachNodeGroup` to `ForEach`
- add tests
  • Loading branch information
errordeveloper committed Apr 9, 2019
1 parent 05c5784 commit 63470760c357f3a2d9bc9b014fa4998d9326e6b0
@@ -179,7 +179,7 @@ func NewCreateClusterLoader(provider *api.ProviderConfig, spec *api.ClusterConfi
return fmt.Errorf("status fields are read-only")
}

return ngFilter.CheckEachNodeGroup(l.spec.NodeGroups, func(i int, ng *api.NodeGroup) error {
return ngFilter.ForEach(l.spec.NodeGroups, func(i int, ng *api.NodeGroup) error {
if cmd.Flag("ssh-public-key").Changed {
if *ng.SSH.PublicKeyPath == "" {
return fmt.Errorf("--ssh-public-key must be non-empty string")
@@ -200,7 +200,7 @@ func NewCreateClusterLoader(provider *api.ProviderConfig, spec *api.ClusterConfi
}

// NewCreateNodeGroupLoader will laod config or use flags for 'eksctl create nodegroup'
func NewCreateNodeGroupLoader(provider *api.ProviderConfig, spec *api.ClusterConfig, clusterConfigFile, nameArg string, cmd *cobra.Command, ngFilter *NodeGroupFilter, nodeGroupOnlyFilters []string) ClusterConfigLoader {
func NewCreateNodeGroupLoader(provider *api.ProviderConfig, spec *api.ClusterConfig, clusterConfigFile, nameArg string, cmd *cobra.Command, ngFilter *NodeGroupFilter, include, exclude []string) ClusterConfigLoader {
l := newCommonClusterConfigLoader(provider, spec, clusterConfigFile, cmd)

l.nameArg = nameArg
@@ -228,22 +228,24 @@ func NewCreateNodeGroupLoader(provider *api.ProviderConfig, spec *api.ClusterCon
)

l.validateWithConfigFile = func() error {
if err := ngFilter.ApplyOnlyFilter(nodeGroupOnlyFilters, spec); err != nil {
if err := ngFilter.SetFilterGlobs(include, exclude, spec.NodeGroups); err != nil {
return err
}
return nil
}

l.flagsIncompatibleWithoutConfigFile.Insert(
"only",
"include",
"exclude",
)

l.validateWithoutConfigFile = func() error {
if spec.Metadata.Name == "" {
return ErrMustBeSet("--cluster")
}

return ngFilter.CheckEachNodeGroup(spec.NodeGroups, func(i int, ng *api.NodeGroup) error {
return ngFilter.ForEach(spec.NodeGroups, func(i int, ng *api.NodeGroup) error {

if cmd.Flag("ssh-public-key").Changed {
if *ng.SSH.PublicKeyPath == "" {
@@ -16,54 +16,79 @@ import (

// NodeGroupFilter holds filter configuration
type NodeGroupFilter struct {
SkipAll bool
IgnoreAllExisting bool
ExcludeAll bool // highest priority

existing sets.String
only []glob.Glob
onlySpec string
// include filters take presidence
IncludeNames sets.String
includeGlobs []glob.Glob
includeGlobsSpec string

ExcludeNames sets.String
excludeGlobs []glob.Glob
excludeGlobsSpec string
}

// NewNodeGroupFilter create new NodeGroupFilter instance
func NewNodeGroupFilter() *NodeGroupFilter {
return &NodeGroupFilter{
IgnoreAllExisting: true,
SkipAll: false,
existing: sets.NewString(),
ExcludeAll: false,
IncludeNames: sets.NewString(),
ExcludeNames: sets.NewString(),
}
}

// SetFilterGlobs sets globs for inclusion and exclusion rules
func (f *NodeGroupFilter) SetFilterGlobs(includeGlobExprs, excludeGlobExprs []string, nodeGroups []*api.NodeGroup) error {
if err := f.SetIncludeFilterGlobs(includeGlobExprs, nodeGroups); err != nil {
return err
}
return f.SetExcludeFilterGlobs(excludeGlobExprs)
}

// ApplyOnlyFilter parses given globs for exclusive filtering
func (f *NodeGroupFilter) ApplyOnlyFilter(globExprs []string, cfg *api.ClusterConfig) error {
// SetIncludeFilterGlobs sets globs for inclusion rules
func (f *NodeGroupFilter) SetIncludeFilterGlobs(globExprs []string, nodeGroups []*api.NodeGroup) error {
for _, expr := range globExprs {
compiledExpr, err := glob.Compile(expr)
if err != nil {
return errors.Wrapf(err, "parsing glob filter %q", expr)
}
f.only = append(f.only, compiledExpr)
f.includeGlobs = append(f.includeGlobs, compiledExpr)
}
f.onlySpec = strings.Join(globExprs, ",")
return f.onlyFilterMatchesAnything(cfg)
f.includeGlobsSpec = strings.Join(globExprs, ",")
return f.includeGlobsMatchAnything(nodeGroups)
}

func (f *NodeGroupFilter) onlyFilterMatchesAnything(cfg *api.ClusterConfig) error {
if len(f.only) == 0 {
func (f *NodeGroupFilter) includeGlobsMatchAnything(nodeGroups []*api.NodeGroup) error {
if len(f.includeGlobs) == 0 {
return nil
}
for _, ng := range cfg.NodeGroups {
for _, compiledExpr := range f.only {
if compiledExpr.Match(ng.Name) {
return nil
}
for _, ng := range nodeGroups {
ok := false
f.matchGlobs(ng.Name, f.includeGlobs, &ok)
if ok {
return nil
}
}
return fmt.Errorf("no nodegroups match include filter specification: %q", f.includeGlobsSpec)
}

// SetExcludeFilterGlobs sets globs for exclusion rules
func (f *NodeGroupFilter) SetExcludeFilterGlobs(globExprs []string) error {
for _, expr := range globExprs {
compiledExpr, err := glob.Compile(expr)
if err != nil {
return errors.Wrapf(err, "parsing glob filter %q", expr)
}
f.excludeGlobs = append(f.excludeGlobs, compiledExpr)
}
return fmt.Errorf("no nodegroups match filter specification: %q", f.onlySpec)
f.excludeGlobsSpec = strings.Join(globExprs, ",")
return nil // exclude filter doesn't have to match anything, so we don't validate it
}

// ApplyExistingFilter uses stackManager to list existing nodegroup stacks and configures
// SetExcludeExistingFilter uses stackManager to list existing nodegroup stacks and configures
// the filter accordingly
func (f *NodeGroupFilter) ApplyExistingFilter(stackManager *manager.StackCollection) error {
if !f.IgnoreAllExisting || f.SkipAll {
func (f *NodeGroupFilter) SetExcludeExistingFilter(stackManager *manager.StackCollection) error {
if f.ExcludeAll {
return nil
}

@@ -72,70 +97,109 @@ func (f *NodeGroupFilter) ApplyExistingFilter(stackManager *manager.StackCollect
return err
}

f.existing.Insert(existing...)
f.ExcludeNames.Insert(existing...)

for _, name := range existing {
isAlsoIncluded := false
f.matchGlobs(name, f.includeGlobs, &isAlsoIncluded)
if isAlsoIncluded {
return fmt.Errorf("existing nodegroup %q should be excluded, but matches include fileter: %q", name, f.includeGlobsSpec)
}
}
return nil
}

// Match checks given nodegroup against the filter
func (f *NodeGroupFilter) Match(ng *api.NodeGroup) bool {
if f.SkipAll {
return false
func (*NodeGroupFilter) matchGlobs(name string, exprs []glob.Glob, result *bool) {
for _, compiledExpr := range exprs {
if compiledExpr.Match(name) {
*result = true
return
}
}
}

// Match given nodegroup against the filter and returns
// true or false if it has to be included or excluded
func (f *NodeGroupFilter) Match(name string) bool {
if f.ExcludeAll {
return false // force exclude
}
if f.IgnoreAllExisting && f.existing.Has(ng.Name) {
return false

hasIncludeRules := f.IncludeNames.Len()+len(f.includeGlobs) != 0
hasExcludeRules := f.ExcludeNames.Len()+len(f.excludeGlobs) != 0

if !hasIncludeRules && !hasExcludeRules {
return true // empty rules - include
}

mustInclude := false // override when rules overlap

if hasIncludeRules {
mustInclude = f.IncludeNames.Has(name)
f.matchGlobs(name, f.includeGlobs, &mustInclude)
if !hasExcludeRules {
// empty exclusion rules - strict inclusion mode
return mustInclude
}
}

for _, compiledExpr := range f.only {
if compiledExpr.Match(ng.Name) {
return true // return first match
if hasExcludeRules {
exclude := f.ExcludeNames.Has(name)
f.matchGlobs(name, f.excludeGlobs, &exclude)
if exclude && !mustInclude {
// no inclusion rules to override
return false
}
}

// if no globs were given, match everything,
// if false - we haven't matched anything so far
return len(f.only) == 0
return true // biased to include
}

// MatchAll checks all nodegroups against the filter and returns all of
// matching names as set
func (f *NodeGroupFilter) MatchAll(cfg *api.ClusterConfig) sets.String {
names := sets.NewString()
if f.SkipAll {
return names
// MatchAll nodegroups against the filter and return two sets of names
func (f *NodeGroupFilter) MatchAll(nodeGroups []*api.NodeGroup) (sets.String, sets.String) {
included, excluded := sets.NewString(), sets.NewString()
if f.ExcludeAll {
for _, ng := range nodeGroups {
excluded.Insert(ng.Name)
}
return included, excluded
}
for _, ng := range cfg.NodeGroups {
if f.Match(ng) {
names.Insert(ng.Name)
for _, ng := range nodeGroups {
if f.Match(ng.Name) {
included.Insert(ng.Name)
} else {
excluded.Insert(ng.Name)
}
}
return names
return included, excluded
}

// LogInfo prints out a user-friendly message about how filter was applied
func (f *NodeGroupFilter) LogInfo(cfg *api.ClusterConfig) {
count := f.MatchAll(cfg).Len()
filteredOutCount := len(cfg.NodeGroups) - count
if filteredOutCount > 0 {
reasons := []string{}
if f.SkipAll {
reasons = append(reasons, fmt.Sprintf("--without-nodegroup was set"))
}
if f.onlySpec != "" {
reasons = append(reasons, fmt.Sprintf("--only=%q was given", f.onlySpec))
func (f *NodeGroupFilter) LogInfo(nodeGroups []*api.NodeGroup) {
logMsg := func(ngSubset sets.String, status string) {
count := ngSubset.Len()
list := strings.Join(ngSubset.List(), ", ")
subject := "nodegroups (%s) were"
if count == 1 {
subject = "nodegroup (%s) was"
}
if existingCount := f.existing.Len(); existingCount > 0 {
reasons = append(reasons, fmt.Sprintf("%d nodegroup(s) (%s) already exist", existingCount, strings.Join(f.existing.List(), ", ")))
}
logger.Info("%d nodegroup(s) were filtered out: %s", filteredOutCount, strings.Join(reasons, ", "))
logger.Info("%d "+subject+" %s", count, list, status)
}

included, excluded := f.MatchAll(nodeGroups)
if excluded.Len() > 0 {
logMsg(excluded, "excluded")
}
if included.Len() > 0 {
logMsg(included, "included")
}
}

// CheckEachNodeGroup iterates over each nodegroup and calls check function if it matches the filter
func (f *NodeGroupFilter) CheckEachNodeGroup(nodeGroups []*api.NodeGroup, check func(i int, ng *api.NodeGroup) error) error {
// ForEach iterates over each nodegroup that is included by the filter and calls iterFn
func (f *NodeGroupFilter) ForEach(nodeGroups []*api.NodeGroup, iterFn func(i int, ng *api.NodeGroup) error) error {
for i, ng := range nodeGroups {
if f.Match(ng) {
if err := check(i, ng); err != nil {
if f.Match(ng.Name) {
if err := iterFn(i, ng); err != nil {
return err
}
}
@@ -146,7 +210,7 @@ func (f *NodeGroupFilter) CheckEachNodeGroup(nodeGroups []*api.NodeGroup, check
// ValidateNodeGroupsAndSetDefaults is calls api.ValidateNodeGroup & api.SetNodeGroupDefaults for
// all nodegroups that match the filter
func (f *NodeGroupFilter) ValidateNodeGroupsAndSetDefaults(nodeGroups []*api.NodeGroup) error {
return f.CheckEachNodeGroup(nodeGroups, func(i int, ng *api.NodeGroup) error {
return f.ForEach(nodeGroups, func(i int, ng *api.NodeGroup) error {
if err := api.ValidateNodeGroup(i, ng); err != nil {
return err
}

0 comments on commit 6347076

Please sign in to comment.
You can’t perform that action at this time.