internal/agentdiag.go (85 lines of code) (raw):
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package internal
import (
"errors"
"fmt"
"time"
"github.com/elastic/eck-diagnostics/internal/archive"
"github.com/elastic/eck-diagnostics/internal/extraction"
internal_filters "github.com/elastic/eck-diagnostics/internal/filters"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/version"
"k8s.io/cli-runtime/pkg/resource"
)
// based on eck-operator code the agent container name is constant "agent"
const agentContainerName = "agent"
func runAgentDiagnostics(k *Kubectl, ns string, zipFile *archive.ZipFile, verbose bool, stopCh chan struct{}, filters internal_filters.Filters) {
outputFile := time.Now().Format("eck-agent-diag-2006-01-02T15-04-05Z.zip")
resources, err := k.getResourcesMatching("pod", ns, "common.k8s.elastic.co/type=agent")
if err != nil {
zipFile.AddError(err)
return // unrecoverable let's return
}
if err := resources.Visit(func(info *resource.Info, _ error) error {
select {
case <-stopCh:
return errors.New("aborting Elastic Agent diagnostic")
default:
// continue processing agents
}
resourceName := info.Name
labels, err := meta.NewAccessor().Labels(info.Object)
if err != nil {
zipFile.AddError(fmt.Errorf("while accessing labels for %s/%s: %w", ns, resourceName, err))
return nil
}
v, found := labels["agent.k8s.elastic.co/version"]
if !found || v == "" {
logger.Printf("Skipping %s/%s as it has no version", ns, resourceName)
return nil
}
ver, err := version.ParseSemantic(v)
if err != nil {
zipFile.AddError(err)
return nil
}
if ver.LessThan(version.MustParseSemantic("7.16.0")) {
logger.Printf("Skipping %s/%s as it is below min version of 7.16.0", ns, resourceName)
return nil
}
if !filters.Matches(labels) {
return nil
}
nsn := types.NamespacedName{Namespace: ns, Name: resourceName}
needsCleanup := diagnosticForAgentPod(nsn, k, outputFile, zipFile, verbose)
// no matter what happened: try to clean up the diagnostic archive in the agent container
if err := k.Exec(nsn, agentContainerName, "rm", outputFile); err != nil {
// but only report any errors during cleaning up if there is a likelihood that we created an archive to clean up
// in the first place
if needsCleanup {
zipFile.AddError(fmt.Errorf("while cleaning up agent container %s: %w", nsn, err))
}
}
return nil
}); err != nil {
zipFile.AddError(err)
}
}
// diagnosticForAgentPod runs the diagnostic sub command in the agent container identified by nsn. Returns a boolean indicating
// whether the diagnostic command has run and there is a diagnostic archive in the container to clean up after to avoid filling up
// the containers file system.
func diagnosticForAgentPod(nsn types.NamespacedName, k *Kubectl, outputFile string, zipFile *archive.ZipFile, verbose bool) bool {
logger.Printf("Extracting agent diagnostics for %s", nsn)
if err := k.Exec(nsn, agentContainerName, "elastic-agent", "diagnostics", "collect", "-f", outputFile); err != nil {
zipFile.AddError(fmt.Errorf("while extracting agent diagnostics: %w", err))
return false
}
reader, err := k.Copy(nsn, agentContainerName, outputFile, zipFile.AddError)
if err != nil {
zipFile.AddError(err)
return true
}
source := extraction.RemoteSource{
Namespace: nsn.Namespace,
PodName: nsn.Name, // no separate diagnostic Pod in this case
Typ: "agent",
ResourceName: nsn.Name,
PodOutputDir: "/",
}
if err := extraction.UntarIntoZip(reader, source, zipFile, verbose); err != nil {
zipFile.AddError(fmt.Errorf("while copying diagnostic data from Pod %s into diagnostic archive: %w", nsn, err))
}
return true
}