updated vendor
This commit is contained in:
-125
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package logging
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// DebugEnabledFunc is a function type that determines if debug logging is enabled
|
||||
// We use a function because we want to check the setting at log time, not when the logger is created
|
||||
type DebugEnabledFunc func() bool
|
||||
|
||||
// DebugCheckHandler checks settings.Debug at log time
|
||||
type DebugCheckHandler struct {
|
||||
handler slog.Handler
|
||||
debugEnabled DebugEnabledFunc
|
||||
}
|
||||
|
||||
// Enabled implements slog.Handler.Enabled
|
||||
func (h *DebugCheckHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
if level == slog.LevelDebug {
|
||||
if h.debugEnabled == nil {
|
||||
return false
|
||||
}
|
||||
return h.debugEnabled()
|
||||
}
|
||||
return true // Always log other levels
|
||||
}
|
||||
|
||||
// Handle implements slog.Handler.Handle
|
||||
func (h *DebugCheckHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
return h.handler.Handle(ctx, r)
|
||||
}
|
||||
|
||||
// WithAttrs implements slog.Handler.WithAttrs
|
||||
func (h *DebugCheckHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &DebugCheckHandler{
|
||||
handler: h.handler.WithAttrs(attrs),
|
||||
debugEnabled: h.debugEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
// WithGroup implements slog.Handler.WithGroup
|
||||
func (h *DebugCheckHandler) WithGroup(name string) slog.Handler {
|
||||
return &DebugCheckHandler{
|
||||
handler: h.handler.WithGroup(name),
|
||||
debugEnabled: h.debugEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
// NewLogger creates a new logger with dynamic debug checking
|
||||
func NewLogger(debugEnabled DebugEnabledFunc) *slog.Logger {
|
||||
// Create base handler that removes timestamps
|
||||
baseHandler := slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{
|
||||
// Always use LevelDebug here to allow all messages through
|
||||
// Our custom handler will do the filtering
|
||||
Level: slog.LevelDebug,
|
||||
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
|
||||
// Remove the time attribute
|
||||
if a.Key == slog.TimeKey {
|
||||
return slog.Attr{}
|
||||
}
|
||||
return a
|
||||
},
|
||||
})
|
||||
|
||||
// Wrap with our dynamic debug-checking handler
|
||||
dynamicHandler := &DebugCheckHandler{
|
||||
handler: baseHandler,
|
||||
debugEnabled: debugEnabled,
|
||||
}
|
||||
|
||||
return slog.New(dynamicHandler)
|
||||
}
|
||||
|
||||
// LoggerSetterGetter is an interface that can set and get a logger
|
||||
type LoggerSetterGetter interface {
|
||||
// SetLogger sets a new slog.Handler
|
||||
SetLogger(newHandler slog.Handler)
|
||||
// Logger returns the slog.Logger created from the slog.Handler
|
||||
Logger() *slog.Logger
|
||||
}
|
||||
|
||||
type LogHolder struct {
|
||||
// logger is an atomic.Pointer[slog.Logger] to store the slog.Logger
|
||||
// We use atomic.Pointer for thread safety
|
||||
logger atomic.Pointer[slog.Logger]
|
||||
}
|
||||
|
||||
// Logger returns the logger for the LogHolder. If nil, returns slog.Default().
|
||||
func (l *LogHolder) Logger() *slog.Logger {
|
||||
if lg := l.logger.Load(); lg != nil {
|
||||
return lg
|
||||
}
|
||||
return slog.New(slog.DiscardHandler) // Should never be reached
|
||||
}
|
||||
|
||||
// SetLogger sets the logger for the LogHolder. If nil, sets the default logger.
|
||||
func (l *LogHolder) SetLogger(newHandler slog.Handler) {
|
||||
if newHandler == nil {
|
||||
l.logger.Store(slog.New(slog.DiscardHandler)) // Assume nil as discarding logs
|
||||
return
|
||||
}
|
||||
l.logger.Store(slog.New(newHandler))
|
||||
}
|
||||
|
||||
// Ensure LogHolder implements LoggerSetterGetter
|
||||
var _ LoggerSetterGetter = &LogHolder{}
|
||||
+29
-8
@@ -19,6 +19,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
@@ -158,18 +159,27 @@ func LoadDir(dirname string) (Plugin, error) {
|
||||
return pm.CreatePlugin(dirname, m)
|
||||
}
|
||||
|
||||
// LoadAll loads all plugins found beneath the base directory.
|
||||
func LogIgnorePluginLoadErrorFilterFunc(pluginYAML string, err error) error {
|
||||
slog.Warn("failed to load plugin (ignoring)", slog.String("plugin_yaml", pluginYAML), slog.Any("error", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
// errorFilterFunc is a function that can filter errors during plugin loading
|
||||
type ErrorFilterFunc func(string, error) error
|
||||
|
||||
// LoadAllDir load all plugins found beneath the base directory, using the provided error filter to determine whether to fail on individual plugin load errors.
|
||||
//
|
||||
// This scans only one directory level.
|
||||
func LoadAll(basedir string) ([]Plugin, error) {
|
||||
var plugins []Plugin
|
||||
// We want basedir/*/plugin.yaml
|
||||
func LoadAllDir(basedir string, errorFilter ErrorFilterFunc) ([]Plugin, error) {
|
||||
// We want <basedir>/*/plugin.yaml
|
||||
scanpath := filepath.Join(basedir, "*", PluginFileName)
|
||||
matches, err := filepath.Glob(scanpath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to search for plugins in %q: %w", scanpath, err)
|
||||
}
|
||||
|
||||
plugins := make([]Plugin, 0, len(matches))
|
||||
|
||||
// empty dir should load
|
||||
if len(matches) == 0 {
|
||||
return plugins, nil
|
||||
@@ -179,9 +189,12 @@ func LoadAll(basedir string) ([]Plugin, error) {
|
||||
dir := filepath.Dir(yamlFile)
|
||||
p, err := LoadDir(dir)
|
||||
if err != nil {
|
||||
return plugins, err
|
||||
if errNew := errorFilter(yamlFile, err); errNew != nil {
|
||||
return plugins, errNew
|
||||
}
|
||||
} else {
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
plugins = append(plugins, p)
|
||||
}
|
||||
return plugins, detectDuplicates(plugins)
|
||||
}
|
||||
@@ -193,8 +206,12 @@ type findFunc func(pluginsDir string) ([]Plugin, error)
|
||||
type filterFunc func(Plugin) bool
|
||||
|
||||
// FindPlugins returns a list of plugins that match the descriptor
|
||||
// Errors loading a plugin are ignored with a warning
|
||||
func FindPlugins(pluginsDirs []string, descriptor Descriptor) ([]Plugin, error) {
|
||||
return findPlugins(pluginsDirs, LoadAll, makeDescriptorFilter(descriptor))
|
||||
loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) {
|
||||
return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc)
|
||||
}
|
||||
return findPlugins(pluginsDirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor))
|
||||
}
|
||||
|
||||
// findPlugins is the internal implementation that uses the find and filter functions
|
||||
@@ -237,7 +254,11 @@ func makeDescriptorFilter(descriptor Descriptor) filterFunc {
|
||||
|
||||
// FindPlugin returns a single plugin that matches the descriptor
|
||||
func FindPlugin(dirs []string, descriptor Descriptor) (Plugin, error) {
|
||||
plugins, err := FindPlugins(dirs, descriptor)
|
||||
loadAllIgnoreErrors := func(pluginsDir string) ([]Plugin, error) {
|
||||
return LoadAllDir(pluginsDir, LogIgnorePluginLoadErrorFilterFunc)
|
||||
}
|
||||
|
||||
plugins, err := findPlugins(dirs, loadAllIgnoreErrors, makeDescriptorFilter(descriptor))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+18
-5
@@ -19,9 +19,17 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
|
||||
"helm.sh/helm/v4/internal/plugin/schema"
|
||||
)
|
||||
|
||||
// isValidSemver checks if the given string is a valid semantic version
|
||||
func isValidSemver(v string) bool {
|
||||
_, err := semver.StrictNewVersion(v)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Metadata of a plugin, converted from the "on-disk" legacy or v1 plugin.yaml
|
||||
// Specifically, Config and RuntimeConfig are converted to their respective types based on the plugin type and runtime
|
||||
type Metadata struct {
|
||||
@@ -57,24 +65,29 @@ func (m Metadata) Validate() error {
|
||||
errs = append(errs, fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name))
|
||||
}
|
||||
|
||||
// Require version to be valid semver if specified
|
||||
if m.Version != "" && !isValidSemver(m.Version) {
|
||||
errs = append(errs, fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version))
|
||||
}
|
||||
|
||||
if m.APIVersion == "" {
|
||||
errs = append(errs, fmt.Errorf("empty APIVersion"))
|
||||
errs = append(errs, errors.New("empty APIVersion"))
|
||||
}
|
||||
|
||||
if m.Type == "" {
|
||||
errs = append(errs, fmt.Errorf("empty type field"))
|
||||
errs = append(errs, errors.New("empty type field"))
|
||||
}
|
||||
|
||||
if m.Runtime == "" {
|
||||
errs = append(errs, fmt.Errorf("empty runtime field"))
|
||||
errs = append(errs, errors.New("empty runtime field"))
|
||||
}
|
||||
|
||||
if m.Config == nil {
|
||||
errs = append(errs, fmt.Errorf("missing config field"))
|
||||
errs = append(errs, errors.New("missing config field"))
|
||||
}
|
||||
|
||||
if m.RuntimeConfig == nil {
|
||||
errs = append(errs, fmt.Errorf("missing runtimeConfig field"))
|
||||
errs = append(errs, errors.New("missing runtimeConfig field"))
|
||||
}
|
||||
|
||||
// Validate the config itself
|
||||
|
||||
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -71,14 +72,19 @@ func (m *MetadataLegacy) Validate() error {
|
||||
if !validPluginName.MatchString(m.Name) {
|
||||
return fmt.Errorf("invalid plugin name %q: must contain only a-z, A-Z, 0-9, _ and -", m.Name)
|
||||
}
|
||||
|
||||
if m.Version != "" && !isValidSemver(m.Version) {
|
||||
return fmt.Errorf("invalid plugin version %q: must be valid semver", m.Version)
|
||||
}
|
||||
|
||||
m.Usage = sanitizeString(m.Usage)
|
||||
|
||||
if len(m.PlatformCommand) > 0 && len(m.Command) > 0 {
|
||||
return fmt.Errorf("both platformCommand and command are set")
|
||||
return errors.New("both platformCommand and command are set")
|
||||
}
|
||||
|
||||
if len(m.PlatformHooks) > 0 && len(m.Hooks) > 0 {
|
||||
return fmt.Errorf("both platformHooks and hooks are set")
|
||||
return errors.New("both platformHooks and hooks are set")
|
||||
}
|
||||
|
||||
// Validate downloader plugins
|
||||
|
||||
+11
-3
@@ -16,6 +16,7 @@ limitations under the License.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
@@ -48,7 +49,14 @@ type MetadataV1 struct {
|
||||
|
||||
func (m *MetadataV1) Validate() error {
|
||||
if !validPluginName.MatchString(m.Name) {
|
||||
return fmt.Errorf("invalid plugin `name`")
|
||||
return errors.New("invalid plugin `name`")
|
||||
}
|
||||
|
||||
if m.Version == "" {
|
||||
return errors.New("plugin `version` is required")
|
||||
}
|
||||
if !isValidSemver(m.Version) {
|
||||
return fmt.Errorf("invalid plugin `version` %q: must be valid semver", m.Version)
|
||||
}
|
||||
|
||||
if m.APIVersion != "v1" {
|
||||
@@ -56,11 +64,11 @@ func (m *MetadataV1) Validate() error {
|
||||
}
|
||||
|
||||
if m.Type == "" {
|
||||
return fmt.Errorf("`type` missing")
|
||||
return errors.New("`type` missing")
|
||||
}
|
||||
|
||||
if m.Runtime == "" {
|
||||
return fmt.Errorf("`runtime` missing")
|
||||
return errors.New("`runtime` missing")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -117,8 +117,8 @@ func (r *SubprocessPluginRuntime) InvokeWithEnv(main string, argv []string, env
|
||||
cmd.Env = slices.Clone(os.Environ())
|
||||
cmd.Env = append(
|
||||
cmd.Env,
|
||||
fmt.Sprintf("HELM_PLUGIN_NAME=%s", r.metadata.Name),
|
||||
fmt.Sprintf("HELM_PLUGIN_DIR=%s", r.pluginDir))
|
||||
"HELM_PLUGIN_NAME="+r.metadata.Name,
|
||||
"HELM_PLUGIN_DIR="+r.pluginDir)
|
||||
cmd.Env = append(cmd.Env, env...)
|
||||
|
||||
cmd.Stdin = stdin
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"helm.sh/helm/v4/internal/plugin/schema"
|
||||
)
|
||||
@@ -63,7 +64,7 @@ func (r *SubprocessPluginRuntime) runGetter(input *Input) (*Output, error) {
|
||||
env["HELM_PLUGIN_DIR"] = r.pluginDir
|
||||
env["HELM_PLUGIN_USERNAME"] = msg.Options.Username
|
||||
env["HELM_PLUGIN_PASSWORD"] = msg.Options.Password
|
||||
env["HELM_PLUGIN_PASS_CREDENTIALS_ALL"] = fmt.Sprintf("%t", msg.Options.PassCredentialsAll)
|
||||
env["HELM_PLUGIN_PASS_CREDENTIALS_ALL"] = strconv.FormatBool(msg.Options.PassCredentialsAll)
|
||||
|
||||
command, args, err := PrepareCommands(d.PlatformCommand, false, []string{}, env)
|
||||
if err != nil {
|
||||
|
||||
+2
-1
@@ -14,6 +14,7 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
@@ -55,7 +56,7 @@ type ConfigGetterV1 struct {
|
||||
|
||||
func (c *ConfigGetterV1) Validate() error {
|
||||
if len(c.Protocols) == 0 {
|
||||
return fmt.Errorf("getter has no protocols")
|
||||
return errors.New("getter has no protocols")
|
||||
}
|
||||
for i, protocol := range c.Protocols {
|
||||
if protocol == "" {
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ProtonMail/go-crypto/openpgp/clearsign" //nolint
|
||||
"github.com/ProtonMail/go-crypto/openpgp/clearsign"
|
||||
|
||||
"helm.sh/helm/v4/pkg/helmpath"
|
||||
)
|
||||
|
||||
@@ -16,7 +16,7 @@ limitations under the License.
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
@@ -80,7 +80,7 @@ func getPlatformCommand(cmds []PlatformCommand) ([]string, []string) {
|
||||
func PrepareCommands(cmds []PlatformCommand, expandArgs bool, extraArgs []string, env map[string]string) (string, []string, error) {
|
||||
cmdParts, args := getPlatformCommand(cmds)
|
||||
if len(cmdParts) == 0 || cmdParts[0] == "" {
|
||||
return "", nil, fmt.Errorf("no plugin command is applicable")
|
||||
return "", nil, errors.New("no plugin command is applicable")
|
||||
}
|
||||
envMappingFunc := func(key string) string {
|
||||
return env[key]
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ func VerifyPlugin(archiveData, provData []byte, filename, keyring string) (*prov
|
||||
return sig.Verify(archiveData, provData, filename)
|
||||
}
|
||||
|
||||
// isTarball checks if a file has a tarball extension
|
||||
// IsTarball checks if a file has a tarball extension
|
||||
func IsTarball(filename string) bool {
|
||||
return filepath.Ext(filename) == ".gz" || filepath.Ext(filename) == ".tgz"
|
||||
}
|
||||
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
This file was initially copied and modified from
|
||||
https://github.com/fluxcd/kustomize-controller/blob/main/internal/statusreaders/job.go
|
||||
Copyright 2022 The Flux authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package statusreaders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
|
||||
"github.com/fluxcd/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
type customJobStatusReader struct {
|
||||
genericStatusReader engine.StatusReader
|
||||
}
|
||||
|
||||
func NewCustomJobStatusReader(mapper meta.RESTMapper) engine.StatusReader {
|
||||
genericStatusReader := statusreaders.NewGenericStatusReader(mapper, jobConditions)
|
||||
return &customJobStatusReader{
|
||||
genericStatusReader: genericStatusReader,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *customJobStatusReader) Supports(gk schema.GroupKind) bool {
|
||||
return gk == batchv1.SchemeGroupVersion.WithKind("Job").GroupKind()
|
||||
}
|
||||
|
||||
func (j *customJobStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) {
|
||||
return j.genericStatusReader.ReadStatus(ctx, reader, resource)
|
||||
}
|
||||
|
||||
func (j *customJobStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) {
|
||||
return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource)
|
||||
}
|
||||
|
||||
// Ref: https://github.com/kubernetes-sigs/cli-utils/blob/v0.29.4/pkg/kstatus/status/core.go
|
||||
// Modified to return Current status only when the Job has completed as opposed to when it's in progress.
|
||||
func jobConditions(u *unstructured.Unstructured) (*status.Result, error) {
|
||||
obj := u.UnstructuredContent()
|
||||
|
||||
parallelism := status.GetIntField(obj, ".spec.parallelism", 1)
|
||||
completions := status.GetIntField(obj, ".spec.completions", parallelism)
|
||||
succeeded := status.GetIntField(obj, ".status.succeeded", 0)
|
||||
failed := status.GetIntField(obj, ".status.failed", 0)
|
||||
|
||||
// Conditions
|
||||
// https://github.com/kubernetes/kubernetes/blob/master/pkg/controller/job/utils.go#L24
|
||||
objc, err := status.GetObjectWithConditions(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, c := range objc.Status.Conditions {
|
||||
switch c.Type {
|
||||
case "Complete":
|
||||
if c.Status == corev1.ConditionTrue {
|
||||
message := fmt.Sprintf("Job Completed. succeeded: %d/%d", succeeded, completions)
|
||||
return &status.Result{
|
||||
Status: status.CurrentStatus,
|
||||
Message: message,
|
||||
Conditions: []status.Condition{},
|
||||
}, nil
|
||||
}
|
||||
case "Failed":
|
||||
message := fmt.Sprintf("Job Failed. failed: %d/%d", failed, completions)
|
||||
if c.Status == corev1.ConditionTrue {
|
||||
return &status.Result{
|
||||
Status: status.FailedStatus,
|
||||
Message: message,
|
||||
Conditions: []status.Condition{
|
||||
{
|
||||
Type: status.ConditionStalled,
|
||||
Status: corev1.ConditionTrue,
|
||||
Reason: "JobFailed",
|
||||
Message: message,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
message := "Job in progress"
|
||||
return &status.Result{
|
||||
Status: status.InProgressStatus,
|
||||
Message: message,
|
||||
Conditions: []status.Condition{
|
||||
{
|
||||
Type: status.ConditionReconciling,
|
||||
Status: corev1.ConditionTrue,
|
||||
Reason: "JobInProgress",
|
||||
Message: message,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
Copyright The Helm Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package statusreaders
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/engine"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/event"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/polling/statusreaders"
|
||||
"github.com/fluxcd/cli-utils/pkg/kstatus/status"
|
||||
"github.com/fluxcd/cli-utils/pkg/object"
|
||||
)
|
||||
|
||||
type customPodStatusReader struct {
|
||||
genericStatusReader engine.StatusReader
|
||||
}
|
||||
|
||||
func NewCustomPodStatusReader(mapper meta.RESTMapper) engine.StatusReader {
|
||||
genericStatusReader := statusreaders.NewGenericStatusReader(mapper, podConditions)
|
||||
return &customPodStatusReader{
|
||||
genericStatusReader: genericStatusReader,
|
||||
}
|
||||
}
|
||||
|
||||
func (j *customPodStatusReader) Supports(gk schema.GroupKind) bool {
|
||||
return gk == corev1.SchemeGroupVersion.WithKind("Pod").GroupKind()
|
||||
}
|
||||
|
||||
func (j *customPodStatusReader) ReadStatus(ctx context.Context, reader engine.ClusterReader, resource object.ObjMetadata) (*event.ResourceStatus, error) {
|
||||
return j.genericStatusReader.ReadStatus(ctx, reader, resource)
|
||||
}
|
||||
|
||||
func (j *customPodStatusReader) ReadStatusForObject(ctx context.Context, reader engine.ClusterReader, resource *unstructured.Unstructured) (*event.ResourceStatus, error) {
|
||||
return j.genericStatusReader.ReadStatusForObject(ctx, reader, resource)
|
||||
}
|
||||
|
||||
func podConditions(u *unstructured.Unstructured) (*status.Result, error) {
|
||||
obj := u.UnstructuredContent()
|
||||
phase := status.GetStringField(obj, ".status.phase", "")
|
||||
switch corev1.PodPhase(phase) {
|
||||
case corev1.PodSucceeded:
|
||||
message := fmt.Sprintf("pod %s succeeded", u.GetName())
|
||||
return &status.Result{
|
||||
Status: status.CurrentStatus,
|
||||
Message: message,
|
||||
Conditions: []status.Condition{
|
||||
{
|
||||
Type: status.ConditionStalled,
|
||||
Status: corev1.ConditionTrue,
|
||||
Message: message,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
case corev1.PodFailed:
|
||||
message := fmt.Sprintf("pod %s failed", u.GetName())
|
||||
return &status.Result{
|
||||
Status: status.FailedStatus,
|
||||
Message: message,
|
||||
Conditions: []status.Condition{
|
||||
{
|
||||
Type: status.ConditionStalled,
|
||||
Status: corev1.ConditionTrue,
|
||||
Reason: "PodFailed",
|
||||
Message: message,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
default:
|
||||
message := "Pod in progress"
|
||||
return &status.Result{
|
||||
Status: status.InProgressStatus,
|
||||
Message: message,
|
||||
Conditions: []status.Condition{
|
||||
{
|
||||
Type: status.ConditionReconciling,
|
||||
Status: corev1.ConditionTrue,
|
||||
Reason: "PodInProgress",
|
||||
Message: message,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
@@ -164,7 +164,8 @@ func CopyFile(src, dst string) (err error) {
|
||||
//
|
||||
// ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522):
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx
|
||||
if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) {
|
||||
lerr := &os.LinkError{}
|
||||
if errors.As(err, &lerr) && !errors.Is(lerr.Err, syscall.Errno(1314)) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -34,6 +34,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
package fs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
@@ -46,10 +47,11 @@ func renameFallback(err error, src, dst string) error {
|
||||
// copy if we detect that case. syscall.EXDEV is the common name for the
|
||||
// cross device link error which has varying output text across different
|
||||
// operating systems.
|
||||
terr, ok := err.(*os.LinkError)
|
||||
terr := &os.LinkError{}
|
||||
ok := errors.As(err, &terr)
|
||||
if !ok {
|
||||
return err
|
||||
} else if terr.Err != syscall.EXDEV {
|
||||
} else if !errors.Is(terr.Err, syscall.EXDEV) {
|
||||
return fmt.Errorf("link error: cannot rename %s to %s: %w", src, dst, terr)
|
||||
}
|
||||
|
||||
|
||||
Vendored
-178
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
apps "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
intstrutil "k8s.io/apimachinery/pkg/util/intstr"
|
||||
appsclient "k8s.io/client-go/kubernetes/typed/apps/v1"
|
||||
)
|
||||
|
||||
// deploymentutil contains a copy of a few functions from Kubernetes controller code to avoid a dependency on k8s.io/kubernetes.
|
||||
// This code is copied from https://github.com/kubernetes/kubernetes/blob/e856613dd5bb00bcfaca6974431151b5c06cbed5/pkg/controller/deployment/util/deployment_util.go
|
||||
// No changes to the code were made other than removing some unused functions
|
||||
|
||||
// RsListFunc returns the ReplicaSet from the ReplicaSet namespace and the List metav1.ListOptions.
|
||||
type RsListFunc func(string, metav1.ListOptions) ([]*apps.ReplicaSet, error)
|
||||
|
||||
// ListReplicaSets returns a slice of RSes the given deployment targets.
|
||||
// Note that this does NOT attempt to reconcile ControllerRef (adopt/orphan),
|
||||
// because only the controller itself should do that.
|
||||
// However, it does filter out anything whose ControllerRef doesn't match.
|
||||
func ListReplicaSets(deployment *apps.Deployment, getRSList RsListFunc) ([]*apps.ReplicaSet, error) {
|
||||
// TODO: Right now we list replica sets by their labels. We should list them by selector, i.e. the replica set's selector
|
||||
// should be a superset of the deployment's selector, see https://github.com/kubernetes/kubernetes/issues/19830.
|
||||
namespace := deployment.Namespace
|
||||
selector, err := metav1.LabelSelectorAsSelector(deployment.Spec.Selector)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
options := metav1.ListOptions{LabelSelector: selector.String()}
|
||||
all, err := getRSList(namespace, options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only include those whose ControllerRef matches the Deployment.
|
||||
owned := make([]*apps.ReplicaSet, 0, len(all))
|
||||
for _, rs := range all {
|
||||
if metav1.IsControlledBy(rs, deployment) {
|
||||
owned = append(owned, rs)
|
||||
}
|
||||
}
|
||||
return owned, nil
|
||||
}
|
||||
|
||||
// ReplicaSetsByCreationTimestamp sorts a list of ReplicaSet by creation timestamp, using their names as a tie breaker.
|
||||
type ReplicaSetsByCreationTimestamp []*apps.ReplicaSet
|
||||
|
||||
func (o ReplicaSetsByCreationTimestamp) Len() int { return len(o) }
|
||||
func (o ReplicaSetsByCreationTimestamp) Swap(i, j int) { o[i], o[j] = o[j], o[i] }
|
||||
func (o ReplicaSetsByCreationTimestamp) Less(i, j int) bool {
|
||||
if o[i].CreationTimestamp.Equal(&o[j].CreationTimestamp) {
|
||||
return o[i].Name < o[j].Name
|
||||
}
|
||||
return o[i].CreationTimestamp.Before(&o[j].CreationTimestamp)
|
||||
}
|
||||
|
||||
// FindNewReplicaSet returns the new RS this given deployment targets (the one with the same pod template).
|
||||
func FindNewReplicaSet(deployment *apps.Deployment, rsList []*apps.ReplicaSet) *apps.ReplicaSet {
|
||||
sort.Sort(ReplicaSetsByCreationTimestamp(rsList))
|
||||
for i := range rsList {
|
||||
if EqualIgnoreHash(&rsList[i].Spec.Template, &deployment.Spec.Template) {
|
||||
// In rare cases, such as after cluster upgrades, Deployment may end up with
|
||||
// having more than one new ReplicaSets that have the same template as its template,
|
||||
// see https://github.com/kubernetes/kubernetes/issues/40415
|
||||
// We deterministically choose the oldest new ReplicaSet.
|
||||
return rsList[i]
|
||||
}
|
||||
}
|
||||
// new ReplicaSet does not exist.
|
||||
return nil
|
||||
}
|
||||
|
||||
// EqualIgnoreHash returns true if two given podTemplateSpec are equal, ignoring the diff in value of Labels[pod-template-hash]
|
||||
// We ignore pod-template-hash because:
|
||||
// 1. The hash result would be different upon podTemplateSpec API changes
|
||||
// (e.g. the addition of a new field will cause the hash code to change)
|
||||
// 2. The deployment template won't have hash labels
|
||||
func EqualIgnoreHash(template1, template2 *v1.PodTemplateSpec) bool {
|
||||
t1Copy := template1.DeepCopy()
|
||||
t2Copy := template2.DeepCopy()
|
||||
// Remove hash labels from template.Labels before comparing
|
||||
delete(t1Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
|
||||
delete(t2Copy.Labels, apps.DefaultDeploymentUniqueLabelKey)
|
||||
return apiequality.Semantic.DeepEqual(t1Copy, t2Copy)
|
||||
}
|
||||
|
||||
// GetNewReplicaSet returns a replica set that matches the intent of the given deployment; get ReplicaSetList from client interface.
|
||||
// Returns nil if the new replica set doesn't exist yet.
|
||||
func GetNewReplicaSet(deployment *apps.Deployment, c appsclient.AppsV1Interface) (*apps.ReplicaSet, error) {
|
||||
rsList, err := ListReplicaSets(deployment, RsListFromClient(c))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FindNewReplicaSet(deployment, rsList), nil
|
||||
}
|
||||
|
||||
// RsListFromClient returns an rsListFunc that wraps the given client.
|
||||
func RsListFromClient(c appsclient.AppsV1Interface) RsListFunc {
|
||||
return func(namespace string, options metav1.ListOptions) ([]*apps.ReplicaSet, error) {
|
||||
rsList, err := c.ReplicaSets(namespace).List(context.Background(), options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ret []*apps.ReplicaSet
|
||||
for i := range rsList.Items {
|
||||
ret = append(ret, &rsList.Items[i])
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
|
||||
// IsRollingUpdate returns true if the strategy type is a rolling update.
|
||||
func IsRollingUpdate(deployment *apps.Deployment) bool {
|
||||
return deployment.Spec.Strategy.Type == apps.RollingUpdateDeploymentStrategyType
|
||||
}
|
||||
|
||||
// MaxUnavailable returns the maximum unavailable pods a rolling deployment can take.
|
||||
func MaxUnavailable(deployment apps.Deployment) int32 {
|
||||
if !IsRollingUpdate(&deployment) || *(deployment.Spec.Replicas) == 0 {
|
||||
return int32(0)
|
||||
}
|
||||
// Error caught by validation
|
||||
_, maxUnavailable, _ := ResolveFenceposts(deployment.Spec.Strategy.RollingUpdate.MaxSurge, deployment.Spec.Strategy.RollingUpdate.MaxUnavailable, *(deployment.Spec.Replicas))
|
||||
if maxUnavailable > *deployment.Spec.Replicas {
|
||||
return *deployment.Spec.Replicas
|
||||
}
|
||||
return maxUnavailable
|
||||
}
|
||||
|
||||
// ResolveFenceposts resolves both maxSurge and maxUnavailable. This needs to happen in one
|
||||
// step. For example:
|
||||
//
|
||||
// 2 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1), then old(-1), then new(+1)
|
||||
// 1 desired, max unavailable 1%, surge 0% - should scale old(-1), then new(+1)
|
||||
// 2 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
|
||||
// 1 desired, max unavailable 25%, surge 1% - should scale new(+1), then old(-1)
|
||||
// 2 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1), then new(+1), then old(-1)
|
||||
// 1 desired, max unavailable 0%, surge 1% - should scale new(+1), then old(-1)
|
||||
func ResolveFenceposts(maxSurge, maxUnavailable *intstrutil.IntOrString, desired int32) (int32, int32, error) {
|
||||
surge, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxSurge, intstrutil.FromInt(0)), int(desired), true)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
unavailable, err := intstrutil.GetValueFromIntOrPercent(intstrutil.ValueOrDefault(maxUnavailable, intstrutil.FromInt(0)), int(desired), false)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
if surge == 0 && unavailable == 0 {
|
||||
// Validation should never allow the user to explicitly use zero values for both maxSurge
|
||||
// maxUnavailable. Due to rounding down maxUnavailable though, it may resolve to zero.
|
||||
// If both fenceposts resolve to zero, then we should set maxUnavailable to 1 on the
|
||||
// theory that surge might not work due to quota.
|
||||
unavailable = 1
|
||||
}
|
||||
|
||||
return int32(surge), int32(unavailable), nil
|
||||
}
|
||||
+1
-1
@@ -112,7 +112,7 @@ func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) {
|
||||
if len(to.caPEMBlock) > 0 {
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(to.caPEMBlock) {
|
||||
return nil, fmt.Errorf("failed to append certificates from pem block")
|
||||
return nil, errors.New("failed to append certificates from pem block")
|
||||
}
|
||||
|
||||
config.RootCAs = cp
|
||||
|
||||
+3
-3
@@ -17,7 +17,7 @@ limitations under the License.
|
||||
package version
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"runtime/debug"
|
||||
"slices"
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
func K8sIOClientGoModVersion() (string, error) {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "", fmt.Errorf("failed to read build info")
|
||||
return "", errors.New("failed to read build info")
|
||||
}
|
||||
|
||||
idx := slices.IndexFunc(info.Deps, func(m *debug.Module) bool {
|
||||
@@ -35,7 +35,7 @@ func K8sIOClientGoModVersion() (string, error) {
|
||||
})
|
||||
|
||||
if idx == -1 {
|
||||
return "", fmt.Errorf("k8s.io/client-go not found in build info")
|
||||
return "", errors.New("k8s.io/client-go not found in build info")
|
||||
}
|
||||
|
||||
m := info.Deps[idx]
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ var (
|
||||
//
|
||||
// Increment major number for new feature additions and behavioral changes.
|
||||
// Increment minor number for bug fixes and performance enhancements.
|
||||
version = "v4.1"
|
||||
version = "v4.2"
|
||||
|
||||
// metadata is extra build time data
|
||||
metadata = ""
|
||||
|
||||
Reference in New Issue
Block a user