Skip to content

Commit

Permalink
move unmanaged nodegroup summaries to pkg/actions/nodegroup (eksctl-i…
Browse files Browse the repository at this point in the history
…o#4855)

* move unmanaged nodegroup summaries to pkg/actions/nodegroup

* refactors

* Update pkg/actions/nodegroup/get.go

Co-authored-by: Gergely Brautigam <[email protected]>

* error when creating managed bottlerocket GPU instances (eksctl-io#4866)

* error when creating managed bottlerocket GPU instances

* Update pkg/apis/eksctl.io/v1alpha5/validation.go

Co-authored-by: Chetan Patwal <[email protected]>

Co-authored-by: Chetan Patwal <[email protected]>

* remove all errors.Wrap calls

* address PR comments

* bring back nodegrouptype check

Co-authored-by: Gergely Brautigam <[email protected]>
Co-authored-by: Chetan Patwal <[email protected]>
  • Loading branch information
3 people authored Mar 7, 2022
1 parent fca3cc5 commit 9407fe3
Show file tree
Hide file tree
Showing 8 changed files with 599 additions and 611 deletions.
9 changes: 4 additions & 5 deletions integration/matchers/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ package matchers
import (
"github.com/onsi/gomega/types"
"github.com/pkg/errors"

"github.com/weaveworks/eksctl/pkg/cfn/manager"
"github.com/weaveworks/eksctl/pkg/actions/nodegroup"

"encoding/json"
)
Expand All @@ -28,9 +27,9 @@ func (matcher *jsonNodeGroupMatcher) Match(actual interface{}) (success bool, er
if !ok {
return false, errors.Wrapf(err, "BeNodeGroupsWithNamesWhich matcher expects a string")
}
ngSummaries := []manager.NodeGroupSummary{}
ngSummaries := []nodegroup.Summary{}
if err := json.Unmarshal([]byte(rawJSON), &ngSummaries); err != nil {
return false, errors.Wrapf(err, "BeNodeGroupsWithNamesWhich matcher expects a NodeGroupSummary JSON array")
return false, errors.Wrapf(err, "BeNodeGroupsWithNamesWhich matcher expects a Summary JSON array")
}
ngNames := extractNames(ngSummaries)
for _, m := range matcher.matchers {
Expand All @@ -43,7 +42,7 @@ func (matcher *jsonNodeGroupMatcher) Match(actual interface{}) (success bool, er
return true, nil
}

func extractNames(ngSummaries []manager.NodeGroupSummary) []string {
func extractNames(ngSummaries []nodegroup.Summary) []string {
ngNames := make([]string, len(ngSummaries))
for i, ngSummary := range ngSummaries {
ngNames[i] = ngSummary.Name
Expand Down
249 changes: 223 additions & 26 deletions pkg/actions/nodegroup/get.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package nodegroup

import (
"fmt"
"strconv"
"strings"
"time"

cfn "github.com/aws/aws-sdk-go/service/cloudformation"
"github.com/tidwall/gjson"
api "github.com/weaveworks/eksctl/pkg/apis/eksctl.io/v1alpha5"

"github.com/aws/aws-sdk-go/aws"
Expand All @@ -12,27 +16,64 @@ import (
"github.com/aws/aws-sdk-go/service/eks"
awseks "github.com/aws/aws-sdk-go/service/eks"
"github.com/kris-nova/logger"
"github.com/pkg/errors"

"github.com/weaveworks/eksctl/pkg/cfn/manager"
"github.com/weaveworks/eksctl/pkg/cfn/outputs"
kubewrapper "github.com/weaveworks/eksctl/pkg/kubernetes"
)

func (m *Manager) GetAll() ([]*manager.NodeGroupSummary, error) {
summaries, err := m.stackManager.GetUnmanagedNodeGroupSummaries("")
const (
imageIDPath = "Resources.NodeGroupLaunchTemplate.Properties.LaunchTemplateData.ImageId"
resourcesRootPath = "Resources"
)

// Summary represents a summary of a nodegroup stack
type Summary struct {
StackName string
Cluster string
Name string
Status string
MaxSize int
MinSize int
DesiredCapacity int
InstanceType string
ImageID string
CreationTime time.Time
NodeInstanceRoleARN string
AutoScalingGroupName string
Version string
NodeGroupType api.NodeGroupType `json:"Type"`
}

func (m *Manager) GetAll() ([]*Summary, error) {
unmanagedSummaries, err := m.getUnmanagedSummaries()
if err != nil {
return nil, errors.Wrap(err, "getting nodegroup stack summaries")
return nil, err
}

for _, summary := range summaries {
if summary.DesiredCapacity > 0 {
summary.Version, err = kubewrapper.GetNodegroupKubernetesVersion(m.clientSet.CoreV1().Nodes(), summary.Name)
if err != nil {
return nil, errors.Wrap(err, "getting nodegroup's kubernetes version")
}
}
managedSummaries, err := m.getManagedSummaries()
if err != nil {
return nil, err
}

return append(unmanagedSummaries, managedSummaries...), nil
}

func (m *Manager) Get(name string) (*Summary, error) {
summary, err := m.getUnmanagedSummary(name)
if err != nil {
return nil, fmt.Errorf("getting nodegroup stack summaries: %w", err)
}

if summary != nil {
return summary, nil
}

return m.getManagedSummary(name)
}

func (m *Manager) getManagedSummaries() ([]*Summary, error) {
var summaries []*Summary
managedNodeGroups, err := m.ctl.Provider.EKS().ListNodegroups(&eks.ListNodegroupsInput{
ClusterName: aws.String(m.cfg.Metadata.Name),
})
Expand All @@ -47,7 +88,7 @@ func (m *Manager) GetAll() ([]*manager.NodeGroupSummary, error) {
stack = &cloudformation.Stack{}
}

summary, err := m.makeManagedNGSummary(*ngName)
summary, err := m.getManagedSummary(*ngName)
if err != nil {
return nil, err
}
Expand All @@ -58,27 +99,183 @@ func (m *Manager) GetAll() ([]*manager.NodeGroupSummary, error) {
return summaries, nil
}

func (m *Manager) Get(name string) (*manager.NodeGroupSummary, error) {
summaries, err := m.stackManager.GetUnmanagedNodeGroupSummaries(name)
func (m *Manager) getUnmanagedSummaries() ([]*Summary, error) {
stacks, err := m.stackManager.DescribeNodeGroupStacks()
if err != nil {
return nil, fmt.Errorf("getting nodegroup stacks: %w", err)
}

// Create an empty array here so that an object is returned rather than null
summaries := make([]*Summary, 0)
for _, s := range stacks {
summary, err := m.unmanagedStackToSummary(s)
if err != nil {
return nil, err
}
if summary != nil {
summaries = append(summaries, summary)
}
}

return summaries, nil
}

func (m *Manager) getUnmanagedSummary(name string) (*Summary, error) {
stack, err := m.stackManager.DescribeNodeGroupStack(name)
if err != nil {
return nil, err
}

return m.unmanagedStackToSummary(stack)
}

func (m *Manager) unmanagedStackToSummary(s *manager.Stack) (*Summary, error) {
nodeGroupType, err := manager.GetNodeGroupType(s.Tags)
if err != nil {
return nil, err
}

if nodeGroupType != api.NodeGroupTypeUnmanaged {
return nil, nil
}

ngPaths, err := getNodeGroupPaths(s.Tags)
if err != nil {
return nil, err
}

summary, err := m.mapStackToNodeGroupSummary(s, ngPaths)

if err != nil {
return nil, fmt.Errorf("mapping stack to nodegroup summary: %w", err)
}
summary.NodeGroupType = api.NodeGroupTypeUnmanaged

asgName, err := m.stackManager.GetUnmanagedNodeGroupAutoScalingGroupName(s)
if err != nil {
return nil, fmt.Errorf("getting autoscalinggroupname: %w", err)
}

summary.AutoScalingGroupName = asgName

scalingGroup, err := m.stackManager.GetAutoScalingGroupDesiredCapacity(asgName)
if err != nil {
return nil, fmt.Errorf("getting autoscalinggroup desired capacity: %w", err)
}
summary.DesiredCapacity = int(*scalingGroup.DesiredCapacity)
summary.MinSize = int(*scalingGroup.MinSize)
summary.MaxSize = int(*scalingGroup.MaxSize)

if summary.DesiredCapacity > 0 {
summary.Version, err = kubewrapper.GetNodegroupKubernetesVersion(m.clientSet.CoreV1().Nodes(), summary.Name)
if err != nil {
return nil, fmt.Errorf("getting nodegroup's kubernetes version: %w", err)
}
}

return summary, nil
}

func getNodeGroupPaths(tags []*cfn.Tag) (*nodeGroupPaths, error) {
nodeGroupType, err := manager.GetNodeGroupType(tags)
if err != nil {
return nil, errors.Wrap(err, "getting nodegroup stack summaries")
return nil, err
}

if len(summaries) > 0 {
s := summaries[0]
if s.DesiredCapacity > 0 {
s.Version, err = kubewrapper.GetNodegroupKubernetesVersion(m.clientSet.CoreV1().Nodes(), s.Name)
if err != nil {
return nil, errors.Wrap(err, "getting nodegroup's kubernetes version")
}
switch nodeGroupType {
case api.NodeGroupTypeManaged:
makePath := func(fieldPath string) string {
return fmt.Sprintf("%s.ManagedNodeGroup.Properties.%s", resourcesRootPath, fieldPath)
}
return s, nil
makeScalingPath := func(field string) string {
return makePath(fmt.Sprintf("ScalingConfig.%s", field))
}
return &nodeGroupPaths{
InstanceType: makePath("InstanceTypes.0"),
DesiredCapacity: makeScalingPath("DesiredSize"),
MinSize: makeScalingPath("MinSize"),
MaxSize: makeScalingPath("MaxSize"),
}, nil

// Tag may not exist for existing nodegroups
case api.NodeGroupTypeUnmanaged, "":
makePath := func(field string) string {
return fmt.Sprintf("%s.NodeGroup.Properties.%s", resourcesRootPath, field)
}
return &nodeGroupPaths{
InstanceType: resourcesRootPath + ".NodeGroupLaunchTemplate.Properties.LaunchTemplateData.InstanceType",
DesiredCapacity: makePath("DesiredCapacity"),
MinSize: makePath("MinSize"),
MaxSize: makePath("MaxSize"),
}, nil

default:
return nil, fmt.Errorf("unexpected nodegroup type tag: %q", nodeGroupType)
}

return m.makeManagedNGSummary(name)
}

func (m *Manager) makeManagedNGSummary(nodeGroupName string) (*manager.NodeGroupSummary, error) {
type nodeGroupPaths struct {
InstanceType string
DesiredCapacity string
MinSize string
MaxSize string
}

func (m *Manager) mapStackToNodeGroupSummary(stack *manager.Stack, ngPaths *nodeGroupPaths) (*Summary, error) {
template, err := m.stackManager.GetStackTemplate(*stack.StackName)
if err != nil {
return nil, fmt.Errorf("error getting CloudFormation template for stack %s: %w", *stack.StackName, err)
}

summary := &Summary{
StackName: *stack.StackName,
Cluster: getClusterNameTag(stack),
Name: m.stackManager.GetNodeGroupName(stack),
Status: *stack.StackStatus,
MaxSize: int(gjson.Get(template, ngPaths.MaxSize).Int()),
MinSize: int(gjson.Get(template, ngPaths.MinSize).Int()),
DesiredCapacity: int(gjson.Get(template, ngPaths.DesiredCapacity).Int()),
InstanceType: gjson.Get(template, ngPaths.InstanceType).String(),
ImageID: gjson.Get(template, imageIDPath).String(),
CreationTime: *stack.CreationTime,
}

nodeGroupType, err := manager.GetNodeGroupType(stack.Tags)
if err != nil {
return nil, err
}

var nodeInstanceRoleARN string
if nodeGroupType == api.NodeGroupTypeUnmanaged {
nodeInstanceRoleARNCollector := func(s string) error {
nodeInstanceRoleARN = s
return nil
}
collectors := map[string]outputs.Collector{
outputs.NodeGroupInstanceRoleARN: nodeInstanceRoleARNCollector,
}
collectorSet := outputs.NewCollectorSet(collectors)
if err := collectorSet.MustCollect(*stack); err != nil {
logger.Warning(fmt.Errorf("error collecting Cloudformation outputs for stack %s: %w", *stack.StackName, err).Error())
}
}

summary.NodeInstanceRoleARN = nodeInstanceRoleARN

return summary, nil
}

func getClusterNameTag(s *manager.Stack) string {
for _, tag := range s.Tags {
if *tag.Key == api.ClusterNameTag || *tag.Key == api.OldClusterNameTag {
return *tag.Value
}
}
return ""
}

func (m *Manager) getManagedSummary(nodeGroupName string) (*Summary, error) {
describeOutput, err := m.ctl.Provider.EKS().DescribeNodegroup(&eks.DescribeNodegroupInput{
ClusterName: aws.String(m.cfg.Metadata.Name),
NodegroupName: aws.String(nodeGroupName),
Expand All @@ -105,7 +302,7 @@ func (m *Manager) makeManagedNGSummary(nodeGroupName string) (*manager.NodeGroup
imageID = *ng.AmiType
}

return &manager.NodeGroupSummary{
return &Summary{
Name: *ng.NodegroupName,
Cluster: *ng.ClusterName,
Status: *ng.Status,
Expand Down
Loading

0 comments on commit 9407fe3

Please sign in to comment.