Add HookOutputFunc and generic yaml unmarshaller

Signed-off-by: Chris Berry <bez625@gmail.com>
pull/10309/head
Chris Berry 7 months ago
parent 52ac92fb69
commit 6d30fa5990

@ -19,6 +19,8 @@ package action
import (
"bytes"
"fmt"
"io"
"log"
"os"
"path"
"path/filepath"
@ -95,6 +97,9 @@ type Configuration struct {
Capabilities *chartutil.Capabilities
Log func(string, ...interface{})
// HookOutputFunc Called with container name and returns and expects writer that will receive the log output
HookOutputFunc func(namespace, pod, container string) io.Writer
}
// renderResources renders the templates in a chart
@ -122,7 +127,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
var err2 error
// A `helm template` should not talk to the remote cluster. However, commands with the flag
//`--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster.
// `--dry-run` with the value of `false`, `none`, or `server` should try to interact with the cluster.
// It may break in interesting and exotic ways because other data (e.g. discovery) is mocked.
if interactWithRemote && cfg.RESTClientGetter != nil {
restConfig, err := cfg.RESTClientGetter.ToRESTConfig()
@ -422,6 +427,12 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp
cfg.KubeClient = kc
cfg.Releases = store
cfg.Log = log
cfg.HookOutputFunc = defaultHookOutputWriter
return nil
}
// defaultHookOutputWriter will write the Hook logs to log.Writer().
func defaultHookOutputWriter(_, _, _ string) io.Writer {
return log.Writer()
}

@ -19,16 +19,16 @@ import (
"bytes"
"fmt"
"log"
"slices"
"sort"
"time"
"helm.sh/helm/v4/pkg/kube"
"helm.sh/helm/v4/pkg/chartutil"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"helm.sh/helm/v4/pkg/release"
helmtime "helm.sh/helm/v4/pkg/time"
@ -179,22 +179,20 @@ func hookHasDeletePolicy(h *release.Hook, policy release.HookDeletePolicy) bool
// outputLogsByPolicy outputs a pods logs if the hook policy instructs it to
func (cfg *Configuration) outputLogsByPolicy(h *release.Hook, releaseNamespace string, policy release.HookOutputLogPolicy) error {
if !hookHasOutputLogPolicy(h, policy) {
return nil
return nil
}
namespace, err := cfg.deriveNamespace(h, releaseNamespace)
if err != nil {
return err
}
switch h.Kind {
case "Job":
return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)})
case "Pod":
return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)})
default:
return nil
}
namespace, err := cfg.deriveNamespace(h, releaseNamespace)
if err != nil {
return err
}
switch h.Kind {
case "Job":
return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{LabelSelector: fmt.Sprintf("job-name=%s", h.Name)})
case "Pod":
return cfg.outputContainerLogsForListOptions(namespace, metav1.ListOptions{FieldSelector: fmt.Sprintf("metadata.name=%s", h.Name)})
default:
return nil
}
return nil
}
func (cfg *Configuration) outputContainerLogsForListOptions(namespace string, listOptions metav1.ListOptions) error {
@ -204,35 +202,30 @@ func (cfg *Configuration) outputContainerLogsForListOptions(namespace string, li
if err != nil {
return err
}
err = kubeClient.OutputContainerLogsForPodList(podList, namespace, log.Writer())
err = kubeClient.OutputContainerLogsForPodList(podList, namespace, cfg.HookOutputFunc)
return err
}
return nil
}
func (cfg *Configuration) deriveNamespace(h *release.Hook, namespace string) (string, error) {
values, err := chartutil.ReadValues([]byte(h.Manifest))
tmp := struct {
Metadata struct {
Namespace string
}
}{}
err := yaml.Unmarshal([]byte(h.Manifest), &tmp)
if err != nil {
return "", errors.Wrapf(err, "unable to parse kubernetes manifest for output logs hook %s", h.Path)
return "", errors.Wrapf(err, "unable to parse metadata.namespace from kubernetes manifest for output logs hook %s", h.Path)
}
value, err := values.PathValue("metadata.namespace")
switch err.(type) {
case nil:
return value.(string), nil
case chartutil.ErrNoValue:
if tmp.Metadata.Namespace == "" {
return namespace, nil
default:
return "", errors.Wrapf(err, "unable to parse path of metadata.namespace in yaml for output logs hook %s", h.Path)
}
return tmp.Metadata.Namespace, nil
}
// hookHasOutputLogPolicy determines whether the defined hook output log policy matches the hook output log policies
// supported by helm.
func hookHasOutputLogPolicy(h *release.Hook, policy release.HookOutputLogPolicy) bool {
for _, v := range h.OutputLogPolicies {
if policy == v {
return true
}
}
return false
return slices.Contains(h.OutputLogPolicies, policy)
}

@ -822,14 +822,14 @@ func (c *Client) GetPodList(namespace string, listOptions metav1.ListOptions) (*
}
// OutputContainerLogsForPodList is a helper that outputs logs for a list of pods
func (c *Client) OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writer io.Writer) error {
func (c *Client) OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error {
for _, pod := range podList.Items {
for _, container := range pod.Spec.Containers {
options := &v1.PodLogOptions{
Container: container.Name,
}
request := c.kubeClient.CoreV1().Pods(namespace).GetLogs(pod.Name, options)
err2 := copyRequestStreamToWriter(request, pod.Name, container.Name, writer)
err2 := copyRequestStreamToWriter(request, pod.Name, container.Name, writerFunc(namespace, pod.Name, container.Name))
if err2 != nil {
return err2
}

@ -711,7 +711,8 @@ func TestOutputContainerLogsForPodList(t *testing.T) {
kubeClient := k8sfake.NewSimpleClientset(&somePodList)
c := Client{Namespace: namespace, kubeClient: kubeClient}
outBuffer := &bytes.Buffer{}
err := c.OutputContainerLogsForPodList(&somePodList, namespace, outBuffer)
outBufferFunc := func(_, _, _ string) io.Writer { return outBuffer }
err := c.OutputContainerLogsForPodList(&somePodList, namespace, outBufferFunc)
clientAssertions := assert.New(t)
clientAssertions.NoError(err)
clientAssertions.Equal("fake logsfake logsfake logs", outBuffer.String())

@ -124,7 +124,7 @@ func (p *PrintingKubeClient) GetPodList(_ string, _ metav1.ListOptions) (*v1.Pod
}
// OutputContainerLogsForPodList implements KubeClient OutputContainerLogsForPodList.
func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNamespace string, _ io.Writer) error {
func (p *PrintingKubeClient) OutputContainerLogsForPodList(_ *v1.PodList, someNamespace string, _ func(namespace, pod, container string) io.Writer) error {
_, err := io.Copy(p.LogOutput, strings.NewReader(fmt.Sprintf("attempted to output logs for namespace: %s", someNamespace)))
return err
}

@ -84,7 +84,7 @@ type InterfaceLogs interface {
GetPodList(namespace string, listOptions metav1.ListOptions) (*v1.PodList, error)
// OutputContainerLogsForPodList output the logs for a pod list
OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writer io.Writer) error
OutputContainerLogsForPodList(podList *v1.PodList, namespace string, writerFunc func(namespace, pod, container string) io.Writer) error
}
// InterfaceDeletionPropagation is introduced to avoid breaking backwards compatibility for Interface implementers.

Loading…
Cancel
Save