Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/kubectl-datadog/autoscaling/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions"

"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/uninstall"
)

// options provides information required by cluster command
Expand All @@ -31,6 +32,7 @@ func New(streams genericclioptions.IOStreams) *cobra.Command {
}

cmd.AddCommand(install.New(streams))
cmd.AddCommand(uninstall.New(streams))

o := newOptions(streams)
o.configFlags.AddFlags(cmd.Flags())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,50 @@ func EnsureAwsAuthRole(ctx context.Context, clientset kubernetes.Interface, role

return nil
}

func RemoveAwsAuthRole(ctx context.Context, clientset kubernetes.Interface, roleArn string) error {
cm, err := clientset.CoreV1().ConfigMaps("kube-system").Get(ctx, "aws-auth", metav1.GetOptions{})
if err != nil {
return fmt.Errorf("failed to get aws-auth ConfigMap: %w", err)
}

var roles []RoleMapping
if mapRoles, ok := cm.Data["mapRoles"]; ok {
if err = yaml.Unmarshal([]byte(mapRoles), &roles); err != nil {
return fmt.Errorf("failed to parse mapRoles: %w", err)
}
} else {
log.Printf("No mapRoles found in aws-auth ConfigMap, skipping role removal.")
return nil
}

found := false
updatedRoles := make([]RoleMapping, 0, len(roles))
for _, role := range roles {
if role.RoleArn == roleArn {
found = true
continue
}
updatedRoles = append(updatedRoles, role)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could use slices.DeleteFunc:

oldLen := len(roles)
roles = slices.DeleteFunc(roles, func(role RoleMapping) bool { return role.RoleArn == roleArn })
found := oldLen != len(roles)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


if !found {
log.Printf("Role %s not found in aws-auth ConfigMap, skipping removal.", roleArn)
return nil
}

updated, err := yaml.Marshal(updatedRoles)
if err != nil {
return fmt.Errorf("failed to marshal updated mapRoles: %w", err)
}

cm.Data["mapRoles"] = string(updated)

if _, err := clientset.CoreV1().ConfigMaps("kube-system").Update(ctx, cm, metav1.UpdateOptions{}); err != nil {
return fmt.Errorf("failed to update aws-auth ConfigMap: %w", err)
}

log.Printf("Removed role %s from aws-auth ConfigMap.", roleArn)

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,48 @@ func updateStack(ctx context.Context, client *cloudformation.Client, stackName s
return nil
}

func DeleteStack(ctx context.Context, client *cloudformation.Client, stackName string) error {
exist, err := doesStackExist(ctx, client, stackName)
if err != nil {
return err
}

if !exist {
log.Printf("Stack %s does not exist, skipping deletion.", stackName)
return nil
}

log.Printf("Deleting stack %s…", stackName)

_, err = client.DeleteStack(
ctx,
&cloudformation.DeleteStackInput{
StackName: aws.String(stackName),
},
)
if err != nil {
return fmt.Errorf("failed to delete stack %s: %w", stackName, err)
}

waiter := cloudformation.NewStackDeleteCompleteWaiter(client)
if err := waiter.Wait(
ctx,
&cloudformation.DescribeStacksInput{
StackName: aws.String(stackName),
},
maxWaitDuration,
); err != nil {
log.Printf("Failed to delete stack %s.", stackName)
describeStack(ctx, client, stackName)

return fmt.Errorf("failed to wait for stack %s deletion: %w", stackName, err)
}

log.Printf("Deleted stack %s.", stackName)

return nil
}

func describeStack(ctx context.Context, client *cloudformation.Client, stackName string) error {
out, err := client.DescribeStacks(
ctx,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,30 @@ func upgrade(ctx context.Context, ac *action.Configuration, releaseName, namespa

return nil
}

func Uninstall(ctx context.Context, ac *action.Configuration, releaseName string) error {
exist, err := doesExist(ctx, ac, releaseName)
if err != nil {
return err
}

if !exist {
log.Printf("Helm release %s does not exist, skipping uninstallation.", releaseName)
return nil
}

log.Printf("Uninstalling Helm release %s…", releaseName)

uninstallAction := action.NewUninstall(ac)
uninstallAction.Wait = true
uninstallAction.Timeout = 30 * time.Minute

response, err := uninstallAction.Run(releaseName)
if err != nil {
return fmt.Errorf("failed to uninstall Helm release %s: %w", releaseName, err)
}

log.Printf("Uninstalled Helm release %s.", response.Release.Name)

return nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)

func createOrUpdate(ctx context.Context, cli client.Client, object client.Object) error {
func CreateOrUpdate(ctx context.Context, cli client.Client, object client.Object) error {
resourceVersion, err := getResourceVersion(ctx, cli, object)
if err != nil {
return err
Expand Down Expand Up @@ -58,3 +58,59 @@ func update(ctx context.Context, cli client.Client, object client.Object) error

return nil
}

func Delete(ctx context.Context, cli client.Client, object client.Object) error {
log.Printf("Deleting %s %s…", object.GetObjectKind().GroupVersionKind().Kind, object.GetName())

if err := cli.Delete(ctx, object); err != nil {
if apierrors.IsNotFound(err) {
log.Printf("%s %s not found, skipping deletion.", object.GetObjectKind().GroupVersionKind().Kind, object.GetName())
return nil
}
Comment on lines +66 to +69
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a good idea to mask 404? E.g. update bubbles it up.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is inside the Delete function the goal of which is to ensure that the given object is eventually not existing anymore.
If the given object cannot be found, it is already not existing, then the system is already in the expected state.
The promise of the Delete function, which is: “The object will eventually be deleted” is already kept.
Such a situation shouldn’t be considered as an error.

The question might then be: why would we try to delete an object that doesn’t exist?
Well, this might happen if a user tries to manually delete some objects concurrently to the execution of this script.
One could argue that it isn’t a good idea to do manual actions that might conflict with a running script.
But if we can handle this case gracefully, that’s still better.

return fmt.Errorf("failed to delete %s %s: %w", object.GetObjectKind().GroupVersionKind().Kind, object.GetName(), err)
}

log.Printf("Deleted %s %s.", object.GetObjectKind().GroupVersionKind().Kind, object.GetName())

return nil
}

func DeleteAllWithLabel(ctx context.Context, cli client.Client, list client.ObjectList, labelSelector client.MatchingLabels) error {
gvk := list.GetObjectKind().GroupVersionKind()
kind := gvk.Kind

log.Printf("Listing %s resources with labels %v…", kind, labelSelector)

if err := cli.List(ctx, list, labelSelector); err != nil {
return fmt.Errorf("failed to list %s resources: %w", kind, err)
}

items, err := extractListItems(list)
if err != nil {
return err
}

if len(items) == 0 {
log.Printf("No %s resources found with labels %v, skipping deletion.", kind, labelSelector)
return nil
}

log.Printf("Found %d %s resource(s) to delete.", len(items), kind)

for _, item := range items {
if err := Delete(ctx, cli, item); err != nil {
return err
}
}

return nil
}

func extractListItems(list client.ObjectList) ([]client.Object, error) {
switch v := list.(type) {
case interface{ GetItems() []client.Object }:
return v.GetItems(), nil
default:
return nil, fmt.Errorf("unsupported list type: %T", list)
}
}
48 changes: 27 additions & 21 deletions cmd/kubectl-datadog/autoscaling/cluster/install/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"

"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/aws"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/aws"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/helm"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/guess"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/helm"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/k8s"
"github.com/DataDog/datadog-operator/pkg/plugin/common"
"github.com/DataDog/datadog-operator/pkg/version"
Expand Down Expand Up @@ -222,6 +224,7 @@ func (o *options) run(cmd *cobra.Command) error {
defer stop()

log.SetOutput(cmd.OutOrStderr())
ctrl.SetLogger(zap.New(zap.UseDevMode(false), zap.WriteTo(cmd.ErrOrStderr())))

if clusterName == "" {
if name, err := o.getClusterNameFromKubeconfig(ctx); err != nil {
Expand Down Expand Up @@ -378,25 +381,28 @@ func updateAwsAuthConfigMap(ctx context.Context, clients *clients, clusterName s
return fmt.Errorf("failed to check if aws-auth ConfigMap is present: %w", err)
}

if awsAuthConfigMapPresent {
// Get AWS account ID
callerIdentity, err := clients.sts.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
if err != nil {
return fmt.Errorf("failed to get identity caller: %w", err)
}
if callerIdentity.Account == nil {
return errors.New("unable to determine AWS account ID from STS GetCallerIdentity")
}
accountID := *callerIdentity.Account

// Add role mapping in the `aws-auth` ConfigMap
if err = aws.EnsureAwsAuthRole(ctx, clients.k8sClientset, aws.RoleMapping{
RoleArn: "arn:aws:iam::" + accountID + ":role/KarpenterNodeRole-" + clusterName,
Username: "system:node:{{EC2PrivateDNSName}}",
Groups: []string{"system:bootstrappers", "system:nodes"},
}); err != nil {
return fmt.Errorf("failed to update aws-auth ConfigMap: %w", err)
}
if !awsAuthConfigMapPresent {
log.Println("aws-auth ConfigMap not present, skipping role addition.")
return nil
}

// Get AWS account ID
callerIdentity, err := clients.sts.GetCallerIdentity(ctx, &sts.GetCallerIdentityInput{})
if err != nil {
return fmt.Errorf("failed to get identity caller: %w", err)
}
if callerIdentity.Account == nil {
return errors.New("unable to determine AWS account ID from STS GetCallerIdentity")
}
accountID := *callerIdentity.Account

// Add role mapping in the `aws-auth` ConfigMap
if err = aws.EnsureAwsAuthRole(ctx, clients.k8sClientset, aws.RoleMapping{
RoleArn: "arn:aws:iam::" + accountID + ":role/KarpenterNodeRole-" + clusterName,
Username: "system:node:{{EC2PrivateDNSName}}",
Groups: []string{"system:bootstrappers", "system:nodes"},
}); err != nil {
return fmt.Errorf("failed to update aws-auth ConfigMap: %w", err)
}

return nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

commonk8s "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/k8s"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/guess"
"github.com/DataDog/datadog-operator/pkg/version"
)
Expand Down Expand Up @@ -38,7 +39,7 @@ func CreateOrUpdateEC2NodeClass(ctx context.Context, client client.Client, clust
}
}

return createOrUpdate(ctx, client, &karpawsv1.EC2NodeClass{
return commonk8s.CreateOrUpdate(ctx, client, &karpawsv1.EC2NodeClass{
TypeMeta: metav1.TypeMeta{
APIVersion: "karpenter.k8s.aws/v1",
Kind: "EC2NodeClass",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
karpv1 "sigs.k8s.io/karpenter/pkg/apis/v1"

commonk8s "github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/common/k8s"
"github.com/DataDog/datadog-operator/cmd/kubectl-datadog/autoscaling/cluster/install/guess"
"github.com/DataDog/datadog-operator/pkg/version"
)
Expand Down Expand Up @@ -55,7 +56,7 @@ func CreateOrUpdateNodePool(ctx context.Context, client client.Client, np guess.
})
}

return createOrUpdate(ctx, client, &karpv1.NodePool{
return commonk8s.CreateOrUpdate(ctx, client, &karpv1.NodePool{
TypeMeta: metav1.TypeMeta{
APIVersion: "karpenter.sh/v1",
Kind: "NodePool",
Expand Down
Loading
Loading