diff --git a/cmd/machine-config-controller/bootstrap.go b/cmd/machine-config-controller/bootstrap.go index 217c1dacef..82e1cae031 100644 --- a/cmd/machine-config-controller/bootstrap.go +++ b/cmd/machine-config-controller/bootstrap.go @@ -43,7 +43,7 @@ func runbootstrapCmd(_ *cobra.Command, _ []string) { klog.Fatalf("--dest-dir or --manifest-dir not set") } - if err := bootstrap.New(rootOpts.templates, bootstrapOpts.manifestsDir, bootstrapOpts.pullSecretFile).Run(bootstrapOpts.destinationDir); err != nil { + if err := bootstrap.New(rootOpts.templates, bootstrapOpts.manifestsDir, bootstrapOpts.pullSecretFile, nil).Run(bootstrapOpts.destinationDir); err != nil { klog.Fatalf("error running MCC[BOOTSTRAP]: %v", err) } } diff --git a/cmd/machine-config-controller/start.go b/cmd/machine-config-controller/start.go index f1fb015b43..23472ba9da 100644 --- a/cmd/machine-config-controller/start.go +++ b/cmd/machine-config-controller/start.go @@ -95,6 +95,7 @@ func runStartCmd(_ *cobra.Command, _ []string) { return } + var inspectorFactory osimagestream.ImagesInspectorFactory var inspectionCache *imageutils.FileInspectionCache if startOpts.streamsCache != "" { inspectionCache = imageutils.NewFileInspectionCache(path.Join(startOpts.streamsCache, "image-inspection.json"), 48*time.Hour) @@ -105,7 +106,6 @@ func runStartCmd(_ *cobra.Command, _ []string) { // controller starts, since render, node, and template depend on it // for OS image URLs. if osimagestream.IsFeatureEnabled(ctrlctx.FeatureGatesHandler) { - var inspectorFactory osimagestream.ImagesInspectorFactory if inspectionCache != nil { inspectorFactory = osimagestream.NewCachedImagesInspectorFactory( &osimagestream.DefaultImagesInspectorFactory{}, @@ -150,7 +150,7 @@ func runStartCmd(_ *cobra.Command, _ []string) { go ctrlcommon.StartMetricsListener(startOpts.promMetricsListenAddress, ctrlctx.Stop, ctrlcommon.RegisterMCCMetrics, startOpts.tlsMinVersion, startOpts.tlsCipherSuites) - controllers := createControllers(ctrlctx, inspectionCache) + controllers := createControllers(ctrlctx, inspectionCache, inspectorFactory) draincontroller := drain.New( drain.DefaultConfig(), ctrlctx.KubeInformerFactory.Core().V1().Nodes(), @@ -272,7 +272,7 @@ func runStartCmd(_ *cobra.Command, _ []string) { panic("unreachable") } -func createControllers(ctx *ctrlcommon.ControllerContext, inspectionCache *imageutils.FileInspectionCache) []ctrlcommon.Controller { +func createControllers(ctx *ctrlcommon.ControllerContext, inspectionCache *imageutils.FileInspectionCache, inspectorFactory osimagestream.ImagesInspectorFactory) []ctrlcommon.Controller { // Only watch IRI informers when the feature gate is enabled. The // InternalReleaseImages CRD is not installed on clusters where the gate is // off, so the informer list call would fail and WaitForCacheSync in the @@ -292,9 +292,15 @@ func createControllers(ctx *ctrlcommon.ControllerContext, inspectionCache *image ctx.InformerFactory.Machineconfiguration().V1().KubeletConfigs(), ctx.OperatorInformerFactory.Operator().V1().MachineConfigurations(), ctx.InformerFactory.Machineconfiguration().V1().OSImageStreams(), + ctx.OpenShiftConfigKubeNamespacedInformerFactory.Core().V1().Secrets(), + ctx.OperatorInformerFactory.Operator().V1alpha1().ImageContentSourcePolicies(), + ctx.ConfigInformerFactory.Config().V1().ImageDigestMirrorSets(), + ctx.ConfigInformerFactory.Config().V1().ImageTagMirrorSets(), + ctx.ConfigInformerFactory.Config().V1().Images(), ctx.ClientBuilder.KubeClientOrDie("render-controller"), ctx.ClientBuilder.MachineConfigClientOrDie("render-controller"), ctx.FeatureGatesHandler, + inspectorFactory, ) if inspectionCache != nil { inspectionCache.RegisterEvicter(renderCtrl) diff --git a/go.mod b/go.mod index 95816dcff9..9a4fa87f9d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/openshift/machine-config-operator -go 1.25.3 +go 1.25.5 require ( github.com/Azure/ARO-RP v0.0.0-20250602035759-0693f32d5ccc @@ -43,11 +43,12 @@ require ( github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818 github.com/openshift/api v0.0.0-20260702202555-ef71f942ef6c github.com/openshift/client-go v0.0.0-20260703082747-24d059aea27a + github.com/openshift/imagebuilder v1.2.21 github.com/openshift/library-go v0.0.0-20260611115129-21dd5809a4b2 github.com/openshift/runtime-utils v0.0.0-20230921210328-7bdb5b9c177b github.com/prometheus/client_golang v1.23.2 github.com/rs/zerolog v1.34.0 - github.com/spf13/cobra v1.10.0 + github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 github.com/tidwall/gjson v1.18.0 @@ -55,7 +56,7 @@ require ( github.com/vincent-petithory/dataurl v1.0.0 github.com/vmware/govmomi v0.45.1 golang.org/x/net v0.56.0 - golang.org/x/time v0.11.0 + golang.org/x/time v0.14.0 google.golang.org/grpc v1.79.3 k8s.io/api v0.35.1 k8s.io/apiextensions-apiserver v0.35.1 @@ -92,8 +93,7 @@ require ( github.com/alexkohler/nakedret/v2 v2.0.5 // indirect github.com/antlr4-go/antlr/v4 v4.13.1 // indirect github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 // indirect - github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect - github.com/aws/aws-sdk-go v1.55.6 // indirect + github.com/aws/aws-sdk-go v1.55.7 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.30 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.30 // indirect github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.31 // indirect @@ -104,16 +104,16 @@ require ( github.com/butuzov/mirror v1.3.0 // indirect github.com/catenacyber/perfsprint v0.7.1 // indirect github.com/ccojocar/zxcvbn-go v1.0.2 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect github.com/ckaznocha/intrange v0.3.0 // indirect - github.com/cloudflare/circl v1.6.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect github.com/container-storage-interface/spec v1.9.0 // indirect - github.com/containerd/containerd/api v1.9.0 // indirect + github.com/containerd/containerd/api v1.10.0 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect - github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/ttrpc v1.2.8 // indirect github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect github.com/cyphar/filepath-securejoin v0.6.1 // indirect @@ -123,29 +123,28 @@ require ( github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/ghostiam/protogetter v0.3.8 // indirect github.com/go-errors/errors v1.4.2 // indirect - github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/analysis v0.23.0 // indirect - github.com/go-openapi/errors v0.22.1 // indirect - github.com/go-openapi/loads v0.22.0 // indirect - github.com/go-openapi/runtime v0.28.0 // indirect - github.com/go-openapi/spec v0.21.0 // indirect - github.com/go-openapi/strfmt v0.23.0 // indirect + github.com/go-openapi/analysis v0.24.3 // indirect + github.com/go-openapi/errors v0.22.7 // indirect + github.com/go-openapi/loads v0.23.3 // indirect + github.com/go-openapi/runtime v0.29.2 // indirect + github.com/go-openapi/spec v0.22.4 // indirect + github.com/go-openapi/strfmt v0.26.1 // indirect github.com/go-openapi/swag/cmdutils v0.25.4 // indirect - github.com/go-openapi/swag/conv v0.25.4 // indirect - github.com/go-openapi/swag/fileutils v0.25.4 // indirect - github.com/go-openapi/swag/jsonname v0.25.4 // indirect - github.com/go-openapi/swag/jsonutils v0.25.4 // indirect - github.com/go-openapi/swag/loading v0.25.4 // indirect - github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.5 // indirect + github.com/go-openapi/swag/fileutils v0.25.5 // indirect + github.com/go-openapi/swag/jsonname v0.25.5 // indirect + github.com/go-openapi/swag/jsonutils v0.25.5 // indirect + github.com/go-openapi/swag/loading v0.25.5 // indirect + github.com/go-openapi/swag/mangling v0.25.5 // indirect github.com/go-openapi/swag/netutils v0.25.4 // indirect - github.com/go-openapi/swag/stringutils v0.25.4 // indirect - github.com/go-openapi/swag/typeutils v0.25.4 // indirect - github.com/go-openapi/swag/yamlutils v0.25.4 // indirect - github.com/go-openapi/validate v0.24.0 // indirect + github.com/go-openapi/swag/stringutils v0.25.5 // indirect + github.com/go-openapi/swag/typeutils v0.25.5 // indirect + github.com/go-openapi/swag/yamlutils v0.25.5 // indirect + github.com/go-openapi/validate v0.25.2 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/godbus/dbus/v5 v5.1.1-0.20240921181615-a817f3cc4a9e // indirect github.com/golangci/go-printf-func-name v0.1.0 // indirect github.com/golangci/modinfo v0.3.4 // indirect @@ -155,11 +154,11 @@ require ( github.com/google/cel-go v0.26.0 // indirect github.com/google/gnostic-models v0.7.0 // indirect github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect - github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect + github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 // indirect github.com/jjti/go-spancheck v0.6.4 // indirect github.com/karamaru-alpha/copyloopvar v1.1.0 // indirect github.com/karrick/godirwalk v1.17.0 // indirect @@ -173,6 +172,7 @@ require ( github.com/maratori/testableexamples v1.0.0 // indirect github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/moby/buildkit v0.29.0 // indirect github.com/moby/spdystream v0.5.0 // indirect github.com/moby/sys/capability v0.4.0 // indirect github.com/moby/sys/user v0.4.0 // indirect @@ -182,25 +182,26 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/nunnatsa/ginkgolinter v0.18.0 // indirect - github.com/oklog/ulid v1.3.1 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/opencontainers/cgroups v0.0.3 // indirect - github.com/opencontainers/selinux v1.13.0 // indirect + github.com/opencontainers/selinux v1.13.1 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/quasilyte/go-ruleguard/dsl v0.3.22 // indirect github.com/raeperd/recvcheck v0.1.2 // indirect github.com/robfig/cron v1.2.0 // indirect github.com/robfig/cron/v3 v3.0.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect + github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect - github.com/secure-systems-lab/go-securesystemslib v0.9.0 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sigstore/fulcio v1.6.6 // indirect - github.com/sigstore/protobuf-specs v0.4.1 // indirect - github.com/sigstore/rekor v1.3.10 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect + github.com/sigstore/protobuf-specs v0.5.0 // indirect + github.com/sigstore/rekor v1.5.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/stoewer/go-strcase v1.3.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect @@ -216,24 +217,24 @@ require ( go.etcd.io/etcd/api/v3 v3.6.5 // indirect go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect go.etcd.io/etcd/client/v3 v3.6.5 // indirect - go.mongodb.org/mongo-driver v1.14.0 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect go.opentelemetry.io/otel v1.40.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 // indirect go.opentelemetry.io/otel/metric v1.40.0 // indirect go.opentelemetry.io/otel/sdk v1.40.0 // indirect go.opentelemetry.io/otel/trace v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.podman.io/storage v1.62.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.uber.org/automaxprocs v1.6.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 // indirect gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect @@ -281,14 +282,14 @@ require ( github.com/containers/ocicrypt v1.2.1 // indirect github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb // indirect github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f // indirect - github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 + github.com/coreos/go-systemd/v22 v22.7.0 github.com/coreos/vcontext v0.0.0-20231102161604-685dc7299dc5 // indirect github.com/curioswitch/go-reassign v0.3.0 // indirect github.com/daixiang0/gci v0.13.5 // indirect github.com/denis-tingaikin/go-header v0.5.0 // indirect github.com/docker/distribution v2.8.3+incompatible - github.com/docker/docker v28.2.2+incompatible // indirect - github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/docker v28.5.2+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.5 // indirect github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.13.0 // indirect @@ -300,8 +301,8 @@ require ( github.com/fzipp/gocyclo v0.6.0 // indirect github.com/go-critic/go-critic v0.11.5 // indirect github.com/go-logr/logr v1.4.3 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/jsonpointer v0.22.5 // indirect + github.com/go-openapi/jsonreference v0.21.5 // indirect github.com/go-openapi/swag v0.25.4 // indirect github.com/go-toolsmith/astcast v1.1.0 // indirect github.com/go-toolsmith/astcopy v1.1.0 // indirect @@ -312,7 +313,7 @@ require ( github.com/go-toolsmith/typep v1.1.0 // indirect github.com/go-xmlfmt/xmlfmt v1.1.3 // indirect github.com/gobwas/glob v0.2.3 // indirect - github.com/gofrs/flock v0.12.1 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 github.com/golang/protobuf v1.5.4 // indirect @@ -321,7 +322,7 @@ require ( github.com/golangci/misspell v0.6.0 // indirect github.com/golangci/revgrep v0.5.3 // indirect github.com/golangci/unconvert v0.0.0-20240309020433-c5143eacb3ed // indirect - github.com/google/go-containerregistry v0.20.3 // indirect + github.com/google/go-containerregistry v0.20.7 // indirect github.com/gordonklaus/ineffassign v0.1.0 // indirect github.com/gorilla/mux v1.8.1 // indirect github.com/gostaticanalysis/analysisutil v0.7.1 // indirect @@ -344,7 +345,6 @@ require ( github.com/ldez/gomoddirectives v0.6.0 // indirect github.com/ldez/tagliatelle v0.5.0 // indirect github.com/leonklingele/grouper v1.1.2 // indirect - github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect github.com/maratori/testpackage v1.1.1 // indirect github.com/matoous/godox v0.0.0-20230222163458-006bad1f9d26 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -352,7 +352,6 @@ require ( github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mgechev/revive v1.5.1 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect @@ -363,15 +362,15 @@ require ( github.com/nishanths/predeclared v0.2.2 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/opencontainers/image-spec v1.1.1 - github.com/opencontainers/runtime-spec v1.2.1 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/opencontainers/runtime-spec v1.3.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pkg/errors v0.9.1 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/polyfloyd/go-errorlint v1.7.0 // indirect github.com/proglottis/gpgme v0.1.4 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 // indirect github.com/quasilyte/gogrep v0.5.0 // indirect github.com/quasilyte/regex/syntax v0.0.0-20210819130434-b3f0c404a727 // indirect @@ -384,15 +383,15 @@ require ( github.com/sashamelentyev/usestdlibvars v1.28.0 // indirect github.com/securego/gosec/v2 v2.21.4 // indirect github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/sigstore/sigstore v1.9.3 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect + github.com/sigstore/sigstore v1.10.4 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect github.com/sivchari/containedctx v1.0.3 // indirect github.com/sivchari/tenv v1.12.1 // indirect github.com/sonatard/noctx v0.1.0 // indirect github.com/sourcegraph/go-diff v0.7.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.7.0 // indirect - github.com/spf13/viper v1.20.0-alpha.6 // indirect + github.com/spf13/afero v1.15.0 // indirect + github.com/spf13/cast v1.10.0 // indirect + github.com/spf13/viper v1.21.0 // indirect github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -400,7 +399,6 @@ require ( github.com/tdakkota/asciicheck v0.3.0 // indirect github.com/tetafro/godot v1.4.20 // indirect github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 // indirect - github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 // indirect github.com/tomarrell/wrapcheck/v2 v2.10.0 // indirect github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect github.com/ultraware/funlen v0.1.0 // indirect @@ -410,10 +408,10 @@ require ( github.com/yeya24/promlinter v0.3.0 // indirect gitlab.com/bosi/decorder v0.4.2 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.0 // indirect + go.uber.org/zap v1.27.1 // indirect go4.org v0.0.0-20200104003542-c7e774b10ea0 // indirect golang.org/x/crypto v0.54.0 - golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 + golang.org/x/exp v0.0.0-20250911091902-df9299821621 golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/mod v0.37.0 // indirect golang.org/x/oauth2 v0.34.0 // indirect @@ -422,7 +420,7 @@ require ( golang.org/x/term v0.45.0 // indirect golang.org/x/text v0.40.0 // indirect golang.org/x/tools v0.47.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 diff --git a/go.sum b/go.sum index 3c4b4af169..97833e104c 100644 --- a/go.sum +++ b/go.sum @@ -75,8 +75,6 @@ github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloD github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= -github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/ashanbrown/forbidigo v1.6.0 h1:D3aewfM37Yb3pxHujIPSpTf6oQk9sc9WZi8gerOIVIY= github.com/ashanbrown/forbidigo v1.6.0/go.mod h1:Y8j9jy9ZYAEHXdu723cUlraTqbzjKF1MUyfOKL+AjcU= github.com/ashanbrown/makezero v1.2.0 h1:/2Lp1bypdmK9wDIq7uWBlDF1iMUpIIS4A+pF6C9IEUU= @@ -84,8 +82,8 @@ github.com/ashanbrown/makezero v1.2.0/go.mod h1:dxlPhHbDMC6N6xICzFBSK+4njQDdK8eu github.com/ashcrow/osrelease v0.0.0-20180626175927-9b292693c55c h1:icme0QhxrgZOxTBnT6K8dfGLwbKWSOVwPB95XTbo8Ws= github.com/ashcrow/osrelease v0.0.0-20180626175927-9b292693c55c/go.mod h1:BRljTyotlu+6N+Qlu5MhjxpdmccCnp9lDvZjNNV8qr4= github.com/aws/aws-sdk-go v1.19.11/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.55.6 h1:cSg4pvZ3m8dgYcgqB97MrcdjUmZ1BeMYKUxMMB89IPk= -github.com/aws/aws-sdk-go v1.55.6/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/aws/aws-sdk-go-v2 v1.42.1 h1:9eOTgu1z/dVtYpNZ3/8/XbbaX0x/BqE3HUzAzs6K0ek= github.com/aws/aws-sdk-go-v2 v1.42.1/go.mod h1:5pKeft2eJj+gElQ38Jqg4ibCqh+/AK33/0X3hip7IjM= github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= @@ -129,8 +127,8 @@ github.com/catenacyber/perfsprint v0.7.1 h1:PGW5G/Kxn+YrN04cRAZKC+ZuvlVwolYMrIyy github.com/catenacyber/perfsprint v0.7.1/go.mod h1:/wclWYompEyjUD2FuIIDVKNkqz7IgBIWXIH3V0Zol50= github.com/ccojocar/zxcvbn-go v1.0.2 h1:na/czXU8RrhXO4EZme6eQJLR4PzcGsahsBOAwU6I3Vg= github.com/ccojocar/zxcvbn-go v1.0.2/go.mod h1:g1qkXtUSvHP8lhHp5GrSmTz6uWALGRMQdw6Qnz/hi60= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= @@ -145,21 +143,21 @@ github.com/ckaznocha/intrange v0.3.0/go.mod h1:+I/o2d2A1FBHgGELbGxzIcyd3/9l9Duwj github.com/clarketm/json v1.17.1 h1:U1IxjqJkJ7bRK4L6dyphmoO840P6bdhPdbbLySourqI= github.com/clarketm/json v1.17.1/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk= -github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/container-storage-interface/spec v1.9.0 h1:zKtX4STsq31Knz3gciCYCi1SXtO2HJDecIjDVboYavY= github.com/container-storage-interface/spec v1.9.0/go.mod h1:ZfDu+3ZRyeVqxZM0Ds19MVLkN2d1XJ5MAfi1L3VjlT0= -github.com/containerd/containerd/api v1.9.0 h1:HZ/licowTRazus+wt9fM6r/9BQO7S0vD5lMcWspGIg0= -github.com/containerd/containerd/api v1.9.0/go.mod h1:GhghKFmTR3hNtyznBoQ0EMWr9ju5AqHjcZPsSpTKutI= +github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= +github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= -github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= -github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/ttrpc v1.2.8 h1:xbVu6D4qF2jihdh9rDVOKqUMiFBQk6YctTdo1zk087Y= +github.com/containerd/ttrpc v1.2.8/go.mod h1:wyZW2K79t4Hfcxl+GUvkZqRBzJlqFFvgEeeWXa42tyE= github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/containers/common v0.62.1 h1:durvu7Kelb8PYgX7bwuAg/d5LKj2hs3cAaqcU7Vnqus= @@ -186,8 +184,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f h1:JOrtw2xFKzlg+cbHpyrpLDmnN1HqhBfnX7WDiW7eG2c= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09 h1:OoRAFlvDGCUqDLampLQjk0yeeSGdF9zzst/3G9IkBbc= -github.com/coreos/go-systemd/v22 v22.5.1-0.20231103132048-7d375ecc2b09/go.mod h1:m2r/smMKsKwgMSAoFKHaa68ImdCSNuKE1MxvQ64xuCQ= +github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= +github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/coreos/ign-converter v0.0.0-20241125185625-2f773079ca81 h1:uz0f9dymW/05AsgIOxFQDsMp/qpqxh3mOvDf7xeXd+w= github.com/coreos/ign-converter v0.0.0-20241125185625-2f773079ca81/go.mod h1:Q7SbzjFkayIfwm+b+nXedvIcP2SFAndA7ET/JPNNc1I= github.com/coreos/ignition v0.35.0 h1:UFodoYq1mOPrbEjtxIsZbThcDyQwAI1owczRDqWmKkQ= @@ -222,14 +220,14 @@ github.com/denis-tingaikin/go-header v0.5.0 h1:SRdnP5ZKvcO9KKRP1KJrhFR3RrlGuD+42 github.com/denis-tingaikin/go-header v0.5.0/go.mod h1:mMenU5bWrok6Wl2UsZjy+1okegmwQ3UgWl4V1D8gjlY= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A= -github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/cli v29.3.1+incompatible h1:M04FDj2TRehDacrosh7Vlkgc7AuQoWloQkf1PA5hmoI= +github.com/docker/cli v29.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw= -github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= -github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM= +github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= @@ -285,8 +283,6 @@ github.com/go-critic/go-critic v0.11.5 h1:TkDTOn5v7EEngMxu8KbuFqFR43USaaH8XRJLz1 github.com/go-critic/go-critic v0.11.5/go.mod h1:wu6U7ny9PiaHaZHcvMDmdysMqvDem162Rh3zWTrqk8M= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-jose/go-jose/v4 v4.1.4 h1:moDMcTHmvE6Groj34emNPLs/qtYXRVcd6S7NHbHz3kA= -github.com/go-jose/go-jose/v4 v4.1.4/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -294,61 +290,59 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC03zFCU= -github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo= -github.com/go-openapi/errors v0.22.1 h1:kslMRRnK7NCb/CvR1q1VWuEQCEIsBGn5GgKD9e+HYhU= -github.com/go-openapi/errors v0.22.1/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0= -github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco= -github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs= -github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ= -github.com/go-openapi/runtime v0.28.0/go.mod h1:QN7OzcS+XuYmkQLw05akXk0jRH/eZ3kb18+1KwW9gyc= -github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= -github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= -github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c= -github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4= +github.com/go-openapi/analysis v0.24.3 h1:a1hrvMr8X0Xt69KP5uVTu5jH62DscmDifrLzNglAayk= +github.com/go-openapi/analysis v0.24.3/go.mod h1:Nc+dWJ/FxZbhSow5Yh3ozg5CLJioB+XXT6MdLvJUsUw= +github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= +github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= +github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= +github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= +github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= +github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= +github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= +github.com/go-openapi/strfmt v0.26.1 h1:7zGCHji7zSYDC2tCXIusoxYQz/48jAf2q+sF6wXTG+c= +github.com/go-openapi/strfmt v0.26.1/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y= github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= -github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= -github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= -github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= -github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= -github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= -github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= -github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= -github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= -github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= -github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= -github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= -github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g= +github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k= +github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk= +github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= +github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo= +github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo= +github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU= +github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g= +github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw= +github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY= github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= -github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= -github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= -github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= -github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= -github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= -github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= -github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= -github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= -github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= -github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= -github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58= -github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ= +github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M= +github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII= +github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E= +github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc= +github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= +github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q= +github.com/go-openapi/testify/v2 v2.4.1 h1:zB34HDKj4tHwyUQHrUkpV0Q0iXQ6dUCOQtIqn8hE6Iw= +github.com/go-openapi/testify/v2 v2.4.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0= +github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY= github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U= -github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/go-toolsmith/astcast v1.1.0 h1:+JN9xZV1A+Re+95pgnMgDboWNVnIMMQXwfBwLRPgSC8= github.com/go-toolsmith/astcast v1.1.0/go.mod h1:qdcuFWeGGS2xX5bLM/c3U9lewg7+Zu4mr+xPwZIB4ZU= github.com/go-toolsmith/astcopy v1.1.0 h1:YGwBN0WM+ekI/6SS6+52zLDEf8Yvp3n2seZITCUBt5s= @@ -368,8 +362,8 @@ github.com/go-toolsmith/strparse v1.1.0 h1:GAioeZUK9TGxnLS+qfdqNbA4z0SSm5zVNtCQi github.com/go-toolsmith/strparse v1.1.0/go.mod h1:7ksGy58fsaQkGQlY8WVoBFNyEPMGuJin1rfoPS4lBSQ= github.com/go-toolsmith/typep v1.1.0 h1:fIRYDyF+JywLfqzyhdiHzRop/GQDxxNhLGQ6gFUNHus= github.com/go-toolsmith/typep v1.1.0/go.mod h1:fVIw+7zjdsMxDA3ITWnH1yOiw1rnTQKCsF/sk2H/qig= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= +github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/go-xmlfmt/xmlfmt v1.1.3 h1:t8Ey3Uy7jDSEisW2K3somuMKIpzktkWptA0iFCnRUWY= github.com/go-xmlfmt/xmlfmt v1.1.3/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -378,15 +372,14 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus v0.0.0-20181025153459-66d97aec3384/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.1-0.20240921181615-a817f3cc4a9e h1:znsZ+BW06LsAtZwQvY/rgWQ3o1q0mnR4SG4q8HCP+3Q= github.com/godbus/dbus/v5 v5.1.1-0.20240921181615-a817f3cc4a9e/go.mod h1:nRJ+j259aT/CW6otoGCHPa1K/lNHLO+UGmW133FNj9s= -github.com/gofrs/flock v0.12.1 h1:MTLVXXHf8ekldpJk3AKicLij9MdwOWkZ+a/jHHZby9E= -github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeHCoD0= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= -github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= @@ -441,15 +434,15 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-containerregistry v0.20.3 h1:oNx7IdTI936V8CQRveCjaxOiegWwvM7kqkbXTpyiovI= -github.com/google/go-containerregistry v0.20.3/go.mod h1:w00pIgBRDVUDFM6bq+Qx8lwNWK+cxgCuX1vd3PIBDNI= +github.com/google/go-containerregistry v0.20.7 h1:24VGNpS0IwrOZ2ms2P1QE3Xa5X9p4phx0aUgzYzHW6I= +github.com/google/go-containerregistry v0.20.7/go.mod h1:Lx5LCZQjLH1QBaMPeGwsME9biPeo1lPx6lbGj/UmzgM= github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f h1:7MmqygqdeJtziBUpm4Z9ThROFZUaVGaePMfcDnluf1E= github.com/google/goexpect v0.0.0-20210430020637-ab937bf7fd6f/go.mod h1:n1ej5+FqyEytMt/mugVDZLIiqTMO+vsrgY+kM6ohzN0= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 h1:EEHtgt9IwisQ2AZ4pIsMjahcegHh6rmhqxzIRQIyepY= +github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6/go.mod h1:I6V7YzU0XDpsHqbsyrghnFZLO1gwK6NPTNvmetQIk9U= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -483,8 +476,8 @@ github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/E github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99 h1:JYghRBlGCZyCF2wNUJ8W0cwaQdtpcssJ4CgC406g+WU= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.1-0.20210315223345-82c243799c99/go.mod h1:3bDW6wMZJB7tiONtC/1Xpicra6Wp5GgbTbQWCbI5fkc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -507,8 +500,6 @@ github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+D github.com/jjti/go-spancheck v0.6.4 h1:Tl7gQpYf4/TMU7AT84MN83/6PutY21Nb9fuQjFTpRRc= github.com/jjti/go-spancheck v0.6.4/go.mod h1:yAEYdKJ2lRkDA8g7X+oKUHXOWVAXSBJRv04OhF+QUjk= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmhodges/clock v1.2.0 h1:eq4kys+NI0PLngzaHEe7AmPT90XMGIEySD1JfV1PDIs= -github.com/jmhodges/clock v1.2.0/go.mod h1:qKjhA7x7u/lQpPB1XAqX1b1lCI/w3/fNuYpI/ZjLynI= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/josephburnett/jd v1.9.2 h1:ECJRRFXCCqbtidkAHckHGSZm/JIaAxS1gygHLF8MI5Y= @@ -530,8 +521,8 @@ github.com/kisielk/errcheck v1.8.0/go.mod h1:1kLL+jV4e+CFfueBmI1dSK2ADDyQnlrnrY/ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkHAIKE/contextcheck v1.1.5 h1:CdnJh63tcDe53vG+RebdpdXJTc9atMgGqdx8LXxiilg= github.com/kkHAIKE/contextcheck v1.1.5/go.mod h1:O930cpht4xb1YQpK+1+AgoM3mFsvxr7uyFptcnWTYUA= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -559,8 +550,6 @@ github.com/ldez/tagliatelle v0.5.0 h1:epgfuYt9v0CG3fms0pEgIMNPuFf/LpPIfjk4kyqSio github.com/ldez/tagliatelle v0.5.0/go.mod h1:rj1HmWiL1MiKQuOONhd09iySTEkUuE/8+5jtPYz9xa4= github.com/leonklingele/grouper v1.1.2 h1:o1ARBDLOmmasUaNDesWqWCIFH3u7hoFlM84YrjT3mIY= github.com/leonklingele/grouper v1.1.2/go.mod h1:6D0M/HVkhs2yRKRFZUoGjeDy7EZTfFBE9gl4kjmIGkA= -github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec h1:2tTW6cDth2TSgRbAhD7yjZzTQmcN25sDRPEeinR51yQ= -github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec/go.mod h1:TmwEoGCwIti7BCeJ9hescZgRtatxRE+A72pCoPfmcfk= github.com/libopenstorage/openstorage v1.0.0 h1:GLPam7/0mpdP8ZZtKjbfcXJBTIA/T1O6CBErVEFEyIM= github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= @@ -599,8 +588,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/buildkit v0.29.0 h1:wxLEFbCOJntEDjSNNN2YWd8zxltZxT5muDQ0LzpbtpU= +github.com/moby/buildkit v0.29.0/go.mod h1:Dmv2FeDe34t75QuzeU87rBoZpAAkcpT5zeu4hXzmASc= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= @@ -643,8 +632,8 @@ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -661,16 +650,18 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8 github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= -github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= -github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/selinux v1.13.0 h1:Zza88GWezyT7RLql12URvoxsbLfjFx988+LGaWfbL84= -github.com/opencontainers/selinux v1.13.0/go.mod h1:XxWTed+A/s5NNq4GmYScVy+9jzXhGBVEOAyucdRUY8s= +github.com/opencontainers/runtime-spec v1.3.0 h1:YZupQUdctfhpZy3TM39nN9Ika5CBWT5diQ8ibYCRkxg= +github.com/opencontainers/runtime-spec v1.3.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.13.1 h1:A8nNeceYngH9Ow++M+VVEwJVpdFmrlxsN22F+ISDCJE= +github.com/opencontainers/selinux v1.13.1/go.mod h1:S10WXZ/osk2kWOYKy1x2f/eXF5ZHJoUs8UU/2caNRbg= github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818 h1:jJLE/aCAqDf8U4wc3bE1IEKgIxbb0ICjCNVFA49x/8s= github.com/openshift-eng/openshift-tests-extension v0.0.0-20260127124016-0fed2b824818/go.mod h1:6gkP5f2HL0meusT0Aim8icAspcD1cG055xxBZ9yC68M= github.com/openshift/api v0.0.0-20260702202555-ef71f942ef6c h1:X1B6zMjD7kmKcuv9Cxs4Zhq/ruJLt7BsywSWKOA9Jn4= github.com/openshift/api v0.0.0-20260702202555-ef71f942ef6c/go.mod h1:7WJ3IPaK6nmWT8bDcaNooHqd0H5WepjVqV/10VlkMEM= github.com/openshift/client-go v0.0.0-20260703082747-24d059aea27a h1:RfmstmmWz5/23/Tt8gBipICzC1VHQLgGBeQUQ1aWoUA= github.com/openshift/client-go v0.0.0-20260703082747-24d059aea27a/go.mod h1:fNuXvm6bMJff2AxiRb9CR5L3a9CLzVgcoSmHZ3l7FgU= +github.com/openshift/imagebuilder v1.2.21 h1:XX0tZVznWTxzYevvNVZ/0eeTzmgY6cfcT4/xjs5ToyU= +github.com/openshift/imagebuilder v1.2.21/go.mod h1:+L09sXUQ0RPdCU1tmzKrfBhqMlYvZtaA3MHb7aTjVU8= github.com/openshift/kubernetes v1.30.1-0.20260305123649-d18f3f005eaa h1:/gPMWR7fdCC3S4wHALD6Em+vztl1q9/cOpdMkFZwDus= github.com/openshift/kubernetes v1.30.1-0.20260305123649-d18f3f005eaa/go.mod h1:1r2FIoYrPU0110cjYlWAwNcbiqRPLWAgmZK4d0YeEZw= github.com/openshift/kubernetes/staging/src/k8s.io/api v0.0.0-20260305123649-d18f3f005eaa h1:ifOqAFthJWnT1HS6Sq2AcLQWNSJ1+XEiyA9eo+PIcR0= @@ -732,13 +723,16 @@ github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pin/tftp v2.1.0+incompatible/go.mod h1:xVpZOMCXTy+A5QMjEVN0Glwa1sUvaJhFXbr/aAxuxGY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -762,8 +756,8 @@ github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7q github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1 h1:+Wl/0aFp0hpuHM3H//KMft64WQ1yX9LdJY64Qm/gFCo= github.com/quasilyte/go-ruleguard v0.4.3-0.20240823090925-0fe6f58b47b1/go.mod h1:GJLgqsLeo4qgavUoL8JeGFNS7qcisx3awV/w9eWTmNI= github.com/quasilyte/go-ruleguard/dsl v0.3.22 h1:wd8zkOhSNr+I+8Qeciml08ivDt1pSXe60+5DqOpCjPE= @@ -794,8 +788,8 @@ github.com/ryancurrah/gomodguard v1.3.5 h1:cShyguSwUEeC0jS7ylOiG/idnd1TpJ1LfHGpV github.com/ryancurrah/gomodguard v1.3.5/go.mod h1:MXlEPQRxgfPQa62O8wzK3Ozbkv9Rkqr+wKjSxTdsNJE= github.com/ryanrolds/sqlclosecheck v0.5.1 h1:dibWW826u0P8jNLsLN+En7+RqWWTYrjCB9fJfSfdyCU= github.com/ryanrolds/sqlclosecheck v0.5.1/go.mod h1:2g3dUjoS6AL4huFdv6wn55WpLIDjY7ZgUR4J8HOO/XQ= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= +github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= +github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sanposhiho/wastedassign/v2 v2.1.0 h1:crurBF7fJKIORrV85u9UUpePDYGWnwvv3+A96WvwXT0= github.com/sanposhiho/wastedassign/v2 v2.1.0/go.mod h1:+oSmSC+9bQ+VUAxA66nBb0Z7N8CK7mscKTDYC6aIek4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= @@ -806,8 +800,8 @@ github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tM github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= github.com/sashamelentyev/usestdlibvars v1.28.0 h1:jZnudE2zKCtYlGzLVreNp5pmCdOxXUzwsMDBkR21cyQ= github.com/sashamelentyev/usestdlibvars v1.28.0/go.mod h1:9nl0jgOfHKWNFS43Ojw0i7aRoS4j6EBye3YBhmAIRF8= -github.com/secure-systems-lab/go-securesystemslib v0.9.0 h1:rf1HIbL64nUpEIZnjLZ3mcNEL9NBPB0iuVjyxvq3LZc= -github.com/secure-systems-lab/go-securesystemslib v0.9.0/go.mod h1:DVHKMcZ+V4/woA/peqr+L0joiRXbPpQ042GgJckkFgw= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= github.com/securego/gosec/v2 v2.21.4 h1:Le8MSj0PDmOnHJgUATjD96PaXRvCpKC+DGJvwyy0Mlk= github.com/securego/gosec/v2 v2.21.4/go.mod h1:Jtb/MwRQfRxCXyCm1rfM1BEiiiTfUOdyzzAhlr6lUTA= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -818,14 +812,14 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= github.com/sigstore/fulcio v1.6.6 h1:XaMYX6TNT+8n7Npe8D94nyZ7/ERjEsNGFC+REdi/wzw= github.com/sigstore/fulcio v1.6.6/go.mod h1:BhQ22lwaebDgIxVBEYOOqLRcN5+xOV+C9bh/GUXRhOk= -github.com/sigstore/protobuf-specs v0.4.1 h1:5SsMqZbdkcO/DNHudaxuCUEjj6x29tS2Xby1BxGU7Zc= -github.com/sigstore/protobuf-specs v0.4.1/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= -github.com/sigstore/rekor v1.3.10 h1:/mSvRo4MZ/59ECIlARhyykAlQlkmeAQpvBPlmJtZOCU= -github.com/sigstore/rekor v1.3.10/go.mod h1:JvryKJ40O0XA48MdzYUPu0y4fyvqt0C4iSY7ri9iu3A= -github.com/sigstore/sigstore v1.9.3 h1:y2qlTj+vh+Or3ictKuR3JUFawZPdDxAjrWkeFhon0OQ= -github.com/sigstore/sigstore v1.9.3/go.mod h1:VwYkiw0G0dRtwL25KSs04hCyVFF6CYMd/qvNeYrl7EQ= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sigstore/protobuf-specs v0.5.0 h1:F8YTI65xOHw70NrvPwJ5PhAzsvTnuJMGLkA4FIkofAY= +github.com/sigstore/protobuf-specs v0.5.0/go.mod h1:+gXR+38nIa2oEupqDdzg4qSBT0Os+sP7oYv6alWewWc= +github.com/sigstore/rekor v1.5.0 h1:rL7SghHd5HLCtsCrxw0yQg+NczGvM75EjSPPWuGjaiQ= +github.com/sigstore/rekor v1.5.0/go.mod h1:D7JoVCUkxwQOpPDNYeu+CE8zeBC18Y5uDo6tF8s2rcQ= +github.com/sigstore/sigstore v1.10.4 h1:ytOmxMgLdcUed3w1SbbZOgcxqwMG61lh1TmZLN+WeZE= +github.com/sigstore/sigstore v1.10.4/go.mod h1:tDiyrdOref3q6qJxm2G+JHghqfmvifB7hw+EReAfnbI= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/sivchari/containedctx v1.0.3 h1:x+etemjbsh2fB5ewm5FeLNi5bUjK0V8n0RB+Wwfd0XE= github.com/sivchari/containedctx v1.0.3/go.mod h1:c1RDvCbnJLtH4lLcYD/GqwiBSSf4F5Qk0xld2rBqzJ4= github.com/sivchari/tenv v1.12.1 h1:+E0QzjktdnExv/wwsnnyk4oqZBUfuh89YMQT1cyuvSY= @@ -836,22 +830,22 @@ github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= github.com/sonatard/noctx v0.1.0 h1:JjqOc2WN16ISWAjAk8M5ej0RfExEXtkEyExl2hLW+OM= github.com/sonatard/noctx v0.1.0/go.mod h1:0RvBxqY8D4j9cTTTWE8ylt2vqj2EPI8fHmrxHdsaZ2c= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/sourcegraph/go-diff v0.7.0 h1:9uLlrd5T46OXs5qpp8L/MTltk0zikUGi0sNNyCpA8G0= github.com/sourcegraph/go-diff v0.7.0/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.10.0 h1:a5/WeUlSDCvV5a45ljW2ZFtV0bTDpkfSAj3uqB6Sc+0= -github.com/spf13/cobra v1.10.0/go.mod h1:9dhySC7dnTtEiqzmqfkLj47BslqLCUPMXjG2lj/NgoE= +github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= +github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= +github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= +github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.8/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.20.0-alpha.6 h1:f65Cr/+2qk4GfHC0xqT/isoupQppwN5+VLRztUGTDbY= -github.com/spf13/viper v1.20.0-alpha.6/go.mod h1:CGBZzv0c9fOUASm6rfus4wdeIjR/04NOLq1P4KRhX3k= +github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= +github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY= github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.2.0 h1:i8pxvGrt1+4G0czLr/WnmyH7zbZ8Bg8etvARQ1rpyl4= @@ -898,16 +892,14 @@ github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3 h1:y4mJRFlM6fUyP github.com/timakin/bodyclose v0.0.0-20241017074812-ed6a65f985e3/go.mod h1:mkjARE7Yr8qU23YcGMSALbIxTQ9r9QBVahQOBRfU460= github.com/timonwong/loggercheck v0.10.1 h1:uVZYClxQFpw55eh+PIoqM7uAOHMrhVcDoWDery9R8Lg= github.com/timonwong/loggercheck v0.10.1/go.mod h1:HEAWU8djynujaAVX7QI65Myb8qgfcZ1uKbdpg3ZzKl8= -github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399 h1:e/5i7d4oYZ+C1wj2THlRK+oAhjeS/TRQwMfkIuet3w0= -github.com/titanous/rocacheck v0.0.0-20171023193734-afe73141d399/go.mod h1:LdwHTNJT99C5fTAzDz0ud328OgXz+gierycbcIx2fRs= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= github.com/tomarrell/wrapcheck/v2 v2.10.0 h1:SzRCryzy4IrAH7bVGG4cK40tNUhmVmMDuJujy4XwYDg= github.com/tomarrell/wrapcheck/v2 v2.10.0/go.mod h1:g9vNIyhb5/9TQgumxQyOEqDHsmGYcGsVMOx/xGkqdMo= github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= -github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ultraware/funlen v0.1.0 h1:BuqclbkY6pO+cvxoq7OsktIXZpgBSkYTQtmwhAK81vI= github.com/ultraware/funlen v0.1.0/go.mod h1:XJqmOQja6DpxarLj6Jj1U7JuoS8PvL4nEqDaQhy22p4= github.com/ultraware/whitespace v0.1.1 h1:bTPOGejYFulW3PkcrqkeQwOd6NKOOXvmGD9bo/Gk8VQ= @@ -916,8 +908,8 @@ github.com/uudashr/gocognit v1.2.0 h1:3BU9aMr1xbhPlvJLSydKwdLN3tEUUrzPSSM8S4hDYR github.com/uudashr/gocognit v1.2.0/go.mod h1:k/DdKPI6XBZO1q7HgoV2juESI2/Ofj9AcHPZhBBdrTU= github.com/uudashr/iface v1.2.0 h1:ECJjh5q/1Zmnv/2yFpWV6H3oMg5+Mo+vL0aqw9Gjazo= github.com/uudashr/iface v1.2.0/go.mod h1:Ux/7d/rAF3owK4m53cTVXL4YoVHKNqnoOeQHn2xrlp0= -github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= -github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/vbatts/tar-split v0.12.2 h1:w/Y6tjxpeiFMR47yzZPlPj/FcPLpXbTUi/9H7d3CPa4= +github.com/vbatts/tar-split v0.12.2/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= github.com/vincent-petithory/dataurl v0.0.0-20160330182126-9a301d65acbb/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= github.com/vincent-petithory/dataurl v1.0.0 h1:cXw+kPto8NLuJtlMsI152irrVw9fRDX8AbShPRpg2CI= github.com/vincent-petithory/dataurl v1.0.0/go.mod h1:FHafX5vmDzyP+1CQATJn7WFKc9CvnvxyvZy6I1MrG/U= @@ -971,24 +963,22 @@ go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0= go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0= go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= -go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= -go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0 h1:KemlMZlVwBSEGaO91WKgp41BBFsnWqqj9sKRwmOqC40= go.opentelemetry.io/contrib/instrumentation/github.com/emicklei/go-restful/otelrestful v0.44.0/go.mod h1:uq8DrRaen3suIWTpdR/JNHCGpurSvMv9D5Nr5CU5TXc= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0 h1:YH4g8lQroajqUwWbq/tr2QX1JFmEXaDLgG+ew9bLMWo= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.63.0/go.mod h1:fvPi2qXDqFs8M4B4fmJhE92TyQs9Ydjlg3RvfUp+NbQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= go.opentelemetry.io/contrib/propagators/b3 v1.19.0 h1:ulz44cpm6V5oAeg5Aw9HyqGFMS6XM7untlMEhD7YzzA= go.opentelemetry.io/contrib/propagators/b3 v1.19.0/go.mod h1:OzCmE2IVS+asTI+odXQstRGVfXQ4bXv9nMBRK0nNyqQ= go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= @@ -997,8 +987,10 @@ go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4A go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= -go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.podman.io/storage v1.62.0 h1:0QjX1XlzVmbiaulb+aR/CG6p9+pzaqwIeZPe3tEjHbY= +go.podman.io/storage v1.62.0/go.mod h1:A3UBK0XypjNZ6pghRhuxg62+2NIm5lcUGv/7XyMhMUI= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/automaxprocs v1.6.0 h1:O3y2/QNTOdbF+e/dpXNNW7Rx2hZ4sTIPyybbxyNqTUs= @@ -1007,8 +999,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= @@ -1025,8 +1017,8 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329 h1:9kj3STMvgqy3YA4VQXBrN7925ICMxD5wzMRcgA30588= -golang.org/x/exp v0.0.0-20250103183323-7d7fa50e5329/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= +golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20241108190413-2d47ceb2692f h1:WTyX8eCCyfdqiPYkRGm0MqElSfYFH3yR1+rl/mct9sA= @@ -1110,7 +1102,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1146,8 +1137,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -1195,10 +1186,10 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls= -google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409 h1:merA0rdPeUV3YIIfHHcH4qBkiQAc1nfCKSI7lB4cV2M= +google.golang.org/genproto/googleapis/api v0.0.0-20260128011058-8636f8732409/go.mod h1:fl8J1IvUjCilwZzQowmw2b7HQB2eAuYBabMXzWurF+I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= @@ -1212,8 +1203,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0 h1:FVCohIoYO7IJoDDVpV2pdq7SgrMH6wHnuTyrdrxJNoY= gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0/go.mod h1:OdE7CF6DbADk7lN8LIKRzRJTTZXIjtWgA5THM5lhBAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/controller/bootimage/boot_image_controller_test.go b/pkg/controller/bootimage/boot_image_controller_test.go index 5858b0d925..786c6d730d 100644 --- a/pkg/controller/bootimage/boot_image_controller_test.go +++ b/pkg/controller/bootimage/boot_image_controller_test.go @@ -1,12 +1,15 @@ package bootimage import ( + "bytes" "context" + "os" "testing" "github.com/coreos/stream-metadata-go/stream" "github.com/coreos/stream-metadata-go/stream/rhcos" osconfigv1 "github.com/openshift/api/config/v1" + machinev1 "github.com/openshift/api/machine/v1" machinev1beta1 "github.com/openshift/api/machine/v1beta1" opv1 "github.com/openshift/api/operator/v1" configlistersv1 "github.com/openshift/client-go/config/listers/config/v1" @@ -16,9 +19,12 @@ import ( corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" + corelisterv1 "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" ) @@ -1076,3 +1082,244 @@ func TestResetClusterBootImage(t *testing.T) { }) } } + +func TestSyncMAPIMachineSetOSStreamLabel(t *testing.T) { + // Uses AWS platform so that when the controller does NOT skip (supported/no-label), + // it continues into reconcilePlatform and fails on the empty provider spec. + // This proves the skip path works: unsupported streams return early with no error + // and log the skip message, while supported streams continue processing. + newTestController := func() *Controller { + cv := &osconfigv1.ClusterVersion{ + ObjectMeta: v1.ObjectMeta{Name: "version"}, + Status: osconfigv1.ClusterVersionStatus{ + Desired: osconfigv1.Release{Architecture: ""}, + History: []osconfigv1.UpdateHistory{ + {State: osconfigv1.CompletedUpdate, Version: "4.18.0"}, + }, + }, + } + cvIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + require.NoError(t, cvIndexer.Add(cv)) + + infra := &osconfigv1.Infrastructure{ + ObjectMeta: v1.ObjectMeta{Name: "cluster"}, + Status: osconfigv1.InfrastructureStatus{ + PlatformStatus: &osconfigv1.PlatformStatus{ + Type: osconfigv1.AWSPlatformType, + }, + }, + } + infraIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + require.NoError(t, infraIndexer.Add(infra)) + + return &Controller{ + kubeClient: fake.NewClientset(), + clusterVersionLister: configlistersv1.NewClusterVersionLister(cvIndexer), + infraLister: configlistersv1.NewInfrastructureLister(infraIndexer), + mapiBootImageState: map[string]BootImageState{}, + fgHandler: ctrlcommon.NewFeatureGatesHardcodedHandler(nil, nil), + } + } + + configMap := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: ctrlcommon.BootImagesConfigMapName, + Namespace: ctrlcommon.MCONamespace, + }, + } + + cases := []struct { + name string + labels map[string]string + expectErr bool + expectLog string + rejectLog string + }{ + { + name: "unsupported stream rhel-10 should skip", + labels: map[string]string{OSStreamLabelKey: "rhel-10"}, + expectErr: false, + expectLog: "has unsupported stream: rhel-10, skipping boot image update", + }, + { + name: "unsupported custom stream should skip", + labels: map[string]string{OSStreamLabelKey: "custom-stream"}, + expectErr: false, + expectLog: "has unsupported stream: custom-stream, skipping boot image update", + }, + { + name: "supported stream rhel-9 should continue processing", + labels: map[string]string{OSStreamLabelKey: SupportedOSStream}, + expectErr: true, + rejectLog: "skipping boot image update", + }, + { + name: "no osstream label should continue processing", + labels: map[string]string{}, + expectErr: true, + rejectLog: "skipping boot image update", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + klog.LogToStderr(false) + klog.SetOutput(&buf) + defer func() { + klog.SetOutput(os.Stderr) + klog.LogToStderr(true) + }() + + ctrl := newTestController() + + ms := &machinev1beta1.MachineSet{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-machineset", + Labels: tc.labels, + Annotations: map[string]string{ + MachineSetArchAnnotationKey: "kubernetes.io/arch=amd64", + }, + }, + Spec: machinev1beta1.MachineSetSpec{ + Template: machinev1beta1.MachineTemplateSpec{ + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &runtime.RawExtension{Raw: []byte("{}")}, + }, + }, + }, + }, + } + + _, _, err := ctrl.syncMAPIMachineSet(ms, configMap) + klog.Flush() + output := buf.String() + + if tc.expectErr { + assert.Error(t, err, "supported/no-label stream should continue processing and error on empty provider spec") + } else { + assert.NoError(t, err, "unsupported stream should be skipped without error") + } + if tc.expectLog != "" { + assert.Contains(t, output, tc.expectLog) + } + if tc.rejectLog != "" { + assert.NotContains(t, output, tc.rejectLog) + } + }) + } +} + +func TestSyncControlPlaneMachineSetOSStreamLabel(t *testing.T) { + newTestController := func() *Controller { + infra := &osconfigv1.Infrastructure{ + ObjectMeta: v1.ObjectMeta{Name: "cluster"}, + Status: osconfigv1.InfrastructureStatus{ + PlatformStatus: &osconfigv1.PlatformStatus{ + Type: osconfigv1.AWSPlatformType, + }, + }, + } + infraIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + require.NoError(t, infraIndexer.Add(infra)) + + cm := &corev1.ConfigMap{ + ObjectMeta: v1.ObjectMeta{ + Name: ctrlcommon.BootImagesConfigMapName, + Namespace: ctrlcommon.MCONamespace, + }, + } + cmIndexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{}) + require.NoError(t, cmIndexer.Add(cm)) + + return &Controller{ + kubeClient: fake.NewClientset(), + infraLister: configlistersv1.NewInfrastructureLister(infraIndexer), + mcoCmLister: corelisterv1.NewConfigMapLister(cmIndexer), + cpmsBootImageState: map[string]BootImageState{}, + fgHandler: ctrlcommon.NewFeatureGatesHardcodedHandler(nil, nil), + } + } + + cases := []struct { + name string + labels map[string]string + expectErr bool + expectLog string + rejectLog string + }{ + { + name: "unsupported stream rhel-10 should skip", + labels: map[string]string{OSStreamLabelKey: "rhel-10"}, + expectErr: false, + expectLog: "has unsupported stream: rhel-10, skipping boot image update", + }, + { + name: "unsupported custom stream should skip", + labels: map[string]string{OSStreamLabelKey: "custom-stream"}, + expectErr: false, + expectLog: "has unsupported stream: custom-stream, skipping boot image update", + }, + { + name: "supported stream rhel-9 should continue processing", + labels: map[string]string{OSStreamLabelKey: SupportedOSStream}, + expectErr: true, + rejectLog: "skipping boot image update", + }, + { + name: "no osstream label should continue processing", + labels: map[string]string{}, + expectErr: true, + rejectLog: "skipping boot image update", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + var buf bytes.Buffer + klog.LogToStderr(false) + klog.SetOutput(&buf) + defer func() { + klog.SetOutput(os.Stderr) + klog.LogToStderr(true) + }() + + ctrl := newTestController() + + cpms := &machinev1.ControlPlaneMachineSet{ + ObjectMeta: v1.ObjectMeta{ + Name: "test-cpms", + Labels: tc.labels, + }, + Spec: machinev1.ControlPlaneMachineSetSpec{ + Template: machinev1.ControlPlaneMachineSetTemplate{ + OpenShiftMachineV1Beta1Machine: &machinev1.OpenShiftMachineV1Beta1MachineTemplate{ + Spec: machinev1beta1.MachineSpec{ + ProviderSpec: machinev1beta1.ProviderSpec{ + Value: &kruntime.RawExtension{Raw: []byte("{}")}, + }, + }, + }, + }, + }, + } + + err := ctrl.syncControlPlaneMachineSet(cpms) + klog.Flush() + output := buf.String() + + if tc.expectErr { + assert.Error(t, err, "supported/no-label stream should continue processing and error on empty provider spec") + } else { + assert.NoError(t, err, "unsupported stream should be skipped without error") + } + if tc.expectLog != "" { + assert.Contains(t, output, tc.expectLog) + } + if tc.rejectLog != "" { + assert.NotContains(t, output, tc.rejectLog) + } + }) + } +} diff --git a/pkg/controller/bootstrap/bootstrap.go b/pkg/controller/bootstrap/bootstrap.go index 06c567ca8e..4a60911e4d 100644 --- a/pkg/controller/bootstrap/bootstrap.go +++ b/pkg/controller/bootstrap/bootstrap.go @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/runtime/serializer/json" + k8sversion "k8s.io/apimachinery/pkg/util/version" yamlutil "k8s.io/apimachinery/pkg/util/yaml" kscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/klog/v2" @@ -40,6 +41,7 @@ import ( kubeletconfig "github.com/openshift/machine-config-operator/pkg/controller/kubelet-config" "github.com/openshift/machine-config-operator/pkg/controller/render" "github.com/openshift/machine-config-operator/pkg/controller/template" + "github.com/openshift/machine-config-operator/pkg/daemon/osrelease" "github.com/openshift/machine-config-operator/pkg/version" ) @@ -50,17 +52,24 @@ type Bootstrap struct { // dir used to read pools and user defined machineconfigs. manifestDir string // pull secret file - pullSecretFile string - // OSImageStreams factory. Used for testing purposes. + pullSecretFile string imageStreamFactory osimagestream.ImageStreamFactory + // ImagesInspector factory, used for OSImageStream discovery and to validate + // pre-built images for hybrid OCL at bootstrap time. Used for testing purposes. + inspectorFactory osimagestream.ImagesInspectorFactory } // New returns controller for bootstrap -func New(templatesDir, manifestDir, pullSecretFile string) *Bootstrap { +func New(templatesDir, manifestDir, pullSecretFile string, inspectorFactory osimagestream.ImagesInspectorFactory) *Bootstrap { + if inspectorFactory == nil { + inspectorFactory = &osimagestream.DefaultImagesInspectorFactory{} + } return &Bootstrap{ - templatesDir: templatesDir, - manifestDir: manifestDir, - pullSecretFile: pullSecretFile, + templatesDir: templatesDir, + manifestDir: manifestDir, + pullSecretFile: pullSecretFile, + imageStreamFactory: osimagestream.NewDefaultStreamSourceFactory(inspectorFactory), + inspectorFactory: inspectorFactory, } } @@ -237,8 +246,12 @@ func (b *Bootstrap) Run(destDir string) error { return fmt.Errorf("error filtering pools: %w", err) } - // Enable OSImageStreams if the FeatureGate is active and the control plane topology is not external (e.g., Hypershift) - if osimagestream.IsFeatureEnabled(fgHandler) && cconfig.Spec.Infra.Status.ControlPlaneTopology != apicfgv1.ExternalTopologyMode { + // Enable OSImageStreams if the FeatureGate is active. + // Previously this also excluded ExternalTopologyMode (HyperShift) because + // HyperShift did not yet write stream selection into the synthetic MCP. + // Now that HyperShift writes 99_osimagestream.yaml into the MCC template + // directory (openshift/hypershift#8792), the guard is no longer needed. + if osimagestream.IsFeatureEnabled(fgHandler) { osImageStream, err = b.fetchOSImageStream( imageStream, cconfig, @@ -348,11 +361,19 @@ func (b *Bootstrap) Run(destDir string) error { } } + sysCtxFactory := buildSysContextFactory(pullSecret, cconfig, imgCfg, icspRules, idmsRules, itmsRules) + // Create component MachineConfigs for pre-built images for hybrid OCL // This must happen BEFORE render.RunBootstrap() so they can be merged into rendered MCs if len(machineOSConfigs) > 0 { klog.Infoln("Found machineOSConfig(s) at install time, will install cluster with Image Mode enabled") - preBuiltImageMCs, err := createPreBuiltImageMachineConfigs(machineOSConfigs, pools) + + preBuiltImageCtx, preBuiltImageCancel := context.WithTimeout(context.Background(), time.Minute) + defer preBuiltImageCancel() + + preBuiltImageInspector := b.inspectorFactory.ForContext(sysCtxFactory) + + preBuiltImageMCs, err := createPreBuiltImageMachineConfigs(preBuiltImageCtx, machineOSConfigs, pools, cconfig, preBuiltImageInspector) if err != nil { return fmt.Errorf("failed to create pre-built image MachineConfigs: %w", err) } @@ -360,7 +381,8 @@ func (b *Bootstrap) Run(destDir string) error { klog.Infof("Successfully created %d pre-built image component MachineConfigs for hybrid OCL.", len(preBuiltImageMCs)) } - fpools, gconfigs, err := render.RunBootstrap(pools, configs, cconfig, osImageStream) + inspector := osimagestream.NewStreamClassInspector(b.inspectorFactory, sysCtxFactory) + fpools, gconfigs, err := render.RunBootstrap(context.TODO(), pools, configs, cconfig, osImageStream, inspector) if err != nil { return err } @@ -502,44 +524,51 @@ func (b *Bootstrap) fetchOSImageStream( ctx, cancel := context.WithTimeout(context.Background(), time.Minute) defer cancel() - sysCtxFactory := func() (*imageutils.SysContext, error) { - sysCtxBuilder := imageutils.NewSysContextBuilder(). - WithControllerConfig(cconfig). - WithSecret(pullSecret) + sysCtxFactory := buildSysContextFactory(pullSecret, cconfig, imgCfg, icspRules, idmsRules, itmsRules) - registriesConfig, err := imageutils.GenerateRegistriesConfig(imgCfg, icspRules, idmsRules, itmsRules) - if err != nil { - return nil, fmt.Errorf("failed to generate registries config for OSImageStreams fetching: %w", err) - } - if registriesConfig != nil { - sysCtxBuilder.WithRegistriesConfig(registriesConfig) - } - - return sysCtxBuilder.Build() - } - - factory := b.getImageStreamFactory() - osImageStream, err := factory.Create(ctx, sysCtxFactory, osimagestream.CreateOptions{ + factory := b.imageStreamFactory + createOpts := osimagestream.CreateOptions{ ExistingOSImageStream: existingOSImageStream, - ReleaseImageStream: imageStream, CliImages: &osimagestream.OSImageTuple{ OSImage: cconfig.Spec.BaseOSContainerImage, OSExtensionsImage: cconfig.Spec.BaseOSExtensionsContainerImage, }, - }) + } + if imageStream != nil { + createOpts.ReleaseImageStream = imageStream + } else { + createOpts.ReleaseImage = cconfig.Spec.ReleaseImage + } + osImageStream, err := factory.Create(ctx, sysCtxFactory, createOpts) if err != nil { return nil, fmt.Errorf("error inspecting available OSImageStreams: %w", err) } return osImageStream, nil } -// Returns the embedded ImageStreamFactory or constructs a new default one. Used primarily for testing. -func (b *Bootstrap) getImageStreamFactory() osimagestream.ImageStreamFactory { - if b.imageStreamFactory != nil { - return b.imageStreamFactory - } +func buildSysContextFactory( + pullSecret *corev1.Secret, + cconfig *mcfgv1.ControllerConfig, + imgCfg *apicfgv1.Image, + icspRules []*apioperatorsv1alpha1.ImageContentSourcePolicy, + idmsRules []*apicfgv1.ImageDigestMirrorSet, + itmsRules []*apicfgv1.ImageTagMirrorSet, +) imageutils.SysContextFactory { + return func() (*imageutils.SysContext, error) { + builder := imageutils.NewSysContextBuilder(). + WithControllerConfig(cconfig). + WithSecret(pullSecret) - return osimagestream.NewDefaultStreamSourceFactory(&osimagestream.DefaultImagesInspectorFactory{}) + registriesConfig, err := imageutils.GenerateRegistriesConfig(imgCfg, icspRules, idmsRules, itmsRules) + if err != nil { + return nil, fmt.Errorf("failed to generate registries config: %w", err) + } + if registriesConfig != nil { + builder.WithRegistriesConfig(registriesConfig) + } + + return builder.Build() + } } func getValidPullSecretFromBytes(sData []byte) (*corev1.Secret, error) { @@ -606,10 +635,13 @@ func parseManifests(filename string, r io.Reader) ([]manifest, error) { // that have associated MachineOSConfigs with pre-built image annotations. // These component MCs will be automatically merged into rendered MCs by the render controller. // This function validates pre-built images at bootstrap time and will fail if: -// - The annotation is missing for a MOSC that hasn't been seeded yet (required for install-time OCL) -// - The image format is invalid +// - The annotation is missing for a MOSC that hasn't been seeded yet (required for install-time OCL) +// - The image format is invalid +// - The image's registry is unreachable, or its OS version/base image doesn't match the cluster +// being installed (see validatePreBuiltImageVersion) +// // MOSCs without the annotation are skipped only if seeding has already completed. -func createPreBuiltImageMachineConfigs(machineOSConfigs []*mcfgv1.MachineOSConfig, pools []*mcfgv1.MachineConfigPool) ([]*mcfgv1.MachineConfig, error) { +func createPreBuiltImageMachineConfigs(ctx context.Context, machineOSConfigs []*mcfgv1.MachineOSConfig, pools []*mcfgv1.MachineConfigPool, cconfig *mcfgv1.ControllerConfig, inspector osimagestream.ImagesInspector) ([]*mcfgv1.MachineConfig, error) { var preBuiltImageMCs []*mcfgv1.MachineConfig var validationErrors []error @@ -655,6 +687,14 @@ func createPreBuiltImageMachineConfigs(machineOSConfigs []*mcfgv1.MachineOSConfi continue } + // Validate that the pre-built image's base OS matches the cluster + // being installed, and that its registry is accessible. + if err := validatePreBuiltImageVersion(ctx, inspector, preBuiltImage, cconfig.Spec.BaseOSContainerImage); err != nil { + validationErrors = append(validationErrors, fmt.Errorf("pre-built image %q for MachineOSConfig %q (pool %q) failed validation: %w", + preBuiltImage, mosc.Name, poolName, err)) + continue + } + // Create the component MachineConfig mc := ctrlcommon.CreatePreBuiltImageMachineConfig(poolName, preBuiltImage, buildconstants.PreBuiltImageAnnotationKey) preBuiltImageMCs = append(preBuiltImageMCs, mc) @@ -699,3 +739,114 @@ func validatePreBuiltImage(imageSpec string) error { return nil } + +// preBuiltImageBaseOSLabelKey is the label MCO's own Containerfile embeds +// with the base OS image used to build the layered image (see +// Containerfile.on-cluster-build-template). +const preBuiltImageBaseOSLabelKey = "baseOSContainerImage" + +// osReleasePath is the path to the os-release file inside a container image. +const osReleasePath = "/etc/os-release" + +// imageDigest parses an image reference and returns its canonical digest. +func imageDigest(imageSpec string) (digest.Digest, error) { + ref, err := reference.ParseNamed(imageSpec) + if err != nil { + return "", fmt.Errorf("invalid image reference %q: %w", imageSpec, err) + } + canonical, ok := ref.(reference.Canonical) + if !ok { + return "", fmt.Errorf("image reference %q is not in digested form", imageSpec) + } + return canonical.Digest(), nil +} + +// openshiftVersionFromImage fetches /etc/os-release from the given image and +// extracts its OPENSHIFT_VERSION field (e.g. "4.16"). Returns an error if the +// file can't be fetched/parsed or the field is absent. +func openshiftVersionFromImage(ctx context.Context, inspector osimagestream.ImagesInspector, imageSpec string) (string, error) { + data, err := inspector.FetchImageFile(ctx, imageSpec, osReleasePath) + if err != nil { + return "", fmt.Errorf("could not fetch %s from image %q: %w", osReleasePath, imageSpec, err) + } + osRelease, err := osrelease.LoadOSRelease(string(data), string(data)) + if err != nil { + return "", fmt.Errorf("could not parse %s from image %q: %w", osReleasePath, imageSpec, err) + } + openshiftVersion, ok := osRelease.OSRelease().ADDITIONAL_FIELDS["OPENSHIFT_VERSION"] + if !ok || openshiftVersion == "" { + return "", fmt.Errorf("image %q has no OPENSHIFT_VERSION field in %s", imageSpec, osReleasePath) + } + return openshiftVersion, nil +} + +// sameMajorMinor compares two version strings by major.minor only. +func sameMajorMinor(a, b string) (bool, error) { + va, err := k8sversion.ParseGeneric(a) + if err != nil { + return false, fmt.Errorf("could not parse version %q: %w", a, err) + } + vb, err := k8sversion.ParseGeneric(b) + if err != nil { + return false, fmt.Errorf("could not parse version %q: %w", b, err) + } + return va.Major() == vb.Major() && va.Minor() == vb.Minor(), nil +} + +// validatePreBuiltImageDigestFallback compares the pre-built image's +// baseOSContainerImage label digest against the cluster's resolved base OS +// image digest. Used only when OCP-version comparison isn't possible. +func validatePreBuiltImageDigestFallback(labels map[string]string, imageSpec, expectedBaseOSImage string) error { + label := labels[preBuiltImageBaseOSLabelKey] + if label == "" { + klog.Warningf("pre-built image %q has no OPENSHIFT_VERSION and no %q label; skipping version verification", imageSpec, preBuiltImageBaseOSLabelKey) + return nil + } + labelDigest, err := imageDigest(label) + if err != nil { + return fmt.Errorf("pre-built image %q has an invalid %q label: %w", imageSpec, preBuiltImageBaseOSLabelKey, err) + } + expectedDigest, err := imageDigest(expectedBaseOSImage) + if err != nil { + return fmt.Errorf("cluster's resolved base OS image %q is invalid: %w", expectedBaseOSImage, err) + } + if labelDigest != expectedDigest { + return fmt.Errorf("pre-built image %q base OS image %q (digest %s) does not match the cluster's resolved base OS image %q (digest %s)", + imageSpec, label, labelDigest, expectedBaseOSImage, expectedDigest) + } + return nil +} + +// validatePreBuiltImageVersion inspects the pre-built image (proving registry +// accessibility as a side effect) and verifies it matches the cluster being +// installed: primarily by comparing OPENSHIFT_VERSION from /etc/os-release +// against version.ReleaseVersion; if that isn't determinable, it falls back +// to a digest comparison via the baseOSContainerImage label. If neither +// signal is available, it logs a warning and lets the image through +// unverified. Inspection/registry failures are always a hard error. +func validatePreBuiltImageVersion(ctx context.Context, inspector osimagestream.ImagesInspector, imageSpec, expectedBaseOSImage string) error { + imgVersion, versionErr := openshiftVersionFromImage(ctx, inspector, imageSpec) + if versionErr == nil { + matches, err := sameMajorMinor(imgVersion, version.ReleaseVersion) + if err != nil { + return fmt.Errorf("could not compare OCP versions for pre-built image %q: %w", imageSpec, err) + } + if !matches { + return fmt.Errorf("pre-built image %q OCP version %q does not match the cluster's OCP version %q", imageSpec, imgVersion, version.ReleaseVersion) + } + return nil + } + klog.V(4).Infof("could not determine OCP version for pre-built image %q, falling back to digest check: %v", imageSpec, versionErr) + + results, err := inspector.Inspect(ctx, imageSpec) + if err != nil { + return fmt.Errorf("could not inspect pre-built image %q: %w", imageSpec, err) + } + if len(results) != 1 { + return fmt.Errorf("unexpected inspection result count for pre-built image %q", imageSpec) + } + if results[0].Error != nil { + return fmt.Errorf("could not access pre-built image %q (registry unreachable or image not found): %w", imageSpec, results[0].Error) + } + return validatePreBuiltImageDigestFallback(results[0].InspectInfo.Labels, imageSpec, expectedBaseOSImage) +} diff --git a/pkg/controller/bootstrap/bootstrap_test.go b/pkg/controller/bootstrap/bootstrap_test.go index 2dfd6ee4d1..9d2b48dedf 100644 --- a/pkg/controller/bootstrap/bootstrap_test.go +++ b/pkg/controller/bootstrap/bootstrap_test.go @@ -10,16 +10,22 @@ import ( "strings" "testing" + imgtypes "github.com/containers/image/v5/types" ign3types "github.com/coreos/ignition/v2/config/v3_5/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/diff" + "sigs.k8s.io/yaml" + apicfgv1 "github.com/openshift/api/config/v1" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" mcoResourceRead "github.com/openshift/machine-config-operator/lib/resourceread" + buildconstants "github.com/openshift/machine-config-operator/pkg/controller/build/constants" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" "github.com/openshift/machine-config-operator/pkg/imageutils" "github.com/openshift/machine-config-operator/pkg/osimagestream" + "github.com/openshift/machine-config-operator/pkg/version" ) func TestParseManifests(t *testing.T) { @@ -168,20 +174,85 @@ type fakeImageStreamFactory struct { stream *mcfgv1.OSImageStream // Whether the Create method was called. createCalled bool + // The CreateOptions passed to the last Create call. + lastCreateOptions osimagestream.CreateOptions } -func (f *fakeImageStreamFactory) Create(_ context.Context, _ imageutils.SysContextFactory, _ osimagestream.CreateOptions) (*mcfgv1.OSImageStream, error) { +func (f *fakeImageStreamFactory) Create(_ context.Context, _ imageutils.SysContextFactory, createOptions osimagestream.CreateOptions) (*mcfgv1.OSImageStream, error) { f.createCalled = true + f.lastCreateOptions = createOptions return f.stream, nil } +// Implements a fake ImagesInspector. FetchImageFileFunc and InspectFunc are +// stubbable per-test; if left nil, the corresponding method returns an error, +// simulating an unreachable registry. +type fakeImagesInspector struct { + FetchImageFileFunc func(ctx context.Context, image, path string) ([]byte, error) + InspectFunc func(ctx context.Context, image ...string) ([]imageutils.BulkInspectResult, error) +} + +func (f *fakeImagesInspector) FetchImageFile(ctx context.Context, image, path string) ([]byte, error) { + if f.FetchImageFileFunc != nil { + return f.FetchImageFileFunc(ctx, image, path) + } + return nil, fmt.Errorf("fakeImagesInspector: FetchImageFile not configured for image %q", image) +} + +func (f *fakeImagesInspector) Inspect(ctx context.Context, image ...string) ([]imageutils.BulkInspectResult, error) { + if f.InspectFunc != nil { + return f.InspectFunc(ctx, image...) + } + return nil, fmt.Errorf("fakeImagesInspector: Inspect not configured for image(s) %v", image) +} + +// Implements a fake ImagesInspectorFactory. +type fakeImagesInspectorFactory struct { + inspector *fakeImagesInspector + // Whether the ForContext method was called. + forContextCalled bool +} + +func (f *fakeImagesInspectorFactory) ForContext(_ imageutils.SysContextFactory) osimagestream.ImagesInspector { + f.forContextCalled = true + return f.inspector +} + +// fakeOSReleaseContent returns minimal /etc/os-release-style content with +// the given OPENSHIFT_VERSION field set. +func fakeOSReleaseContent(openshiftVersion string) []byte { + return []byte(fmt.Sprintf("ID=\"rhcos\"\nVERSION_ID=\"9\"\nOPENSHIFT_VERSION=%q\n", openshiftVersion)) +} + +// inspectResultWithLabels builds a successful imageutils.BulkInspectResult +// with the given labels for a single image. +func inspectResultWithLabels(image string, labels map[string]string) []imageutils.BulkInspectResult { + return []imageutils.BulkInspectResult{{ + Image: image, + InspectInfo: &imgtypes.ImageInspectInfo{Labels: labels}, + }} +} + +// inspectResultWithError builds a failed imageutils.BulkInspectResult for a +// single image, simulating an unreachable registry. +func inspectResultWithError(image string, err error) []imageutils.BulkInspectResult { + return []imageutils.BulkInspectResult{{ + Image: image, + Error: err, + }} +} + // Instantiates a new instance of the Bootstrap struct for testing. This also // does the following: // 1. Copies the data from testdata/bootstrap into a temp directory so that it // may be safely overwritten to test specific scenarios. // 2. Creates a fake ImageStreamFactory instance and wires it up to return an // OSImageStream. -func setupForBootstrapTest(t *testing.T) (*Bootstrap, *fakeImageStreamFactory, string, string) { +// 3. Creates a fake ImagesInspectorFactory instance and wires it up so that +// the pre-built image referenced by testdata/bootstrap/layered-worker.machineosconfig.yaml +// passes version validation (its /etc/os-release always reports an +// OPENSHIFT_VERSION matching version.ReleaseVersion). +func setupForBootstrapTest(t *testing.T) (*Bootstrap, *fakeImageStreamFactory, *fakeImagesInspectorFactory, string, string) { t.Helper() srcDir := t.TempDir() @@ -189,8 +260,6 @@ func setupForBootstrapTest(t *testing.T) (*Bootstrap, *fakeImageStreamFactory, s require.NoError(t, exec.Command("cp", "-r", "testdata/bootstrap/.", srcDir).Run()) - bootstrap := New("../../../templates", srcDir, filepath.Join(srcDir, "machineconfigcontroller-pull-secret")) - fakeFactory := &fakeImageStreamFactory{ stream: &mcfgv1.OSImageStream{ Status: mcfgv1.OSImageStreamStatus{ @@ -206,36 +275,138 @@ func setupForBootstrapTest(t *testing.T) (*Bootstrap, *fakeImageStreamFactory, s }, } + fakeInspectorFactory := &fakeImagesInspectorFactory{ + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return fakeOSReleaseContent(version.ReleaseVersion), nil + }, + }, + } + + bootstrap := New("../../../templates", srcDir, filepath.Join(srcDir, "machineconfigcontroller-pull-secret"), fakeInspectorFactory) bootstrap.imageStreamFactory = fakeFactory - return bootstrap, fakeFactory, srcDir, destDir + return bootstrap, fakeFactory, fakeInspectorFactory, srcDir, destDir +} + +type noopImagesInspectorFactory struct{} + +func (n *noopImagesInspectorFactory) ForContext(_ imageutils.SysContextFactory) osimagestream.ImagesInspector { + return &noopImagesInspector{} +} + +type noopImagesInspector struct{} + +func (n *noopImagesInspector) Inspect(_ context.Context, _ ...string) ([]imageutils.BulkInspectResult, error) { + return []imageutils.BulkInspectResult{{}}, nil +} + +func (n *noopImagesInspector) FetchImageFile(_ context.Context, _, _ string) ([]byte, error) { + return nil, nil } -// Ensures that when Hypershift is enabled that the OSImageStream value is not consumed. +// TestBootstrapRunHypershift validates OSImageStream behavior under +// ExternalTopologyMode (HyperShift). The ExternalTopologyMode guard was +// removed in CNTRLPLANE-3840 because HyperShift now writes +// 99_osimagestream.yaml into the MCC template directory +// (openshift/hypershift#8792). +// +// NOTE for feature gate graduation: when OSStreams is promoted to GA and +// the feature gate check is removed, these tests should still pass since +// the fixture has OSStreams enabled. If the fixture changes, update +// accordingly. func TestBootstrapRunHypershift(t *testing.T) { - bootstrap, fakeFactory, srcDir, destDir := setupForBootstrapTest(t) + disabledFG := apicfgv1.FeatureGate{ + TypeMeta: metav1.TypeMeta{ + APIVersion: apicfgv1.GroupVersion.String(), + Kind: "FeatureGate", + }, + ObjectMeta: metav1.ObjectMeta{Name: "cluster"}, + Status: apicfgv1.FeatureGateStatus{ + FeatureGates: []apicfgv1.FeatureGateDetails{{ + Version: "0.0.1-snapshot", + Enabled: []apicfgv1.FeatureGateAttributes{ + {Name: "OpenShiftPodSecurityAdmission"}, + }, + Disabled: []apicfgv1.FeatureGateAttributes{ + {Name: "OSStreams"}, + {Name: "SigstoreImageVerification"}, + }, + }}, + }, + } + + testCases := []struct { + name string + featureGateOverride *apicfgv1.FeatureGate + expectCreate bool + expectOSImages bool + expectReleaseImageUsed bool + }{ + { + name: "When OSStreams feature gate is enabled it should consume OSImageStream via ReleaseImage fallback", + expectCreate: true, + expectOSImages: true, + expectReleaseImageUsed: true, + }, + { + name: "When OSStreams feature gate is disabled it should not consume OSImageStream", + featureGateOverride: &disabledFG, + expectCreate: false, + expectOSImages: false, + }, + } - // Overwrite the default ControllerConfig with one that specifies an - // external control plane value e.g., Hypershift. - require.NoError(t, exec.Command("cp", "testdata/bootstrap-hypershift/machineconfigcontroller-controllerconfig.yaml", srcDir).Run()) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + bootstrap, fakeFactory, _, srcDir, destDir := setupForBootstrapTest(t) - err := bootstrap.Run(destDir) - require.NoError(t, err) + require.NoError(t, exec.Command("cp", "testdata/bootstrap-hypershift/machineconfigcontroller-controllerconfig.yaml", srcDir).Run()) - // Ensure that the values from the OSImageStream are *not* populated into the ControllerConfig. - assert.False(t, fakeFactory.createCalled) - cconfigBytes, err := os.ReadFile(filepath.Join(destDir, "controller-config", "machine-config-controller.yaml")) - require.NoError(t, err) - assert.NotContains(t, string(cconfigBytes), "baseOSContainerImage: registry.host.com/os:latest") - assert.NotContains(t, string(cconfigBytes), "baseOSExtensionsContainerImage: registry.host.com/extensions:latest") + if tc.featureGateOverride != nil { + fgBytes, err := yaml.Marshal(tc.featureGateOverride) + require.NoError(t, err) + require.NoError(t, os.WriteFile(filepath.Join(srcDir, "featuregate.yaml"), fgBytes, 0644)) + } + + err := bootstrap.Run(destDir) + require.NoError(t, err) + + assert.Equal(t, tc.expectCreate, fakeFactory.createCalled) + cconfigBytes, err := os.ReadFile(filepath.Join(destDir, "controller-config", "machine-config-controller.yaml")) + require.NoError(t, err) + + if tc.expectOSImages { + assert.Contains(t, string(cconfigBytes), "baseOSContainerImage: registry.host.com/os:latest") + assert.Contains(t, string(cconfigBytes), "baseOSExtensionsContainerImage: registry.host.com/extensions:latest") + } else { + assert.NotContains(t, string(cconfigBytes), "baseOSContainerImage: registry.host.com/os:latest") + assert.NotContains(t, string(cconfigBytes), "baseOSExtensionsContainerImage: registry.host.com/extensions:latest") + } + + if tc.expectReleaseImageUsed { + // HyperShift has no ImageStream in manifests, so fetchOSImageStream + // must fall back to cconfig.Spec.ReleaseImage for network-based discovery. + assert.Nil(t, fakeFactory.lastCreateOptions.ReleaseImageStream, + "ReleaseImageStream should be nil in HyperShift — no ImageStream in manifests") + assert.NotEmpty(t, fakeFactory.lastCreateOptions.ReleaseImage, + "ReleaseImage should be set from ControllerConfig.Spec.ReleaseImage as fallback") + } + }) + } } func TestBootstrapRun(t *testing.T) { - bootstrap, fakeFactory, _, destDir := setupForBootstrapTest(t) + bootstrap, fakeFactory, fakeInspectorFactory, _, destDir := setupForBootstrapTest(t) err := bootstrap.Run(destDir) require.NoError(t, err) + // testdata/bootstrap/layered-worker.machineosconfig.yaml carries a + // pre-built image annotation, so bootstrap-time validation must have + // used the (fake) ImagesInspector to check it. + assert.True(t, fakeInspectorFactory.forContextCalled) + for _, poolName := range []string{"master", "worker"} { t.Run(poolName, func(t *testing.T) { paths, err := filepath.Glob(filepath.Join(destDir, "machine-configs", fmt.Sprintf("rendered-%s-*.yaml", poolName))) @@ -340,3 +511,365 @@ func TestValidatePreBuiltImage(t *testing.T) { }) } } + +const ( + testDigestA = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + testDigestB = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" +) + +func TestImageDigest(t *testing.T) { + tests := []struct { + name string + imageSpec string + wantDigest string + errorContains string + }{ + { + name: "valid digested reference", + imageSpec: "registry.example.com/test@sha256:" + testDigestA, + wantDigest: "sha256:" + testDigestA, + }, + { + name: "reference without digest", + imageSpec: "registry.example.com/test:latest", + errorContains: "is not in digested form", + }, + { + name: "malformed reference", + imageSpec: "not a valid ref!!", + errorContains: "invalid image reference", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := imageDigest(tt.imageSpec) + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantDigest, got.String()) + }) + } +} + +func TestSameMajorMinor(t *testing.T) { + tests := []struct { + name string + a string + b string + want bool + errorContains string + }{ + {name: "identical major.minor with differing patch", a: "4.16", b: "4.16.3", want: true}, + {name: "differing minor", a: "4.16", b: "4.17", want: false}, + {name: "differing major", a: "5.0", b: "4.16", want: false}, + {name: "leading v is tolerated", a: "v4.16", b: "4.16.1", want: true}, + {name: "unparseable a", a: "not-a-version", b: "4.16", errorContains: "could not parse version"}, + {name: "unparseable b", a: "4.16", b: "not-a-version", errorContains: "could not parse version"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := sameMajorMinor(tt.a, tt.b) + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + return + } + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} + +func TestOpenshiftVersionFromImage(t *testing.T) { + ctx := context.Background() + + t.Run("success", func(t *testing.T) { + inspector := &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, path string) ([]byte, error) { + assert.Equal(t, osReleasePath, path) + return fakeOSReleaseContent("4.16"), nil + }, + } + got, err := openshiftVersionFromImage(ctx, inspector, "registry.example.com/test@sha256:"+testDigestA) + require.NoError(t, err) + assert.Equal(t, "4.16", got) + }) + + t.Run("fetch error", func(t *testing.T) { + inspector := &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return nil, fmt.Errorf("registry unreachable") + }, + } + _, err := openshiftVersionFromImage(ctx, inspector, "registry.example.com/test@sha256:"+testDigestA) + require.Error(t, err) + assert.Contains(t, err.Error(), "could not fetch") + }) + + t.Run("missing OPENSHIFT_VERSION field", func(t *testing.T) { + inspector := &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return []byte(`ID="rhel"` + "\n"), nil + }, + } + _, err := openshiftVersionFromImage(ctx, inspector, "registry.example.com/test@sha256:"+testDigestA) + require.Error(t, err) + assert.Contains(t, err.Error(), "no OPENSHIFT_VERSION field") + }) +} + +func TestValidatePreBuiltImageDigestFallback(t *testing.T) { + imageSpec := "registry.example.com/layered@sha256:" + testDigestA + + tests := []struct { + name string + labels map[string]string + expectedBaseOSImage string + errorContains string + }{ + { + name: "matching digest through a different registry host (mirror-tolerant)", + labels: map[string]string{ + preBuiltImageBaseOSLabelKey: "mirror.example.com/os@sha256:" + testDigestB, + }, + expectedBaseOSImage: "registry.host.com/os@sha256:" + testDigestB, + }, + { + name: "mismatched digest", + labels: map[string]string{ + preBuiltImageBaseOSLabelKey: "registry.host.com/os@sha256:" + testDigestA, + }, + expectedBaseOSImage: "registry.host.com/os@sha256:" + testDigestB, + errorContains: "does not match the cluster's resolved base OS image", + }, + { + name: "missing label is allowed through with a warning", + labels: map[string]string{}, + expectedBaseOSImage: "registry.host.com/os@sha256:" + testDigestB, + }, + { + name: "invalid label", + labels: map[string]string{ + preBuiltImageBaseOSLabelKey: "not a valid ref!!", + }, + expectedBaseOSImage: "registry.host.com/os@sha256:" + testDigestB, + errorContains: "invalid", + }, + { + name: "invalid expected base OS image", + labels: map[string]string{ + preBuiltImageBaseOSLabelKey: "registry.host.com/os@sha256:" + testDigestB, + }, + expectedBaseOSImage: "registry.host.com/os:latest", + errorContains: "is invalid", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validatePreBuiltImageDigestFallback(tt.labels, imageSpec, tt.expectedBaseOSImage) + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + return + } + require.NoError(t, err) + }) + } +} + +func TestValidatePreBuiltImageVersion(t *testing.T) { + const imageSpec = "registry.example.com/layered@sha256:" + testDigestA + const expectedBaseOSImage = "registry.host.com/os@sha256:" + testDigestB + + tests := []struct { + name string + inspector *fakeImagesInspector + errorContains string + }{ + { + name: "OCP version matches", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return fakeOSReleaseContent(version.ReleaseVersion), nil + }, + InspectFunc: func(_ context.Context, _ ...string) ([]imageutils.BulkInspectResult, error) { + t.Fatal("Inspect should not be called when the OCP version check succeeds") + return nil, nil + }, + }, + }, + { + name: "OCP version mismatch", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return fakeOSReleaseContent("999.999"), nil + }, + }, + errorContains: "does not match the cluster's OCP version", + }, + { + name: "no os-release, digest fallback matches", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return nil, fmt.Errorf("no such file") + }, + InspectFunc: func(_ context.Context, image ...string) ([]imageutils.BulkInspectResult, error) { + return inspectResultWithLabels(image[0], map[string]string{ + preBuiltImageBaseOSLabelKey: expectedBaseOSImage, + }), nil + }, + }, + }, + { + name: "no os-release, digest mismatch", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return nil, fmt.Errorf("no such file") + }, + InspectFunc: func(_ context.Context, image ...string) ([]imageutils.BulkInspectResult, error) { + return inspectResultWithLabels(image[0], map[string]string{ + preBuiltImageBaseOSLabelKey: "registry.host.com/os@sha256:" + testDigestA, + }), nil + }, + }, + errorContains: "does not match the cluster's resolved base OS image", + }, + { + name: "neither OPENSHIFT_VERSION nor baseOSContainerImage label available", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return nil, fmt.Errorf("no such file") + }, + InspectFunc: func(_ context.Context, image ...string) ([]imageutils.BulkInspectResult, error) { + return inspectResultWithLabels(image[0], map[string]string{}), nil + }, + }, + }, + { + name: "Inspect call itself fails (registry unreachable)", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return nil, fmt.Errorf("no such file") + }, + InspectFunc: func(_ context.Context, _ ...string) ([]imageutils.BulkInspectResult, error) { + return nil, fmt.Errorf("dial tcp: no route to host") + }, + }, + errorContains: "could not inspect pre-built image", + }, + { + name: "Inspect returns a per-image error (registry unreachable or image not found)", + inspector: &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return nil, fmt.Errorf("no such file") + }, + InspectFunc: func(_ context.Context, image ...string) ([]imageutils.BulkInspectResult, error) { + return inspectResultWithError(image[0], fmt.Errorf("manifest unknown")), nil + }, + }, + errorContains: "could not access pre-built image", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := validatePreBuiltImageVersion(context.Background(), tt.inspector, imageSpec, expectedBaseOSImage) + if tt.errorContains != "" { + require.Error(t, err) + assert.Contains(t, err.Error(), tt.errorContains) + return + } + require.NoError(t, err) + }) + } +} + +func TestCreatePreBuiltImageMachineConfigs(t *testing.T) { + pools := []*mcfgv1.MachineConfigPool{ + {ObjectMeta: metav1.ObjectMeta{Name: "layered-worker"}}, + } + cconfig := &mcfgv1.ControllerConfig{ + Spec: mcfgv1.ControllerConfigSpec{ + BaseOSContainerImage: "registry.host.com/os@sha256:" + testDigestB, + }, + } + preBuiltImage := "quay.io/example/layered@sha256:" + testDigestA + + versionMatchingInspector := &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return fakeOSReleaseContent(version.ReleaseVersion), nil + }, + } + + newMOSC := func(annotations map[string]string, conditions []metav1.Condition) *mcfgv1.MachineOSConfig { + return &mcfgv1.MachineOSConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "layered-worker", + Annotations: annotations, + }, + Spec: mcfgv1.MachineOSConfigSpec{ + MachineConfigPool: mcfgv1.MachineConfigPoolReference{Name: "layered-worker"}, + }, + Status: mcfgv1.MachineOSConfigStatus{ + Conditions: conditions, + }, + } + } + + t.Run("valid pre-built image produces a component MachineConfig", func(t *testing.T) { + mosc := newMOSC(map[string]string{buildconstants.PreBuiltImageAnnotationKey: preBuiltImage}, nil) + mcs, err := createPreBuiltImageMachineConfigs(context.Background(), []*mcfgv1.MachineOSConfig{mosc}, pools, cconfig, versionMatchingInspector) + require.NoError(t, err) + require.Len(t, mcs, 1) + }) + + t.Run("missing annotation before seeding fails", func(t *testing.T) { + mosc := newMOSC(nil, nil) + _, err := createPreBuiltImageMachineConfigs(context.Background(), []*mcfgv1.MachineOSConfig{mosc}, pools, cconfig, versionMatchingInspector) + require.Error(t, err) + assert.Contains(t, err.Error(), "is missing required annotation") + }) + + t.Run("missing annotation after seeding is skipped", func(t *testing.T) { + mosc := newMOSC(nil, []metav1.Condition{{ + Type: buildconstants.MachineOSConfigSeeded, + Status: metav1.ConditionTrue, + }}) + mcs, err := createPreBuiltImageMachineConfigs(context.Background(), []*mcfgv1.MachineOSConfig{mosc}, pools, cconfig, versionMatchingInspector) + require.NoError(t, err) + assert.Empty(t, mcs) + }) + + t.Run("non-existent pool fails", func(t *testing.T) { + mosc := newMOSC(map[string]string{buildconstants.PreBuiltImageAnnotationKey: preBuiltImage}, nil) + mosc.Spec.MachineConfigPool.Name = "does-not-exist" + _, err := createPreBuiltImageMachineConfigs(context.Background(), []*mcfgv1.MachineOSConfig{mosc}, pools, cconfig, versionMatchingInspector) + require.Error(t, err) + assert.Contains(t, err.Error(), "references non-existent pool") + }) + + t.Run("invalid image format fails", func(t *testing.T) { + mosc := newMOSC(map[string]string{buildconstants.PreBuiltImageAnnotationKey: "quay.io/example/layered:latest"}, nil) + _, err := createPreBuiltImageMachineConfigs(context.Background(), []*mcfgv1.MachineOSConfig{mosc}, pools, cconfig, versionMatchingInspector) + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid pre-built image") + }) + + t.Run("OCP version mismatch fails", func(t *testing.T) { + mismatchInspector := &fakeImagesInspector{ + FetchImageFileFunc: func(_ context.Context, _, _ string) ([]byte, error) { + return fakeOSReleaseContent("999.999"), nil + }, + } + mosc := newMOSC(map[string]string{buildconstants.PreBuiltImageAnnotationKey: preBuiltImage}, nil) + _, err := createPreBuiltImageMachineConfigs(context.Background(), []*mcfgv1.MachineOSConfig{mosc}, pools, cconfig, mismatchInspector) + require.Error(t, err) + assert.Contains(t, err.Error(), "failed validation") + }) +} diff --git a/pkg/controller/build/buildrequest/buildrequest.go b/pkg/controller/build/buildrequest/buildrequest.go index 58d67d6d9b..b7707b7b41 100644 --- a/pkg/controller/build/buildrequest/buildrequest.go +++ b/pkg/controller/build/buildrequest/buildrequest.go @@ -5,11 +5,14 @@ import ( _ "embed" "encoding/json" "fmt" + "regexp" "strings" "text/template" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" mcfgclientset "github.com/openshift/client-go/machineconfiguration/clientset/versioned" + command "github.com/openshift/imagebuilder/dockerfile/command" + parser "github.com/openshift/imagebuilder/dockerfile/parser" "github.com/openshift/machine-config-operator/pkg/controller/build/constants" "github.com/openshift/machine-config-operator/pkg/controller/build/utils" chelpers "github.com/openshift/machine-config-operator/pkg/controller/common" @@ -39,6 +42,43 @@ const ( machineConfigJSONFilename string = "machineconfig.json.gz" ) +var basicSyntaxRegex = regexp.MustCompile(`(?m)(?i)^\s*FROM`) + +// ContainerfileValidationError is returned when a rendered Containerfile fails +// syntax validation. It is a permanent error — the build should not be retried. +type ContainerfileValidationError struct { + cause error +} + +func (e *ContainerfileValidationError) Error() string { + return fmt.Sprintf("Containerfile validation failed: %v", e.cause) +} + +func (e *ContainerfileValidationError) Unwrap() error { + return e.cause +} + +// instructionRequirements maps Containerfile instructions that MUST have arguments to their requirement descriptions +// Instructions not in this map either don't require arguments or have optional arguments +var instructionRequirements = map[string]string{ + command.Add: "source and destination paths", + command.Arg: "a variable name", + command.Cmd: "a command or arguments", + command.Copy: "source and destination paths", + command.Entrypoint: "a command or arguments", + command.Env: "key=value pairs", + command.Expose: "a port", + command.From: "a base image", + command.Label: "key=value pairs", + command.Onbuild: "an instruction to execute", + command.Run: "a command", + command.Shell: "a shell command array", + command.StopSignal: "a signal", + command.User: "a username or UID", + command.Volume: "a mount point", + command.Workdir: "a directory path", +} + // Represents the request to build a layered OS image. type buildRequestImpl struct { opts BuildRequestOpts @@ -69,6 +109,13 @@ func newBuildRequest(opts BuildRequestOpts) BuildRequest { } } + // Validate user's Containerfile if provided + if br.userContainerfile != "" { + if err := br.validateContainerfileSyntax(br.userContainerfile); err != nil { + klog.Warningf("User Containerfile validation failed") + } + } + return br } @@ -346,7 +393,183 @@ func (br buildRequestImpl) renderContainerfile() (string, error) { return "", fmt.Errorf("could not execute containerfile template: %w", err) } - return out.String(), nil + renderedContainerfile := out.String() + + if renderedContainerfile != "" { + if err := br.validateContainerfileSyntax(renderedContainerfile); err != nil { + return "", &ContainerfileValidationError{cause: err} + } + } + + return renderedContainerfile, nil +} + +// validateContainerfileSyntax validates the syntax of a Containerfile using basic checks and the imagebuilder parser. +func (br buildRequestImpl) validateContainerfileSyntax(containerfile string) error { + if containerfile == "" || hasOnlyCommentsOrWhitespace(containerfile) { + klog.V(4).Infof("Containerfile is empty or contains only comments, nothing to validate") + return nil + } + + if err := br.validateBasicSyntax(containerfile); err != nil { + return err + } + + result, err := parser.Parse(strings.NewReader(containerfile)) + if err != nil { + return handleParserError(err, containerfile) + } + + if result.AST == nil || len(result.AST.Children) == 0 { + klog.V(4).Infof("Containerfile parsed with no instructions (may be all comments), nothing to validate") + return nil + } + + for _, child := range result.AST.Children { + if err := br.validateInstruction(child); err != nil { + return err + } + } + + return nil +} + +// handleParserError translates a parser.Parse error into a validation outcome. +// Known parser limitations (heredoc, unquoted LABEL/ENV values) are allowed through; +// genuinely malformed instructions and all other errors fail. +func handleParserError(err error, containerfile string) error { + switch { + case isKnownParserLimitation(err): + klog.V(4).Infof("Containerfile uses advanced syntax the parser doesn't support, skipping detailed parser validation: %v", err) + return nil + case isParseNameValError(err): + // imagebuilders parseNameVal rejects LABEL/ENV values that contain unquoted spaces + if malformed := findMalformedEnvLabel(containerfile); malformed != nil { + return malformed + } + klog.V(4).Infof("Containerfile LABEL/ENV values contain unquoted spaces; imagebuilder cannot parse them but buildah/podman will: %v", err) + return nil + default: + return fmt.Errorf("failed to parse Containerfile: %w", err) + } +} + +// validateBasicSyntax performs basic sanity checks that don't require parsing. +func (br buildRequestImpl) validateBasicSyntax(containerfile string) error { + if !basicSyntaxRegex.MatchString(containerfile) { + return fmt.Errorf("Containerfile must contain at least one FROM instruction") + } + + return nil +} + +// hasOnlyCommentsOrWhitespace reports whether every non-empty line in the +// containerfile is a comment line (starts with '#' after trimming whitespace). +func hasOnlyCommentsOrWhitespace(containerfile string) bool { + for _, line := range strings.Split(containerfile, "\n") { + trimmed := strings.TrimSpace(line) + if trimmed != "" && !strings.HasPrefix(trimmed, "#") { + return false + } + } + return true +} + +// isKnownParserLimitation checks if a parser error is from known limitations of the +// imagebuilder parser library that buildah/podman handle correctly. +func isKnownParserLimitation(err error) bool { + if err == nil { + return false + } + // Heredoc syntax (e.g. RUN bash <<'EOF') is valid in Podman/Buildah but not + // supported by the imagebuilder parser. + errMsg := err.Error() + for _, token := range []string{"<<", "heredoc"} { + if strings.Contains(errMsg, token) { + return true + } + } + return false +} + +// isParseNameValError reports whether the error came from imagebuilder's parseNameVal, +// which rejects LABEL/ENV values containing unquoted spaces. +func isParseNameValError(err error) bool { + if err == nil { + return false + } + msg := err.Error() + return strings.Contains(msg, "can't find = in") || strings.Contains(msg, "must be of the form: name=value") +} + +// findMalformedEnvLabel scans containerfile lines for ENV or LABEL instructions that +// lack any '=' sign in their arguments, which means the instruction is genuinely malformed. +func findMalformedEnvLabel(containerfile string) error { + for i, line := range strings.Split(containerfile, "\n") { + trimmed := strings.TrimSpace(line) + if trimmed == "" || strings.HasPrefix(trimmed, "#") { + continue + } + instruction, rest, ok := cutEnvLabelPrefix(trimmed) + if !ok { + continue + } + if !strings.Contains(rest, "=") { + return fmt.Errorf("line %d: %s instruction must use key=value form", i+1, instruction) + } + } + return nil +} + +// cutEnvLabelPrefix returns the instruction name and the remainder of the line if the +// line begins with ENV or LABEL (case-insensitive). ok is false for all other instructions. +func cutEnvLabelPrefix(line string) (instruction, rest string, ok bool) { + for _, inst := range []string{"ENV", "LABEL"} { + prefix := inst + " " + if len(line) > len(prefix) && strings.EqualFold(line[:len(prefix)], prefix) { + return inst, strings.TrimSpace(line[len(prefix):]), true + } + } + return "", "", false +} + +// validateInstruction validates that an instruction is valid and has required arguments. +func (br buildRequestImpl) validateInstruction(node *parser.Node) error { + if node == nil || node.Value == "" { + return nil + } + + instruction := node.Value + + // First check if this is a valid Dockerfile instruction + if _, exists := command.Commands[instruction]; !exists { + return fmt.Errorf("unknown Dockerfile instruction: %s", node.Value) + } + + // NONE is valid without additional arguments + if instruction == command.Healthcheck && node.Next != nil && strings.EqualFold(node.Next.Value, "NONE") { + return nil + } + + // Then check if it has required arguments + requirement, requiresArgs := instructionRequirements[instruction] + if requiresArgs { + // Check if the node has arguments + hasArgs := false + if node.Next != nil && node.Next.Value != "" { + hasArgs = true + } + // Also check if there's a raw attribute that contains args + if !hasArgs && node.Attributes != nil && len(node.Attributes) > 0 { + hasArgs = true + } + + if !hasArgs { + return fmt.Errorf("%s instruction requires %s", strings.ToUpper(instruction), requirement) + } + } + + return nil } // podToJob creates a Job with the spec of the given Pod diff --git a/pkg/controller/build/buildrequest/buildrequest_test.go b/pkg/controller/build/buildrequest/buildrequest_test.go index 22033c6c11..f7dce687fa 100644 --- a/pkg/controller/build/buildrequest/buildrequest_test.go +++ b/pkg/controller/build/buildrequest/buildrequest_test.go @@ -333,3 +333,194 @@ RUN rpm-ostree install && \ }, } } + +// TestValidateContainerfileSyntax tests the Containerfile validation logic +func TestValidateContainerfileSyntax(t *testing.T) { + t.Parallel() + + br := &buildRequestImpl{} + + testCases := []struct { + name string + containerfile string + expectError bool + errorContains string + }{ + { + name: "Valid simple Containerfile", + containerfile: `FROM registry.example.com/base:latest +RUN echo "hello" +COPY . /app`, + expectError: false, + }, + { + name: "Empty Containerfile", + containerfile: "", + expectError: false, + }, + { + name: "No FROM instruction", + containerfile: `RUN echo "hello" +COPY . /app`, + expectError: true, + errorContains: "must contain at least one FROM instruction", + }, + { + name: "FROM without arguments", + containerfile: `FROM`, + expectError: true, + errorContains: "FROM instruction requires a base image", + }, + { + name: "RUN without arguments", + containerfile: `FROM registry.example.com/base:latest +RUN`, + expectError: true, + errorContains: "RUN instruction requires a command", + }, + { + name: "COPY without arguments", + containerfile: `FROM registry.example.com/base:latest +COPY`, + expectError: true, + errorContains: "COPY instruction requires source and destination paths", + }, + { + name: "WORKDIR without arguments", + containerfile: `FROM registry.example.com/base:latest +WORKDIR`, + expectError: true, + errorContains: "WORKDIR instruction requires a directory path", + }, + { + name: "Valid Containerfile with comments", + containerfile: `# Base image +FROM registry.example.com/base:latest +# Install dependencies +RUN yum install -y python3`, + expectError: false, + }, + { + name: "Advanced syntax with heredoc - should pass", + containerfile: `FROM registry.example.com/base:latest +RUN bash <<'EOF' +set -xeuo pipefail +echo "hello" +EOF +`, + expectError: false, // Hybrid validator allows advanced syntax + }, + { + name: "Comments only - should pass", + containerfile: `# This Containerfile is temporarily disabled for testing +# FROM registry.example.com/base:latest +# RUN echo "hello"`, + expectError: false, + }, + } + + for _, testCase := range testCases { + testCase := testCase + t.Run(testCase.name, func(t *testing.T) { + t.Parallel() + + err := br.validateContainerfileSyntax(testCase.containerfile) + + if testCase.expectError { + assert.Error(t, err) + if testCase.errorContains != "" { + assert.Contains(t, err.Error(), testCase.errorContains) + } + } else { + assert.NoError(t, err) + } + }) + } +} + +// TestValidateContainerfileInBuildRequest tests that validation happens during the build request flow +func TestValidateContainerfileInBuildRequest(t *testing.T) { + t.Parallel() + + t.Run("Valid user Containerfile renders successfully", func(t *testing.T) { + opts := getBuildRequestOpts() + opts.MachineOSConfig.Spec.Containerfile[0].Content = `FROM configs AS final +RUN rpm-ostree install vim && ostree container commit` + + br := newBuildRequest(opts) + configMaps, err := br.ConfigMaps() + + assert.NoError(t, err) + assert.NotNil(t, configMaps) + // Verify the Containerfile ConfigMap was created + assert.Greater(t, len(configMaps), 0) + }) +} + +func TestValidateUnknownInstruction(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + containerfile string + expectError bool + errorContains string + }{ + { + name: "Unknown instruction BADINSTRUCTION", + containerfile: `FROM registry.example.com/base:latest +BADINSTRUCTION this should fail +RUN echo "test"`, + expectError: true, + errorContains: "unknown Dockerfile instruction", + }, + { + name: "Unknown instruction INVALIDCMD", + containerfile: `FROM registry.example.com/base:latest +RUN echo "valid" +INVALIDCMD foo bar`, + expectError: true, + errorContains: "unknown Dockerfile instruction", + }, + { + name: "Valid instructions only", + containerfile: `FROM registry.example.com/base:latest +RUN echo "test" +COPY . /app +ENV FOO=bar`, + expectError: false, + }, + { + name: "Malformed ENV without equals sign - should fail", + containerfile: `FROM registry.example.com/base:latest +ENV INVALID_NO_EQUALS`, + expectError: true, + errorContains: "failed to parse Containerfile", + }, + { + name: "Malformed LABEL without equals sign - should fail", + containerfile: `FROM registry.example.com/base:latest +LABEL badlabel`, + expectError: true, + errorContains: "failed to parse Containerfile", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + br := &buildRequestImpl{} + err := br.validateContainerfileSyntax(tc.containerfile) + + if tc.expectError { + assert.Error(t, err) + if tc.errorContains != "" { + assert.Contains(t, err.Error(), tc.errorContains) + } + } else { + assert.NoError(t, err) + } + }) + } +} diff --git a/pkg/controller/build/reconciler.go b/pkg/controller/build/reconciler.go index 91ea0acf1c..34c2b8cdbd 100644 --- a/pkg/controller/build/reconciler.go +++ b/pkg/controller/build/reconciler.go @@ -2,6 +2,7 @@ package build import ( "context" + "errors" "fmt" "strings" "time" @@ -457,6 +458,17 @@ func (b *buildReconciler) startBuild(ctx context.Context, mosb *mcfgv1.MachineOS // Next, create our new MachineOSBuild. if err := imagebuilder.NewJobImageBuilder(b.kubeclient, b.mcfgclient, mosb, mosc).Start(ctx); err != nil { + var validationErr *buildrequest.ContainerfileValidationError + if errors.As(err, &validationErr) { + klog.Warningf("MachineOSBuild %q has an invalid Containerfile; marking as failed: %v", mosb.Name, validationErr) + failedStatus := mcfgv1.MachineOSBuildStatus{ + Conditions: apihelpers.MachineOSBuildFailedConditions(), + } + if statusErr := b.setStatusOnMachineOSBuildIfNeeded(ctx, mosb, mosb.Status, failedStatus); statusErr != nil { + klog.Errorf("Could not mark MachineOSBuild %q as failed: %v", mosb.Name, statusErr) + } + return nil + } return fmt.Errorf("imagebuilder could not start build for MachineOSBuild %q: %w", mosb.Name, err) } diff --git a/pkg/controller/common/constants.go b/pkg/controller/common/constants.go index 3aa5b9adc5..d1a8589c6e 100644 --- a/pkg/controller/common/constants.go +++ b/pkg/controller/common/constants.go @@ -36,6 +36,9 @@ const ( // OSImageURLOverriddenKey is used to tag a rendered machineconfig when OSImageURL has been overridden from default using machineconfig OSImageURLOverriddenKey = "machineconfiguration.openshift.io/os-image-url-overridden" + // OSImageURLOverriddenTrue is the annotation value set on OSImageURLOverriddenKey when the OSImageURL is overridden. + OSImageURLOverriddenTrue = "true" + // RenderedMachineConfigPrefix is the name prefix for rendered MachineConfigs RenderedMachineConfigPrefix = "rendered-" diff --git a/pkg/controller/common/sys_context.go b/pkg/controller/common/sys_context.go new file mode 100644 index 0000000000..fdae1e9b5c --- /dev/null +++ b/pkg/controller/common/sys_context.go @@ -0,0 +1,66 @@ +package common + +import ( + "fmt" + + configlistersv1 "github.com/openshift/client-go/config/listers/config/v1" + mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1" + operatorlistersv1alpha1 "github.com/openshift/client-go/operator/listers/operator/v1alpha1" + "github.com/openshift/machine-config-operator/pkg/imageutils" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + corelistersv1 "k8s.io/client-go/listers/core/v1" +) + +// NewSysContextFactory builds a SysContextFactory that resolves pull secrets, +// ControllerConfig, and registry mirror rules from informer-backed listers. +func NewSysContextFactory( + ccLister mcfglistersv1.ControllerConfigLister, + secretLister corelistersv1.SecretLister, + imgLister configlistersv1.ImageLister, + icspLister operatorlistersv1alpha1.ImageContentSourcePolicyLister, + idmsLister configlistersv1.ImageDigestMirrorSetLister, + itmsLister configlistersv1.ImageTagMirrorSetLister, +) imageutils.SysContextFactory { + return func() (*imageutils.SysContext, error) { + cc, err := ccLister.Get(ControllerConfigName) + if err != nil { + return nil, fmt.Errorf("failed to get controller config: %w", err) + } + secret, err := secretLister.Secrets(OpenshiftConfigNamespace).Get(GlobalPullSecretName) + if err != nil { + return nil, fmt.Errorf("failed to get pull secret: %w", err) + } + + builder := imageutils.NewSysContextBuilder(). + WithSecret(secret). + WithControllerConfig(cc) + + imgCfg, err := imgLister.Get("cluster") + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to get cluster image config: %w", err) + } + icspRules, err := icspLister.List(labels.Everything()) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to list ICSP rules: %w", err) + } + idmsRules, err := idmsLister.List(labels.Everything()) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to list IDMS rules: %w", err) + } + itmsRules, err := itmsLister.List(labels.Everything()) + if err != nil && !apierrors.IsNotFound(err) { + return nil, fmt.Errorf("failed to list ITMS rules: %w", err) + } + + if imgCfg != nil || len(icspRules) != 0 || len(idmsRules) != 0 || len(itmsRules) != 0 { + registriesConf, err := imageutils.GenerateRegistriesConfig(imgCfg, icspRules, idmsRules, itmsRules) + if err != nil { + return nil, fmt.Errorf("failed to generate registries config: %w", err) + } + builder.WithRegistriesConfig(registriesConf) + } + + return builder.Build() + } +} diff --git a/pkg/controller/node/status.go b/pkg/controller/node/status.go index 14923a53d1..9993a1f038 100644 --- a/pkg/controller/node/status.go +++ b/pkg/controller/node/status.go @@ -280,16 +280,31 @@ func (ctrl *Controller) calculateStatus(mcns []*mcfgv1.MachineConfigNode, cconfi updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is waiting for a new OS image build to start (mosc: %s)", mosc.Name)) apihelpers.SetMachineConfigPoolCondition(&status, *updating) default: - // Some cases we have an old MOSB object that still exists, we still update MCP mosbState := ctrlcommon.NewMachineOSBuildState(mosb) switch { - case mosbState.IsBuilding() || mosbState.IsBuildPrepared(): + case mosbState.IsBuildPrepared(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is waiting for OS image build to start (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuilding(): updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is waiting for OS image build to complete (mosb: %s)", mosb.Name)) apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuildSuccess(): + // Build succeeded: if nodes still need to apply the image, keep Updating=True. + // If allUpdated already cleared Updating above, leave it alone so a fully + // converged pool does not get stuck as Updating. + if !allUpdated { + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is waiting for nodes to apply OS image (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + } case mosbState.IsBuildFailure(): - // Clear updating status when build fails updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionFalse, "", fmt.Sprintf("Pool update stopped due to OS image build failure (mosb: %s)", mosb.Name)) apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuildInterrupted(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionFalse, "", fmt.Sprintf("Pool update stopped due to OS image build being interrupted (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsInInitialState(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is waiting for OS image build to start (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) } } default: @@ -297,6 +312,35 @@ func (ctrl *Controller) calculateStatus(mcns []*mcfgv1.MachineConfigNode, cconfi updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionFalse, "", "Pool update paused due to degraded condition") apihelpers.SetMachineConfigPoolCondition(&status, *updating) } + } else if mosc != nil && pool.Spec.Paused { + // Pool is paused but a background build may still be running or may have failed. + // Surface build state so operators can observe progress without unpausing. + if mosb == nil { + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is paused; waiting for a new OS image build to start (mosc: %s)", mosc.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + } else { + mosbState := ctrlcommon.NewMachineOSBuildState(mosb) + switch { + case mosbState.IsBuildPrepared(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is paused; OS image build has been prepared but will not rollout (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuilding(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is paused; OS image build in progress (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuildSuccess(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionFalse, "", fmt.Sprintf("Pool is paused; OS image build completed successfully (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuildFailure(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionFalse, "", fmt.Sprintf("Pool is paused; OS image build failed (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsBuildInterrupted(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionFalse, "", fmt.Sprintf("Pool is paused; OS image build was interrupted (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + case mosbState.IsInInitialState(): + updating := apihelpers.NewMachineConfigPoolCondition(mcfgv1.MachineConfigPoolUpdating, corev1.ConditionTrue, "", fmt.Sprintf("Pool is paused; OS image build has been created but not yet started (mosb: %s)", mosb.Name)) + apihelpers.SetMachineConfigPoolCondition(&status, *updating) + } + } } // Update degrade condition diff --git a/pkg/controller/node/status_test.go b/pkg/controller/node/status_test.go index 4fd46e2dc4..604533ce16 100644 --- a/pkg/controller/node/status_test.go +++ b/pkg/controller/node/status_test.go @@ -308,6 +308,8 @@ func TestCalculateStatus(t *testing.T) { nodes []*corev1.Node currentConfig string paused bool + overrideMosb bool + mosb *mcfgv1.MachineOSBuild osStream mcfgv1.OSImageStreamReference needsOSImageStreamSetup bool initialConditions []mcfgv1.MachineConfigPoolCondition @@ -479,179 +481,326 @@ func TestCalculateStatus(t *testing.T) { } }, }, { - name: "0 nodes updated, 1 node updating, 0 nodes degraded", + name: "pool paused, mosc exists but no mosb, waiting for build to start", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionFalse), + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), }, currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: nil, verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) - } - - if got, want := status.UpdatedMachineCount, int32(0); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) - } - - if got, want := status.ReadyMachineCount, int32(0); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) - } - - if got, want := status.UnavailableMachineCount, int32(1); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) - } - - if got, want := status.DegradedMachineCount, int32(0); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) - } - - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") - } - condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + assert.Equal(t, "Pool is paused; waiting for a new OS image build to start (mosc: mosc-1)", condupdating.Message) + }, + }, + { + name: "pool paused, mosc but is in initial state", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithBuildInitialState().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build has been created but not yet started (mosb: mosb-1)", condupdating.Message) + }, + }, + { + name: "pool paused, build prepared", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithBuildPrepared().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build has been prepared but will not rollout (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool paused, build in progress", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithBuildInProgress().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build in progress (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool paused, build failed", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithFailedBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build failed (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool paused, build interrupted", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithInterruptedBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build was interrupted (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool paused, build succeeded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithSuccessfulBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build completed successfully (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, mosc exists but no mosb, waiting for build to start", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: nil, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - if got, want := condupdating.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) - } + assert.Equal(t, "Pool is waiting for a new OS image build to start (mosc: mosc-1)", condupdating.Message) }, }, { - name: "0 nodes updated, 1 node updating, 1 node degraded", + name: "pool not paused, build prepared", nodes: []*corev1.Node{ - newNodeWithReadyAndDaemonState("node-0", machineConfigV0, machineConfigV1, corev1.ConditionFalse, daemonconsts.MachineConfigDaemonStateDegraded), + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), }, currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithBuildPrepared().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.UpdatedMachineCount, int32(0); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := status.ReadyMachineCount, int32(0); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + assert.Equal(t, "Pool is waiting for OS image build to start (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build in progress", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithBuildInProgress().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.UnavailableMachineCount, int32(1); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := status.DegradedMachineCount, int32(1); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + assert.Equal(t, "Pool is waiting for OS image build to complete (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build failed", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithFailedBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - + assert.Equal(t, "Pool update stopped due to OS image build failure (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build interrupted", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithInterruptedBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("updating condition not found") + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + assert.Equal(t, "Pool update stopped due to OS image build being interrupted (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build in initial state", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithBuildInitialState().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - if got, want := condupdating.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := conddegraded.Status, corev1.ConditionTrue; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) - } + assert.Equal(t, "Pool is waiting for OS image build to start (mosb: mosb-1)", condupdating.Message) }, }, { - name: "1 node updated, 1 node updating, 0 nodes degraded", + name: "pool not paused, build succeeded, nodes still applying", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionFalse), + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), }, currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithSuccessfulBuild().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) - } - - if got, want := status.UpdatedMachineCount, int32(1); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) - } - - if got, want := status.ReadyMachineCount, int32(0); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) - } - - if got, want := status.UnavailableMachineCount, int32(1); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.DegradedMachineCount, int32(0); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - + assert.Equal(t, "Pool is waiting for nodes to apply OS image (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build succeeded, all nodes updated", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithDigestedImagePushspec("registry.host.com/org/repo@sha256:12345").WithSuccessfulBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) if condupdated == nil { t.Fatal("updated condition not found") } - + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") - } - - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) - } - - if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) - } }, }, { - name: "1 node updated, 2 nodes updating, 0 nodes degraded", + name: "0 nodes updated, 1 node updating, 0 nodes degraded", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionFalse), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), }, currentConfig: machineConfigV1, verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { @@ -659,15 +808,15 @@ func TestCalculateStatus(t *testing.T) { t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) } - if got, want := status.UpdatedMachineCount, int32(1); got != want { + if got, want := status.UpdatedMachineCount, int32(0); got != want { t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) } - if got, want := status.ReadyMachineCount, int32(1); got != want { + if got, want := status.ReadyMachineCount, int32(0); got != want { t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) } - if got, want := status.UnavailableMachineCount, int32(2); got != want { + if got, want := status.UnavailableMachineCount, int32(1); got != want { t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) } @@ -703,67 +852,675 @@ func TestCalculateStatus(t *testing.T) { } }, }, { - name: "3 nodes updated, 0 nodes updating, 0 nodes degraded", - nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV1, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV1, machineConfigV1, corev1.ConditionTrue), - }, - currentConfig: machineConfigV1, - verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) - } + name: "0 nodes updated, 1 node updating, 1 node degraded", + nodes: []*corev1.Node{ + newNodeWithReadyAndDaemonState("node-0", machineConfigV0, machineConfigV1, corev1.ConditionFalse, daemonconsts.MachineConfigDaemonStateDegraded), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } - if got, want := status.UpdatedMachineCount, int32(3); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) - } + if got, want := status.UpdatedMachineCount, int32(0); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } - if got, want := status.ReadyMachineCount, int32(3); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) - } + if got, want := status.ReadyMachineCount, int32(0); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } - if got, want := status.UnavailableMachineCount, int32(0); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) - } + if got, want := status.UnavailableMachineCount, int32(1); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } - if got, want := status.DegradedMachineCount, int32(0); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) - } + if got, want := status.DegradedMachineCount, int32(1); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") - } + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } - condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) - if condupdating == nil { - t.Fatal("updating condition not found") + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("updating condition not found") + } + + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + + if got, want := conddegraded.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + } + }, + }, { + name: "1 node updated, 1 node updating, 0 nodes degraded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionFalse), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } + + if got, want := status.UpdatedMachineCount, int32(1); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } + + if got, want := status.ReadyMachineCount, int32(0); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } + + if got, want := status.UnavailableMachineCount, int32(1); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } + + if got, want := status.DegradedMachineCount, int32(0); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } + + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") + } + + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + } + }, + }, { + name: "1 node updated, 2 nodes updating, 0 nodes degraded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } + + if got, want := status.UpdatedMachineCount, int32(1); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } + + if got, want := status.ReadyMachineCount, int32(1); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } + + if got, want := status.UnavailableMachineCount, int32(2); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } + + if got, want := status.DegradedMachineCount, int32(0); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } + + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") + } + + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + } + }, + }, { + name: "3 nodes updated, 0 nodes updating, 0 nodes degraded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } + + if got, want := status.UpdatedMachineCount, int32(3); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } + + if got, want := status.ReadyMachineCount, int32(3); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } + + if got, want := status.UnavailableMachineCount, int32(0); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } + + if got, want := status.DegradedMachineCount, int32(0); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } + + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") + } + + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + } + }, + }, { + name: "OSImageStream is empty when OSImageStream CR does not exist", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV0, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } + + if got, want := status.UpdatedMachineCount, int32(3); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } + + if got, want := status.ReadyMachineCount, int32(3); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } + + if got, want := status.UnavailableMachineCount, int32(0); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } + + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + + // When OSImageStream CR does not exist, status.OSImageStream should be empty + if got, want := status.OSImageStream.Name, ""; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - OSImageStream should be empty when CR does not exist", got, want) + } + }, + }, { + name: "OSImageStream status populated when pool updated and osImageURL matches stream", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV0, + needsOSImageStreamSetup: true, + initialConditions: []mcfgv1.MachineConfigPoolCondition{ + {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, + {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, + {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + }, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + // Verify pool is fully updated + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") + } + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + } + + // OSImageStream status should be populated with the matching stream + if got, want := status.OSImageStream.Name, "rhel-9"; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q", got, want) + } + }, + }, { + name: "OSImageStream status empty when pool updated but osImageURL doesn't match any stream (override)", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + needsOSImageStreamSetup: true, + initialConditions: []mcfgv1.MachineConfigPoolCondition{ + {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, + {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, + {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + }, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + // Verify pool is fully updated + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + // OSImageStream status should be empty (override scenario) + if got, want := status.OSImageStream.Name, ""; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty for override scenario", got, want) + } + }, + }, { + name: "OSImageStream status empty when pool is updating", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV1, + needsOSImageStreamSetup: true, + // No initialConditions - let calculateStatus set them based on node states + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + // Verify pool is updating + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + // OSImageStream status should be empty when pool is updating + if got, want := status.OSImageStream.Name, ""; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty when updating", got, want) + } + }, + }, { + name: "OSImageStream status empty when pool is degraded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV0, machineConfigV0, "", "", daemonconsts.MachineConfigDaemonStateDegraded, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + }, + currentConfig: machineConfigV0, + needsOSImageStreamSetup: true, + initialConditions: []mcfgv1.MachineConfigPoolCondition{ + {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionFalse}, + {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, + {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionTrue}, + }, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + // Verify pool is degraded + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") + } + if got, want := conddegraded.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + } + + // OSImageStream status should be empty when pool is degraded + if got, want := status.OSImageStream.Name, ""; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty when degraded", got, want) + } + }, + }, { + name: "OSImageStream status empty when rendered config has empty osImageURL", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", machineConfigV2, machineConfigV2, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", machineConfigV2, machineConfigV2, corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", machineConfigV2, machineConfigV2, corev1.ConditionTrue), + }, + currentConfig: machineConfigV2, + needsOSImageStreamSetup: true, + initialConditions: []mcfgv1.MachineConfigPoolCondition{ + {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, + {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, + {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + }, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + // Verify pool is fully updated + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + // OSImageStream status should be empty when osImageURL is empty + if got, want := status.OSImageStream.Name, ""; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty when osImageURL is empty", got, want) + } + }, + }, { + name: "OSImageStream status matches second stream when osImageURL matches it", + nodes: []*corev1.Node{ + helpers.NewNodeWithReady("node-0", "rendered-rhel10", "rendered-rhel10", corev1.ConditionTrue), + helpers.NewNodeWithReady("node-1", "rendered-rhel10", "rendered-rhel10", corev1.ConditionTrue), + helpers.NewNodeWithReady("node-2", "rendered-rhel10", "rendered-rhel10", corev1.ConditionTrue), + }, + currentConfig: "rendered-rhel10", + needsOSImageStreamSetup: true, + initialConditions: []mcfgv1.MachineConfigPoolCondition{ + {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, + {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, + {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + }, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + // Verify pool is fully updated + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + + // OSImageStream status should match the second stream (rhel-10) + if got, want := status.OSImageStream.Name, "rhel-10"; got != want { + t.Fatalf("mismatch OSImageStream.Name: got %q want: %q", got, want) + } + }, + }} + for idx, test := range tests { + idx := idx + test := test + t.Run(fmt.Sprintf("case#%d", idx), func(t *testing.T) { + t.Parallel() + pool := &mcfgv1.MachineConfigPool{ + Spec: mcfgv1.MachineConfigPoolSpec{ + Configuration: mcfgv1.MachineConfigPoolStatusConfiguration{ObjectReference: corev1.ObjectReference{Name: test.currentConfig}}, + Paused: test.paused, + OSImageStream: test.osStream, + }, + Status: mcfgv1.MachineConfigPoolStatus{ + Configuration: mcfgv1.MachineConfigPoolStatusConfiguration{ + ObjectReference: corev1.ObjectReference{Name: test.currentConfig}, + }, + }, } + f := newFixtureWithFeatureGates(t, + []apicfgv1.FeatureGateName{ + features.FeatureGateOSStreams, + }, + []apicfgv1.FeatureGateName{}, + ) - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") + var c *Controller + if test.needsOSImageStreamSetup { + // Set pool conditions if specified in the test + if len(test.initialConditions) > 0 { + pool.Status.Conditions = test.initialConditions + } + + // Add MachineConfig objects with different osImageURLs for testing + mc0 := &mcfgv1.MachineConfig{ + ObjectMeta: metav1.ObjectMeta{Name: machineConfigV0}, + Spec: mcfgv1.MachineConfigSpec{ + OSImageURL: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel9image", + }, + } + mc1 := &mcfgv1.MachineConfig{ + ObjectMeta: metav1.ObjectMeta{Name: machineConfigV1}, + Spec: mcfgv1.MachineConfigSpec{ + OSImageURL: "quay.io/custom/custom-image:latest", // Custom URL that doesn't match any stream + }, + } + mc2 := &mcfgv1.MachineConfig{ + ObjectMeta: metav1.ObjectMeta{Name: machineConfigV2}, + Spec: mcfgv1.MachineConfigSpec{ + OSImageURL: "", // Empty osImageURL + }, + } + mcRhel10 := &mcfgv1.MachineConfig{ + ObjectMeta: metav1.ObjectMeta{Name: "rendered-rhel10"}, + Spec: mcfgv1.MachineConfigSpec{ + OSImageURL: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel10image", + }, + } + // Add OSImageStream CR with available streams + osImageStream := &mcfgv1.OSImageStream{ + ObjectMeta: metav1.ObjectMeta{ + Name: ctrlcommon.ClusterInstanceNameOSImageStream, + }, + Status: mcfgv1.OSImageStreamStatus{ + DefaultStream: "rhel-9", + AvailableStreams: []mcfgv1.OSImageStreamSet{ + { + Name: "rhel-9", + OSImage: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel9image", + }, + { + Name: "rhel-10", + OSImage: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel10image", + }, + }, + }, + } + + // Add all test objects to the fixture + f.objects = append(f.objects, mc0, mc1, mc2, mcRhel10, osImageStream) + + c = f.newController() + + // The controller's informers were already created, but we need to add MC and OSImageStream objects + // to their indexers. We can't access the informer factory from here, so we'll use the client + // that was already created and manually create new informers to populate the listers. + tmpInformer := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc()) + tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mc0) + tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mc1) + tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mc2) + tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mcRhel10) + tmpInformer.Machineconfiguration().V1().OSImageStreams().Informer().GetIndexer().Add(osImageStream) + + // Replace the controller's listers with the populated ones + c.mcLister = tmpInformer.Machineconfiguration().V1().MachineConfigs().Lister() + c.osImageStreamLister = tmpInformer.Machineconfiguration().V1().OSImageStreams().Lister() + } else { + c = f.newController() } - if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + var mosc *mcfgv1.MachineOSConfig + var mosb *mcfgv1.MachineOSBuild + if test.overrideMosb { + mosc = helpers.NewMachineOSConfigBuilder("mosc-1").WithCurrentImagePullspec("registry.host.com/org/repo@sha256:12345").MachineOSConfig() + mosb = test.mosb + } + + status := c.calculateStatus([]*mcfgv1.MachineConfigNode{}, nil, pool, test.nodes, mosc, mosb) + test.verify(status, t) + }) + } +} + +// Assisted by: Cursor +// TestCalculateStatusWithImageModeReporting tests the status calculation with ImageModeStatusReporting feature gate enabled +func TestCalculateStatusWithImageModeReporting(t *testing.T) { + t.Parallel() + + // Create feature gate handler that directly enables ImageModeStatusReporting + // This simulates a DevPreview environment where this feature gate is available + fgHandler := ctrlcommon.NewFeatureGatesHardcodedHandler( + []apicfgv1.FeatureGateName{ + features.FeatureGateImageModeStatusReporting, // Enable ImageModeStatusReporting directly + }, + []apicfgv1.FeatureGateName{}, + ) + + // Verify that ImageModeStatusReporting is enabled + if !fgHandler.Enabled(features.FeatureGateImageModeStatusReporting) { + t.Skip("ImageModeStatusReporting could not be enabled") + } + + tests := []struct { + name string + nodes []*corev1.Node + mcns []*mcfgv1.MachineConfigNode + currentConfig string + paused bool + overrideMosb bool + mosb *mcfgv1.MachineOSBuild + verify func(mcfgv1.MachineConfigPoolStatus, *testing.T) + }{{ + name: "0 nodes updated, 0 nodes updating, 0 nodes degraded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } + + if got, want := status.UpdatedMachineCount, int32(0); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } + + if got, want := status.ReadyMachineCount, int32(0); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } + + if got, want := status.UnavailableMachineCount, int32(0); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } + + if got, want := status.DegradedMachineCount, int32(0); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } + + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) + if condupdated == nil { + t.Fatal("updated condition not found") + } + + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) } - if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") + } + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) } }, }, { - name: "OSImageStream is empty when OSImageStream CR does not exist", + name: "0 nodes updated, 1 node updating, 0 nodes degraded", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV0, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, currentConfig: machineConfigV0, verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { @@ -771,119 +1528,159 @@ func TestCalculateStatus(t *testing.T) { t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) } - if got, want := status.UpdatedMachineCount, int32(3); got != want { + if got, want := status.UpdatedMachineCount, int32(0); got != want { t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) } - if got, want := status.ReadyMachineCount, int32(3); got != want { + if got, want := status.ReadyMachineCount, int32(0); got != want { t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) } - if got, want := status.UnavailableMachineCount, int32(0); got != want { + if got, want := status.UnavailableMachineCount, int32(1); got != want { t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) } + if got, want := status.DegradedMachineCount, int32(0); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) if condupdated == nil { t.Fatal("updated condition not found") } + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + } + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - if got, want := condupdated.Status, corev1.ConditionTrue; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - if got, want := condupdating.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) + if conddegraded == nil { + t.Fatal("degraded condition not found") } - // When OSImageStream CR does not exist, status.OSImageStream should be empty - if got, want := status.OSImageStream.Name, ""; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - OSImageStream should be empty when CR does not exist", got, want) + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) } }, }, { - name: "OSImageStream status populated when pool updated and osImageURL matches stream", + name: "0 nodes updates, 0 nodes updating, 0 nodes degraded, pool paused", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV0, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), }, - currentConfig: machineConfigV0, - needsOSImageStreamSetup: true, - initialConditions: []mcfgv1.MachineConfigPoolCondition{ - {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, - {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, - {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, + currentConfig: machineConfigV0, + paused: true, verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - // Verify pool is fully updated + if got, want := status.MachineCount, int32(3); got != want { + t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) + } + + if got, want := status.UpdatedMachineCount, int32(0); got != want { + t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + } + + if got, want := status.ReadyMachineCount, int32(0); got != want { + t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + } + + if got, want := status.UnavailableMachineCount, int32(1); got != want { + t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + } + + if got, want := status.DegradedMachineCount, int32(0); got != want { + t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + } + condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) if condupdated == nil { t.Fatal("updated condition not found") } - if got, want := condupdated.Status, corev1.ConditionTrue; got != want { + + if got, want := condupdated.Status, corev1.ConditionFalse; got != want { t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) } + // The default MachineOSBuild created by the test driver (below) has no build + // conditions set, so it is in its "initial state". A paused pool with a MOSC/MOSB + // pair whose build is in its initial state is still considered "Updating" so + // operators can see that a build has been created but has not started yet. + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build has been created but not yet started (mosb: mosb-1)", condupdating.Message) + conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) if conddegraded == nil { t.Fatal("degraded condition not found") } + if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) } - - // OSImageStream status should be populated with the matching stream - if got, want := status.OSImageStream.Name, "rhel-9"; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q", got, want) - } }, }, { - name: "OSImageStream status empty when pool updated but osImageURL doesn't match any stream (override)", + name: "pool paused, mosc exists but no mosb, waiting for build to start", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV1, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV1, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV1, machineConfigV1, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), }, - currentConfig: machineConfigV1, - needsOSImageStreamSetup: true, - initialConditions: []mcfgv1.MachineConfigPoolCondition{ - {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, - {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, - {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, + currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: nil, verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - // Verify pool is fully updated - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") - } - if got, want := condupdated.Status, corev1.ConditionTrue; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - // OSImageStream status should be empty (override scenario) - if got, want := status.OSImageStream.Name, ""; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty for override scenario", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } + assert.Equal(t, "Pool is paused; waiting for a new OS image build to start (mosc: mosc-1)", condupdating.Message) }, }, { - name: "OSImageStream status empty when pool is updating", + name: "pool paused, mosb exists but is in initial state", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV0, machineConfigV1, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, - currentConfig: machineConfigV1, - needsOSImageStreamSetup: true, - // No initialConditions - let calculateStatus set them based on node states + currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithBuildInitialState().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - // Verify pool is updating condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") @@ -891,301 +1688,165 @@ func TestCalculateStatus(t *testing.T) { if got, want := condupdating.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") - } - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) - } - - // OSImageStream status should be empty when pool is updating - if got, want := status.OSImageStream.Name, ""; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty when updating", got, want) - } + assert.Equal(t, "Pool is paused; OS image build has been created but not yet started (mosb: mosb-1)", condupdating.Message) }, }, { - name: "OSImageStream status empty when pool is degraded", + name: "pool paused, build prepared", nodes: []*corev1.Node{ - helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV0, machineConfigV0, "", "", daemonconsts.MachineConfigDaemonStateDegraded, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV0, machineConfigV0, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV0, machineConfigV0, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), }, - currentConfig: machineConfigV0, - needsOSImageStreamSetup: true, - initialConditions: []mcfgv1.MachineConfigPoolCondition{ - {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionFalse}, - {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, - {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionTrue}, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, + currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithBuildPrepared().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - // Verify pool is degraded - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") - } - if got, want := conddegraded.Status, corev1.ConditionTrue; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - // OSImageStream status should be empty when pool is degraded - if got, want := status.OSImageStream.Name, ""; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty when degraded", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } + assert.Equal(t, "Pool is paused; OS image build has been prepared but will not rollout (mosb: mosb-1)", condupdating.Message) }, }, { - name: "OSImageStream status empty when rendered config has empty osImageURL", + name: "pool paused, build in progress", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", machineConfigV2, machineConfigV2, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", machineConfigV2, machineConfigV2, corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", machineConfigV2, machineConfigV2, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), }, - currentConfig: machineConfigV2, - needsOSImageStreamSetup: true, - initialConditions: []mcfgv1.MachineConfigPoolCondition{ - {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, - {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, - {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, + currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithBuildInProgress().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - // Verify pool is fully updated - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") - } - if got, want := condupdated.Status, corev1.ConditionTrue; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - // OSImageStream status should be empty when osImageURL is empty - if got, want := status.OSImageStream.Name, ""; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q - should be empty when osImageURL is empty", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } + assert.Equal(t, "Pool is paused; OS image build in progress (mosb: mosb-1)", condupdating.Message) }, }, { - name: "OSImageStream status matches second stream when osImageURL matches it", + name: "pool paused, build failed", nodes: []*corev1.Node{ - helpers.NewNodeWithReady("node-0", "rendered-rhel10", "rendered-rhel10", corev1.ConditionTrue), - helpers.NewNodeWithReady("node-1", "rendered-rhel10", "rendered-rhel10", corev1.ConditionTrue), - helpers.NewNodeWithReady("node-2", "rendered-rhel10", "rendered-rhel10", corev1.ConditionTrue), - }, - currentConfig: "rendered-rhel10", - needsOSImageStreamSetup: true, - initialConditions: []mcfgv1.MachineConfigPoolCondition{ - {Type: mcfgv1.MachineConfigPoolUpdated, Status: corev1.ConditionTrue}, - {Type: mcfgv1.MachineConfigPoolUpdating, Status: corev1.ConditionFalse}, - {Type: mcfgv1.MachineConfigPoolDegraded, Status: corev1.ConditionFalse}, - }, - verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - // Verify pool is fully updated - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") - } - if got, want := condupdated.Status, corev1.ConditionTrue; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) - } - - // OSImageStream status should match the second stream (rhel-10) - if got, want := status.OSImageStream.Name, "rhel-10"; got != want { - t.Fatalf("mismatch OSImageStream.Name: got %q want: %q", got, want) - } - }, - }} - for idx, test := range tests { - idx := idx - test := test - t.Run(fmt.Sprintf("case#%d", idx), func(t *testing.T) { - t.Parallel() - pool := &mcfgv1.MachineConfigPool{ - Spec: mcfgv1.MachineConfigPoolSpec{ - Configuration: mcfgv1.MachineConfigPoolStatusConfiguration{ObjectReference: corev1.ObjectReference{Name: test.currentConfig}}, - Paused: test.paused, - OSImageStream: test.osStream, - }, - Status: mcfgv1.MachineConfigPoolStatus{ - Configuration: mcfgv1.MachineConfigPoolStatusConfiguration{ - ObjectReference: corev1.ObjectReference{Name: test.currentConfig}, - }, - }, - } - f := newFixtureWithFeatureGates(t, - []apicfgv1.FeatureGateName{ - features.FeatureGateOSStreams, - }, - []apicfgv1.FeatureGateName{}, - ) - - var c *Controller - if test.needsOSImageStreamSetup { - // Set pool conditions if specified in the test - if len(test.initialConditions) > 0 { - pool.Status.Conditions = test.initialConditions - } - - // Add MachineConfig objects with different osImageURLs for testing - mc0 := &mcfgv1.MachineConfig{ - ObjectMeta: metav1.ObjectMeta{Name: machineConfigV0}, - Spec: mcfgv1.MachineConfigSpec{ - OSImageURL: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel9image", - }, - } - mc1 := &mcfgv1.MachineConfig{ - ObjectMeta: metav1.ObjectMeta{Name: machineConfigV1}, - Spec: mcfgv1.MachineConfigSpec{ - OSImageURL: "quay.io/custom/custom-image:latest", // Custom URL that doesn't match any stream - }, - } - mc2 := &mcfgv1.MachineConfig{ - ObjectMeta: metav1.ObjectMeta{Name: machineConfigV2}, - Spec: mcfgv1.MachineConfigSpec{ - OSImageURL: "", // Empty osImageURL - }, - } - mcRhel10 := &mcfgv1.MachineConfig{ - ObjectMeta: metav1.ObjectMeta{Name: "rendered-rhel10"}, - Spec: mcfgv1.MachineConfigSpec{ - OSImageURL: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel10image", - }, - } - // Add OSImageStream CR with available streams - osImageStream := &mcfgv1.OSImageStream{ - ObjectMeta: metav1.ObjectMeta{ - Name: ctrlcommon.ClusterInstanceNameOSImageStream, - }, - Status: mcfgv1.OSImageStreamStatus{ - DefaultStream: "rhel-9", - AvailableStreams: []mcfgv1.OSImageStreamSet{ - { - Name: "rhel-9", - OSImage: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel9image", - }, - { - Name: "rhel-10", - OSImage: "quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:rhel10image", - }, - }, - }, - } - - // Add all test objects to the fixture - f.objects = append(f.objects, mc0, mc1, mc2, mcRhel10, osImageStream) - - c = f.newController() - - // The controller's informers were already created, but we need to add MC and OSImageStream objects - // to their indexers. We can't access the informer factory from here, so we'll use the client - // that was already created and manually create new informers to populate the listers. - tmpInformer := informers.NewSharedInformerFactory(f.client, noResyncPeriodFunc()) - tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mc0) - tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mc1) - tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mc2) - tmpInformer.Machineconfiguration().V1().MachineConfigs().Informer().GetIndexer().Add(mcRhel10) - tmpInformer.Machineconfiguration().V1().OSImageStreams().Informer().GetIndexer().Add(osImageStream) - - // Replace the controller's listers with the populated ones - c.mcLister = tmpInformer.Machineconfiguration().V1().MachineConfigs().Lister() - c.osImageStreamLister = tmpInformer.Machineconfiguration().V1().OSImageStreams().Lister() - } else { - c = f.newController() - } - - status := c.calculateStatus([]*mcfgv1.MachineConfigNode{}, nil, pool, test.nodes, nil, nil) - test.verify(status, t) - }) - } -} - -// Assisted by: Cursor -// TestCalculateStatusWithImageModeReporting tests the status calculation with ImageModeStatusReporting feature gate enabled -func TestCalculateStatusWithImageModeReporting(t *testing.T) { - t.Parallel() - - // Create feature gate handler that directly enables ImageModeStatusReporting - // This simulates a DevPreview environment where this feature gate is available - fgHandler := ctrlcommon.NewFeatureGatesHardcodedHandler( - []apicfgv1.FeatureGateName{ - features.FeatureGateImageModeStatusReporting, // Enable ImageModeStatusReporting directly + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), }, - []apicfgv1.FeatureGateName{}, - ) - - // Verify that ImageModeStatusReporting is enabled - if !fgHandler.Enabled(features.FeatureGateImageModeStatusReporting) { - t.Skip("ImageModeStatusReporting could not be enabled") - } - - tests := []struct { - name string - nodes []*corev1.Node - mcns []*mcfgv1.MachineConfigNode - currentConfig string - paused bool - verify func(mcfgv1.MachineConfigPoolStatus, *testing.T) - }{{ - name: "0 nodes updated, 0 nodes updating, 0 nodes degraded", + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithFailedBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") + } + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) + } + assert.Equal(t, "Pool is paused; OS image build failed (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool paused, build interrupted", nodes: []*corev1.Node{ - helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), }, mcns: []*mcfgv1.MachineConfigNode{ - helpers.NewMachineConfigNode("node-0", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithInterruptedBuild().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) - } - - if got, want := status.UpdatedMachineCount, int32(0); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) - } - - if got, want := status.ReadyMachineCount, int32(0); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) - } - - if got, want := status.UnavailableMachineCount, int32(0); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.DegradedMachineCount, int32(0); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") + assert.Equal(t, "Pool is paused; OS image build was interrupted (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool paused, build succeeded", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithSuccessfulBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - + assert.Equal(t, "Pool is paused; OS image build completed successfully (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, mosc exists but no mosb, waiting for build to start", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + overrideMosb: true, + mosb: nil, + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - if got, want := condupdating.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") - } - - if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) - } + assert.Equal(t, "Pool is waiting for a new OS image build to start (mosc: mosc-1)", condupdating.Message) }, }, { - name: "0 nodes updated, 1 node updating, 0 nodes degraded", + name: "pool not paused, build in initial state", nodes: []*corev1.Node{ helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), @@ -1197,56 +1858,95 @@ func TestCalculateStatusWithImageModeReporting(t *testing.T) { helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, currentConfig: machineConfigV0, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithBuildInitialState().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) - } - - if got, want := status.UpdatedMachineCount, int32(0); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) - } - - if got, want := status.ReadyMachineCount, int32(0); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) - } - - if got, want := status.UnavailableMachineCount, int32(1); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.DegradedMachineCount, int32(0); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) - if condupdated == nil { - t.Fatal("updated condition not found") + assert.Equal(t, "Pool is waiting for OS image build to start (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build prepared", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithBuildPrepared().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - + assert.Equal(t, "Pool is waiting for OS image build to start (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build in progress", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithBuildInProgress().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - if got, want := condupdating.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") + assert.Equal(t, "Pool is waiting for OS image build to complete (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build failed", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithFailedBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } + assert.Equal(t, "Pool update stopped due to OS image build failure (mosb: mosb-1)", condupdating.Message) }, }, { - name: "0 nodes updates, 0 nodes updating, 0 nodes degraded, pool paused", + name: "pool not paused, build interrupted", nodes: []*corev1.Node{ helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), @@ -1258,54 +1958,73 @@ func TestCalculateStatusWithImageModeReporting(t *testing.T) { helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, currentConfig: machineConfigV0, - paused: true, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithInterruptedBuild().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { - if got, want := status.MachineCount, int32(3); got != want { - t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) - } - - if got, want := status.UpdatedMachineCount, int32(0); got != want { - t.Fatalf("mismatch UpdatedMachineCount: got %d want: %d", got, want) + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.ReadyMachineCount, int32(0); got != want { - t.Fatalf("mismatch ReadyMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionFalse; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - if got, want := status.UnavailableMachineCount, int32(1); got != want { - t.Fatalf("mismatch UnavailableMachineCount: got %d want: %d", got, want) + assert.Equal(t, "Pool update stopped due to OS image build being interrupted (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build succeeded, nodes still applying", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV0, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12346", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV0, "registry.host.com/org/repo@sha256:12345", false, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV0, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV0).WithSuccessfulBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { + condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) + if condupdating == nil { + t.Fatal("updating condition not found") } - - if got, want := status.DegradedMachineCount, int32(0); got != want { - t.Fatalf("mismatch DegradedMachineCount: got %d want: %d", got, want) + if got, want := condupdating.Status, corev1.ConditionTrue; got != want { + t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - + assert.Equal(t, "Pool is waiting for nodes to apply OS image (mosb: mosb-1)", condupdating.Message) + }, + }, { + name: "pool not paused, build succeeded, all nodes updated", + nodes: []*corev1.Node{ + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-0", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-1", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + helpers.NewNodeWithReadyAndDaemonStateAndImageAnnos("node-2", machineConfigV1, machineConfigV1, "registry.host.com/org/repo@sha256:12345", "registry.host.com/org/repo@sha256:12345", daemonconsts.MachineConfigDaemonStateDone, corev1.ConditionTrue), + }, + mcns: []*mcfgv1.MachineConfigNode{ + helpers.NewMachineConfigNode("node-0", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-1", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), + }, + currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithDigestedImagePushspec("registry.host.com/org/repo@sha256:12345").WithSuccessfulBuild().MachineOSBuild(), + verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { condupdated := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdated) if condupdated == nil { t.Fatal("updated condition not found") } - - if got, want := condupdated.Status, corev1.ConditionFalse; got != want { + if got, want := condupdated.Status, corev1.ConditionTrue; got != want { t.Fatalf("mismatch condupdated.Status: got %s want: %s", got, want) } - condupdating := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolUpdating) if condupdating == nil { t.Fatal("updating condition not found") } - if got, want := condupdating.Status, corev1.ConditionFalse; got != want { t.Fatalf("mismatch condupdating.Status: got %s want: %s", got, want) } - - conddegraded := apihelpers.GetMachineConfigPoolCondition(status, mcfgv1.MachineConfigPoolDegraded) - if conddegraded == nil { - t.Fatal("degraded condition not found") - } - - if got, want := conddegraded.Status, corev1.ConditionFalse; got != want { - t.Fatalf("mismatch conddegraded.Status: got %s want: %s", got, want) - } }, }, { name: "0 nodes updated, 1 node updating, 1 node degraded", @@ -1381,6 +2100,8 @@ func TestCalculateStatusWithImageModeReporting(t *testing.T) { helpers.NewMachineConfigNode("node-2", "worker", machineConfigV1, "registry.host.com/org/repo@sha256:12345", true, false), }, currentConfig: machineConfigV1, + overrideMosb: true, + mosb: helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(machineConfigV1).WithDigestedImagePushspec("registry.host.com/org/repo@sha256:12345").WithSuccessfulBuild().MachineOSBuild(), verify: func(status mcfgv1.MachineConfigPoolStatus, t *testing.T) { if got, want := status.MachineCount, int32(3); got != want { t.Fatalf("mismatch MachineCount: got %d want: %d", got, want) @@ -1517,7 +2238,12 @@ func TestCalculateStatusWithImageModeReporting(t *testing.T) { // For ImageModeStatusReporting tests, we need MachineOSConfig and MachineOSBuild // Use the same image that we set in the MCN Status mosc := helpers.NewMachineOSConfigBuilder("mosc-1").WithCurrentImagePullspec("registry.host.com/org/repo@sha256:12345").MachineOSConfig() - mosb := helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(test.currentConfig).MachineOSBuild() + var mosb *mcfgv1.MachineOSBuild + if test.overrideMosb { + mosb = test.mosb + } else { + mosb = helpers.NewMachineOSBuildBuilder("mosb-1").WithDesiredConfig(test.currentConfig).MachineOSBuild() + } c := f.newController() status := c.calculateStatus(test.mcns, nil, pool, test.nodes, mosc, mosb) diff --git a/pkg/controller/osimagestream/osimagestream_controller.go b/pkg/controller/osimagestream/osimagestream_controller.go index e191f30d7b..9f71607682 100644 --- a/pkg/controller/osimagestream/osimagestream_controller.go +++ b/pkg/controller/osimagestream/osimagestream_controller.go @@ -9,9 +9,7 @@ import ( "sync" "time" - configv1 "github.com/openshift/api/config/v1" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" - apioperatorsv1alpha1 "github.com/openshift/api/operator/v1alpha1" configinformersv1 "github.com/openshift/client-go/config/informers/externalversions/config/v1" configlistersv1 "github.com/openshift/client-go/config/listers/config/v1" mcfgclientset "github.com/openshift/client-go/machineconfiguration/clientset/versioned" @@ -19,14 +17,12 @@ import ( mcfginformersv1 "github.com/openshift/client-go/machineconfiguration/informers/externalversions/machineconfiguration/v1" mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1" operatorinformersv1alpha1 "github.com/openshift/client-go/operator/informers/externalversions/operator/v1alpha1" - operatorlistersv1alpha1 "github.com/openshift/client-go/operator/listers/operator/v1alpha1" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" "github.com/openshift/machine-config-operator/pkg/imageutils" "github.com/openshift/machine-config-operator/pkg/osimagestream" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" @@ -62,24 +58,17 @@ type Controller struct { clusterVersionLister configlistersv1.ClusterVersionLister clusterVersionListerSynced cache.InformerSynced - secretLister corelistersv1.SecretLister secretListerSynced cache.InformerSynced mcoCmLister corelistersv1.ConfigMapLister mcoCmListerSynced cache.InformerSynced - icspLister operatorlistersv1alpha1.ImageContentSourcePolicyLister icspListerSynced cache.InformerSynced - - idmsLister configlistersv1.ImageDigestMirrorSetLister idmsListerSynced cache.InformerSynced - - itmsLister configlistersv1.ImageTagMirrorSetLister itmsListerSynced cache.InformerSynced + imgListerSynced cache.InformerSynced - imgLister configlistersv1.ImageLister - imgListerSynced cache.InformerSynced - + sysCtxFactory imageutils.SysContextFactory inspectorFactory osimagestream.ImagesInspectorFactory queue workqueue.TypedRateLimitingInterface[string] @@ -136,24 +125,21 @@ func New( ctrl.clusterVersionLister = clusterVersionInformer.Lister() ctrl.clusterVersionListerSynced = clusterVersionInformer.Informer().HasSynced - ctrl.secretLister = secretInformer.Lister() ctrl.secretListerSynced = secretInformer.Informer().HasSynced ctrl.mcoCmLister = cmInformer.Lister() ctrl.mcoCmListerSynced = cmInformer.Informer().HasSynced - ctrl.icspLister = icspInformer.Lister() ctrl.icspListerSynced = icspInformer.Informer().HasSynced - - ctrl.idmsLister = idmsInformer.Lister() ctrl.idmsListerSynced = idmsInformer.Informer().HasSynced - - ctrl.itmsLister = itmsInformer.Lister() ctrl.itmsListerSynced = itmsInformer.Informer().HasSynced - - ctrl.imgLister = imgInformer.Lister() ctrl.imgListerSynced = imgInformer.Informer().HasSynced + ctrl.sysCtxFactory = ctrlcommon.NewSysContextFactory( + ccInformer.Lister(), secretInformer.Lister(), imgInformer.Lister(), + icspInformer.Lister(), idmsInformer.Lister(), itmsInformer.Lister(), + ) + return ctrl } @@ -370,11 +356,6 @@ func (ctrl *Controller) getExistingOSImageStream() (*mcfgv1.OSImageStream, error func (ctrl *Controller) buildOSImageStream(ctx context.Context, existing *mcfgv1.OSImageStream) error { klog.Info("Starting building of the OSImageStream instance") - cc, err := ctrl.ccLister.Get(ctrlcommon.ControllerConfigName) - if err != nil { - return fmt.Errorf("getting ControllerConfig for OSImageStream build: %w", err) - } - clusterVersion, err := osimagestream.GetClusterVersion(ctrl.clusterVersionLister) if err != nil { return fmt.Errorf("getting cluster version for OSImageStream inspection: %w", err) @@ -389,26 +370,13 @@ func (ctrl *Controller) buildOSImageStream(ctx context.Context, existing *mcfgv1 klog.Warningf("Unable to get install version for OSImageStream build: %s", err) } - clusterPullSecret, err := ctrl.secretLister.Secrets(ctrlcommon.OpenshiftConfigNamespace).Get("pull-secret") - if err != nil { - return fmt.Errorf("could not get the cluster PullSecret for OSImageStream sync: %w", err) - } - - sysCtxFactory := func() (*imageutils.SysContext, error) { - builder, err := ctrl.getSysContextBuilder(clusterPullSecret, cc) - if err != nil { - return nil, fmt.Errorf("could not build SysContext for OSImageStream build: %w", err) - } - return builder.Build() - } - buildCtx, buildCancel := context.WithTimeout(ctx, 5*time.Minute) defer buildCancel() factory := osimagestream.NewDefaultStreamSourceFactory(ctrl.inspectorFactory) osImageStream, err := factory.Create( buildCtx, - sysCtxFactory, + ctrl.sysCtxFactory, osimagestream.CreateOptions{ ReleaseImage: image, ConfigMapLister: ctrl.mcoCmLister, @@ -498,50 +466,6 @@ func osImageStreamRequiresRebuild(osImageStream *mcfgv1.OSImageStream, releaseIm return !ok || storedVersion != version.Hash } -func (ctrl *Controller) getSysContextBuilder(clusterPullSecret *corev1.Secret, cc *mcfgv1.ControllerConfig) (*imageutils.SysContextBuilder, error) { - sysCtxBuilder := imageutils.NewSysContextBuilder(). - WithSecret(clusterPullSecret). - WithControllerConfig(cc) - - imageConfig, err := ctrl.imgLister.Get("cluster") - if err != nil && !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("could not get image configuration: %w", err) - } - - icspRules, err := ctrl.icspLister.List(labels.Everything()) - if err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("could not get ICSP rules: %w", err) - } - icspRules = []*apioperatorsv1alpha1.ImageContentSourcePolicy{} - } - - idmsRules, err := ctrl.idmsLister.List(labels.Everything()) - if err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("could not get IDMS rules: %w", err) - } - idmsRules = []*configv1.ImageDigestMirrorSet{} - } - - itmsRules, err := ctrl.itmsLister.List(labels.Everything()) - if err != nil { - if !apierrors.IsNotFound(err) { - return nil, fmt.Errorf("could not get ITMS rules: %w", err) - } - itmsRules = []*configv1.ImageTagMirrorSet{} - } - - if imageConfig != nil || len(icspRules) != 0 || len(idmsRules) != 0 || len(itmsRules) != 0 { - registriesConf, err := imageutils.GenerateRegistriesConfig(imageConfig, icspRules, idmsRules, itmsRules) - if err != nil { - return nil, fmt.Errorf("could not build registries configuration: %w", err) - } - sysCtxBuilder.WithRegistriesConfig(registriesConf) - } - - return sysCtxBuilder, nil -} // Retain implements CacheEvicter — it keeps cached digests referenced by the // OSImage and OSExtensionsImage of each available stream in the OSImageStream. diff --git a/pkg/controller/render/render_controller.go b/pkg/controller/render/render_controller.go index e2a96879eb..25d6a703cd 100644 --- a/pkg/controller/render/render_controller.go +++ b/pkg/controller/render/render_controller.go @@ -11,17 +11,19 @@ import ( "github.com/openshift/api/features" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" opv1 "github.com/openshift/api/operator/v1" + configinformersv1 "github.com/openshift/client-go/config/informers/externalversions/config/v1" mcfgclientset "github.com/openshift/client-go/machineconfiguration/clientset/versioned" "github.com/openshift/client-go/machineconfiguration/clientset/versioned/scheme" mcfginformersv1 "github.com/openshift/client-go/machineconfiguration/informers/externalversions/machineconfiguration/v1" mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1" mcopinformersv1 "github.com/openshift/client-go/operator/informers/externalversions/operator/v1" + operatorinformersv1alpha1 "github.com/openshift/client-go/operator/informers/externalversions/operator/v1alpha1" mcoplistersv1 "github.com/openshift/client-go/operator/listers/operator/v1" mcoResourceApply "github.com/openshift/machine-config-operator/lib/resourceapply" "github.com/openshift/machine-config-operator/pkg/apihelpers" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" - "github.com/openshift/machine-config-operator/pkg/imageutils" daemonconsts "github.com/openshift/machine-config-operator/pkg/daemon/constants" + "github.com/openshift/machine-config-operator/pkg/imageutils" "github.com/openshift/machine-config-operator/pkg/osimagestream" "github.com/openshift/machine-config-operator/pkg/version" corev1 "k8s.io/api/core/v1" @@ -32,6 +34,7 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/wait" + coreinformersv1 "k8s.io/client-go/informers/core/v1" clientset "k8s.io/client-go/kubernetes" corev1client "k8s.io/client-go/kubernetes/typed/core/v1" "k8s.io/client-go/tools/cache" @@ -65,7 +68,7 @@ type Controller struct { client mcfgclientset.Interface eventRecorder record.EventRecorder - syncHandler func(mcp string) error + syncHandler func(ctx context.Context, mcp string) error enqueueMachineConfigPool func(*mcfgv1.MachineConfigPool) mcpLister mcfglistersv1.MachineConfigPoolLister @@ -88,8 +91,16 @@ type Controller struct { mcopLister mcoplistersv1.MachineConfigurationLister mcopListerSynced cache.InformerSynced + secretListerSynced cache.InformerSynced + icspListerSynced cache.InformerSynced + idmsListerSynced cache.InformerSynced + itmsListerSynced cache.InformerSynced + imgListerSynced cache.InformerSynced + fgHandler ctrlcommon.FeatureGatesHandler + imageInspector osimagestream.StreamClassInspector + queue workqueue.TypedRateLimitingInterface[string] } @@ -102,9 +113,15 @@ func New( mckInformer mcfginformersv1.KubeletConfigInformer, mcopInformer mcopinformersv1.MachineConfigurationInformer, osImageStreamInformer mcfginformersv1.OSImageStreamInformer, + secretInformer coreinformersv1.SecretInformer, + icspInformer operatorinformersv1alpha1.ImageContentSourcePolicyInformer, + idmsInformer configinformersv1.ImageDigestMirrorSetInformer, + itmsInformer configinformersv1.ImageTagMirrorSetInformer, + imgInformer configinformersv1.ImageInformer, kubeClient clientset.Interface, mcfgClient mcfgclientset.Interface, featureGatesHandler ctrlcommon.FeatureGatesHandler, + inspectorFactory osimagestream.ImagesInspectorFactory, ) *Controller { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(klog.Infof) @@ -146,6 +163,23 @@ func New( ctrl.mcopLister = mcopInformer.Lister() ctrl.mcopListerSynced = mcopInformer.Informer().HasSynced + if inspectorFactory != nil { + ctrl.imageInspector = osimagestream.NewStreamClassInspector(inspectorFactory, + ctrlcommon.NewSysContextFactory( + ccInformer.Lister(), + secretInformer.Lister(), + imgInformer.Lister(), + icspInformer.Lister(), + idmsInformer.Lister(), + itmsInformer.Lister(), + )) + ctrl.secretListerSynced = secretInformer.Informer().HasSynced + ctrl.icspListerSynced = icspInformer.Informer().HasSynced + ctrl.idmsListerSynced = idmsInformer.Informer().HasSynced + ctrl.itmsListerSynced = itmsInformer.Informer().HasSynced + ctrl.imgListerSynced = imgInformer.Informer().HasSynced + } + if osImageStreamInformer != nil && osimagestream.IsFeatureEnabled(ctrl.fgHandler) { ctrl.osImageStreamLister = osImageStreamInformer.Lister() ctrl.osImageStreamListerSynced = osImageStreamInformer.Informer().HasSynced @@ -161,8 +195,22 @@ func (ctrl *Controller) Run(workers int, stopCh <-chan struct{}) { defer utilruntime.HandleCrash() defer ctrl.queue.ShutDown() + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go func() { + <-stopCh + cancel() + }() + listerCaches := []cache.InformerSynced{ctrl.mcpListerSynced, ctrl.mcListerSynced, ctrl.ccListerSynced} + if ctrl.secretListerSynced != nil { + listerCaches = append(listerCaches, + ctrl.secretListerSynced, ctrl.icspListerSynced, ctrl.idmsListerSynced, + ctrl.itmsListerSynced, ctrl.imgListerSynced, + ) + } + // OSImageStreams and MCPs fetched only if FeatureGateOSStreams active if ctrl.osImageStreamListerSynced != nil { listerCaches = append(listerCaches, ctrl.osImageStreamListerSynced) @@ -175,7 +223,7 @@ func (ctrl *Controller) Run(workers int, stopCh <-chan struct{}) { defer klog.Info("Shutting down MachineConfigController-RenderController") for i := 0; i < workers; i++ { - go wait.Until(ctrl.worker, time.Second, stopCh) + go wait.Until(func() { ctrl.worker(ctx) }, time.Second, stopCh) } <-stopCh @@ -449,19 +497,19 @@ func (ctrl *Controller) enqueueCustomPools() { // worker runs a worker thread that just dequeues items, processes them, and marks them done. // It enforces that the syncHandler is never invoked concurrently with the same key. -func (ctrl *Controller) worker() { - for ctrl.processNextWorkItem() { +func (ctrl *Controller) worker(ctx context.Context) { + for ctrl.processNextWorkItem(ctx) { } } -func (ctrl *Controller) processNextWorkItem() bool { +func (ctrl *Controller) processNextWorkItem(ctx context.Context) bool { key, quit := ctrl.queue.Get() if quit { return false } defer ctrl.queue.Done(key) - err := ctrl.syncHandler(key) + err := ctrl.syncHandler(ctx, key) ctrl.handleErr(err, key) return true @@ -487,7 +535,7 @@ func (ctrl *Controller) handleErr(err error, key string) { // syncMachineConfigPool will sync the machineconfig pool with the given key. // This function is not meant to be invoked concurrently with the same key. -func (ctrl *Controller) syncMachineConfigPool(key string) error { +func (ctrl *Controller) syncMachineConfigPool(ctx context.Context, key string) error { startTime := time.Now() klog.V(4).Infof("Started syncing machineconfigpool %q (%v)", key, startTime) defer func() { @@ -540,7 +588,7 @@ func (ctrl *Controller) syncMachineConfigPool(key string) error { return ctrl.syncFailingStatus(pool, fmt.Errorf("no MachineConfigs found matching selector %v", selector)) } - if err := ctrl.syncGeneratedMachineConfig(pool, mcs); err != nil { + if err := ctrl.syncGeneratedMachineConfig(ctx, pool, mcs); err != nil { klog.Errorf("Error syncing Generated MCFG: %v", err) return ctrl.syncFailingStatus(pool, err) } @@ -679,7 +727,7 @@ func (ctrl *Controller) getOSImageStreamForPool(pool *mcfgv1.MachineConfigPool) return imageStreamSet, nil } -func (ctrl *Controller) syncGeneratedMachineConfig(pool *mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineConfig) error { +func (ctrl *Controller) syncGeneratedMachineConfig(ctx context.Context, pool *mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineConfig) error { if len(configs) == 0 { return nil } @@ -714,6 +762,18 @@ func (ctrl *Controller) syncGeneratedMachineConfig(pool *mcfgv1.MachineConfigPoo ctrlcommon.OSImageURLOverride.WithLabelValues(pool.Name).Set(0) } + // Check for runc on RHEL 10. When the OSImageURL was overridden or no + // stream is available, inspect the actual image; otherwise use the stream. + if isOSImageURLOverridden { + if err := validateNoRuncOnRHEL10FromOSImageURL(ctx, pool, generated, ctrl.imageInspector); err != nil { + return err + } + } else if osImageStreamSet != nil { + if err := validateNoRuncOnRHEL10FromOSImageStream(pool.Name, generated, osImageStreamSet); err != nil { + return err + } + } + source := getMachineConfigRefs(configs) _, err = ctrl.mcLister.Get(generated.Name) @@ -807,7 +867,7 @@ func generateRenderedMachineConfig(pool *mcfgv1.MachineConfigPool, configs []*mc // OSImageURL check during upgrade -- if the user took over managing OS upgrades this way, // the operator shouldn't stop the rest of the upgrade from progressing/completing. if merged.Spec.OSImageURL != ctrlcommon.GetBaseImageContainer(&cconfig.Spec, osImageStreamSet) { - merged.Annotations[ctrlcommon.OSImageURLOverriddenKey] = "true" + merged.Annotations[ctrlcommon.OSImageURLOverriddenKey] = ctrlcommon.OSImageURLOverriddenTrue // Log a warning if the osImageURL is set using a tag instead of a digest if !strings.Contains(merged.Spec.OSImageURL, "sha256:") { klog.Warningf("OSImageURL %q for MachineConfig %s is set using a tag instead of a digest. It is highly recommended to use a digest", merged.Spec.OSImageURL, merged.Name) @@ -826,10 +886,6 @@ func generateRenderedMachineConfig(pool *mcfgv1.MachineConfigPool, configs []*mc } } - if err := validateNoRuncOnRHEL10(pool.Name, merged, osImageStreamSet); err != nil { - return nil, err - } - return merged, nil } @@ -920,7 +976,7 @@ func getOSImageStreamNameForPoolBootstrap(pool *mcfgv1.MachineConfigPool, pools // RunBootstrap runs the render controller in bootstrap mode. // For each pool, it matches the machineconfigs based on label selector and // returns the generated machineconfigs and pool with CurrentMachineConfig status field set. -func RunBootstrap(pools []*mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineConfig, cconfig *mcfgv1.ControllerConfig, osImageStream *mcfgv1.OSImageStream) ([]*mcfgv1.MachineConfigPool, []*mcfgv1.MachineConfig, error) { +func RunBootstrap(ctx context.Context, pools []*mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineConfig, cconfig *mcfgv1.ControllerConfig, osImageStream *mcfgv1.OSImageStream, inspector osimagestream.StreamClassInspector) ([]*mcfgv1.MachineConfigPool, []*mcfgv1.MachineConfig, error) { var ( opools []*mcfgv1.MachineConfigPool oconfigs []*mcfgv1.MachineConfig @@ -945,6 +1001,19 @@ func RunBootstrap(pools []*mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineCo return nil, nil, err } + // Check for runc on RHEL 10. When the OSImageURL was overridden or no + // stream is available, inspect the actual image; otherwise use the stream. + isOverridden := generated.Annotations[ctrlcommon.OSImageURLOverriddenKey] == ctrlcommon.OSImageURLOverriddenTrue + if isOverridden { + if err := validateNoRuncOnRHEL10FromOSImageURL(ctx, pool, generated, inspector); err != nil { + return nil, nil, err + } + } else if osImageStreamSet != nil { + if err := validateNoRuncOnRHEL10FromOSImageStream(pool.Name, generated, osImageStreamSet); err != nil { + return nil, nil, err + } + } + source := getMachineConfigRefs(configs) pool.Spec.Configuration.Name = generated.Name @@ -957,33 +1026,66 @@ func RunBootstrap(pools []*mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineCo return opools, oconfigs, nil } -// validateNoRuncOnRHEL10 returns an error if the generated MachineConfig uses runc +// validateNoRuncOnRHEL10FromOSImageStream returns an error if the generated MachineConfig uses runc // as the default container runtime and the pool targets a RHEL 10 / CentOS 10 OS -// image stream. runc is not available on RHCOS 10; clusters must migrate to crun -// before switching to a RHEL 10 stream. -// TODO(OCP 5.3): Remove this guard once RHEL 9 is no longer supported and all nodes run RHEL 10. -func validateNoRuncOnRHEL10(poolName string, mc *mcfgv1.MachineConfig, osImageStreamSet *mcfgv1.OSImageStreamSet) error { +// image stream. +func validateNoRuncOnRHEL10FromOSImageStream(poolName string, mc *mcfgv1.MachineConfig, osImageStreamSet *mcfgv1.OSImageStreamSet) error { if osImageStreamSet == nil { return nil } if !osimagestream.IsRHEL10Stream(osImageStreamSet.Name) { return nil } - runcMCName, err := ctrlcommon.DetectRuncInMachineConfig(mc) if err != nil { return fmt.Errorf("failed to check runc in generated MachineConfig for pool %s: %w", poolName, err) } if runcMCName != "" { return fmt.Errorf( - "MachineConfigPool %s targets OS image stream %q where runc is not available. "+ + "MachineConfigPool %s targets a RHEL 10 OS image where runc is not available. "+ "To unblock, migrate to crun by removing any ContainerRuntimeConfig that sets defaultRuntime to runc, "+ "and removing any MachineConfig that sets default_runtime = \"runc\" in CRI-O configuration under /etc/crio/crio.conf.d/", - poolName, osImageStreamSet.Name) + poolName) } return nil } +// validateNoRuncOnRHEL10FromOSImageURL checks whether the generated MachineConfig uses +// runc on a RHEL 10 image by inspecting the container image's labels. +func validateNoRuncOnRHEL10FromOSImageURL(ctx context.Context, pool *mcfgv1.MachineConfigPool, generated *mcfgv1.MachineConfig, inspector osimagestream.StreamClassInspector) error { + osImageURL := generated.Spec.OSImageURL + if osImageURL == "" || inspector == nil { + return nil + } + + // Check runc first: this is a local config check that doesn't need network access. + runcMCName, err := ctrlcommon.DetectRuncInMachineConfig(generated) + if err != nil { + return fmt.Errorf("pool %s: failed to check runc in generated MachineConfig: %w", pool.Name, err) + } + if runcMCName == "" { + return nil + } + + streamClass, err := inspector.InspectStreamClass(ctx, osImageURL) + if err != nil { + return fmt.Errorf("pool %s: failed to inspect OS image for stream class: %w", pool.Name, err) + } + if streamClass == "" { + klog.V(4).Infof("Pool %s: OS image has no stream class label; skipping runc check", pool.Name) + return nil + } + if !osimagestream.IsRHEL10Stream(streamClass) { + return nil + } + + return fmt.Errorf( + "MachineConfigPool %s targets a RHEL 10 OS image where runc is not available. "+ + "To unblock, migrate to crun by removing any ContainerRuntimeConfig that sets defaultRuntime to runc, "+ + "and removing any MachineConfig that sets default_runtime = \"runc\" in CRI-O configuration under /etc/crio/crio.conf.d/", + pool.Name) +} + // getMachineConfigsForPool is called by RunBootstrap and returns configs that match label from configs for a pool. func getMachineConfigsForPool(pool *mcfgv1.MachineConfigPool, configs []*mcfgv1.MachineConfig) ([]*mcfgv1.MachineConfig, error) { selector, err := metav1.LabelSelectorAsSelector(pool.Spec.MachineConfigSelector) diff --git a/pkg/controller/render/render_controller_test.go b/pkg/controller/render/render_controller_test.go index d8ed8f155a..176bc5647d 100644 --- a/pkg/controller/render/render_controller_test.go +++ b/pkg/controller/render/render_controller_test.go @@ -1,6 +1,7 @@ package render import ( + "context" "fmt" "reflect" "testing" @@ -33,6 +34,7 @@ import ( informers "github.com/openshift/client-go/machineconfiguration/informers/externalversions" mcfglistersv1 "github.com/openshift/client-go/machineconfiguration/listers/machineconfiguration/v1" ctrlcommon "github.com/openshift/machine-config-operator/pkg/controller/common" + "github.com/openshift/machine-config-operator/pkg/osimagestream" daemonconsts "github.com/openshift/machine-config-operator/pkg/daemon/constants" "github.com/openshift/machine-config-operator/pkg/version" "github.com/openshift/machine-config-operator/test/helpers" @@ -83,7 +85,10 @@ func (f *fixture) newController() *Controller { c := New(i.Machineconfiguration().V1().MachineConfigPools(), i.Machineconfiguration().V1().MachineConfigs(), i.Machineconfiguration().V1().ControllerConfigs(), i.Machineconfiguration().V1().ContainerRuntimeConfigs(), i.Machineconfiguration().V1().KubeletConfigs(), oi.Operator().V1().MachineConfigurations(), - i.Machineconfiguration().V1().OSImageStreams(), k8sfake.NewSimpleClientset(), f.client, f.fgHandler) + i.Machineconfiguration().V1().OSImageStreams(), + nil, nil, nil, nil, nil, + k8sfake.NewSimpleClientset(), f.client, f.fgHandler, + nil) c.mcpListerSynced = alwaysReady c.mcListerSynced = alwaysReady @@ -133,7 +138,7 @@ func (f *fixture) runExpectError(mcpname string) { func (f *fixture) runController(mcpname string, expectError bool) { c := f.newController() - err := c.syncHandler(mcpname) + err := c.syncHandler(context.Background(), mcpname) if !expectError && err != nil { f.t.Errorf("error syncing machineconfigpool: %v", err) } else if expectError && err == nil { @@ -1081,7 +1086,7 @@ func TestValidateNoRuncOnRHEL10(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - err := validateNoRuncOnRHEL10("worker", tt.mc, tt.osImageStreamSet) + err := validateNoRuncOnRHEL10FromOSImageStream("worker", tt.mc, tt.osImageStreamSet) if tt.expectError { require.Error(t, err) assert.Contains(t, err.Error(), "runc") @@ -1092,6 +1097,121 @@ func TestValidateNoRuncOnRHEL10(t *testing.T) { } } +type fakeStreamClassInspector struct { + streamClass string + err error +} + +func (f *fakeStreamClassInspector) InspectStreamClass(context.Context, string) (string, error) { + return f.streamClass, f.err +} + +func TestValidateNoRuncOnRHEL10FromOSImageURL(t *testing.T) { + pool := &mcfgv1.MachineConfigPool{ + ObjectMeta: metav1.ObjectMeta{Name: "worker"}, + } + + runcMC := func(osImageURL string) *mcfgv1.MachineConfig { + mc := helpers.NewMachineConfig("rendered-worker", nil, "", []ign3types.File{ + helpers.CreateEncodedIgn3File("/etc/crio/crio.conf.d/00-default", makeCRIODropIn("runc"), 0644), + }) + mc.Spec.OSImageURL = osImageURL + return mc + } + + crunMC := func(osImageURL string) *mcfgv1.MachineConfig { + mc := helpers.NewMachineConfig("rendered-worker", nil, "", []ign3types.File{ + helpers.CreateEncodedIgn3File("/etc/crio/crio.conf.d/00-default", makeCRIODropIn("crun"), 0644), + }) + mc.Spec.OSImageURL = osImageURL + return mc + } + + tests := []struct { + name string + mc *mcfgv1.MachineConfig + inspector *fakeStreamClassInspector + expectError bool + }{ + { + name: "runc on RHEL 10 image should error", + mc: runcMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: &fakeStreamClassInspector{streamClass: "rhel-10"}, + expectError: true, + }, + { + name: "crun on RHEL 10 image should succeed", + mc: crunMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: &fakeStreamClassInspector{streamClass: "rhel-10"}, + expectError: false, + }, + { + name: "runc on RHEL 9 image should succeed", + mc: runcMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: &fakeStreamClassInspector{streamClass: "rhel-9"}, + expectError: false, + }, + { + name: "runc on CentOS 10 image should error", + mc: runcMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: &fakeStreamClassInspector{streamClass: "centos-10"}, + expectError: true, + }, + { + name: "runc with empty OSImageURL should succeed", + mc: runcMC(""), + inspector: &fakeStreamClassInspector{streamClass: "rhel-10"}, + expectError: false, + }, + { + name: "runc with nil inspector should succeed", + mc: runcMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: nil, + expectError: false, + }, + { + name: "runc with empty stream class label should succeed", + mc: runcMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: &fakeStreamClassInspector{streamClass: ""}, + expectError: false, + }, + { + name: "runc with inspector error should error", + mc: runcMC("quay.io/openshift/rhcos@sha256:abc"), + inspector: &fakeStreamClassInspector{err: fmt.Errorf("connection refused")}, + expectError: true, + }, + { + name: "runc overridden by crun on RHEL 10 image should succeed", + mc: func() *mcfgv1.MachineConfig { + mc := helpers.NewMachineConfig("rendered-worker", nil, "", []ign3types.File{ + helpers.CreateEncodedIgn3File("/etc/crio/crio.conf.d/00-default", makeCRIODropIn("runc"), 0644), + helpers.CreateEncodedIgn3File("/etc/crio/crio.conf.d/01-ctrcfg", makeCRIODropIn("crun"), 0644), + }) + mc.Spec.OSImageURL = "quay.io/openshift/rhcos@sha256:abc" + return mc + }(), + inspector: &fakeStreamClassInspector{streamClass: "rhel-10"}, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var inspector osimagestream.StreamClassInspector + if tt.inspector != nil { + inspector = tt.inspector + } + err := validateNoRuncOnRHEL10FromOSImageURL(context.Background(), pool, tt.mc, inspector) + if tt.expectError { + require.Error(t, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestRunBootstrapBlocksRuncOnRHEL10(t *testing.T) { pool := &mcfgv1.MachineConfigPool{ ObjectMeta: metav1.ObjectMeta{Name: "worker"}, @@ -1126,10 +1246,12 @@ func TestRunBootstrapBlocksRuncOnRHEL10(t *testing.T) { } _, _, err := RunBootstrap( + context.Background(), []*mcfgv1.MachineConfigPool{pool}, []*mcfgv1.MachineConfig{runcMC}, cc, osImageStream, + nil, ) require.Error(t, err) assert.Contains(t, err.Error(), "runc") diff --git a/pkg/osimagestream/helpers.go b/pkg/osimagestream/helpers.go index 00adceaa92..d757fc1a47 100644 --- a/pkg/osimagestream/helpers.go +++ b/pkg/osimagestream/helpers.go @@ -5,7 +5,6 @@ import ( "github.com/openshift/api/features" mcfgv1 "github.com/openshift/api/machineconfiguration/v1" "github.com/openshift/machine-config-operator/pkg/controller/common" - "github.com/openshift/machine-config-operator/pkg/version" k8serrors "k8s.io/apimachinery/pkg/api/errors" ) @@ -37,10 +36,9 @@ func GetOSImageStreamSetByName(osImageStream *mcfgv1.OSImageStream, name string) return nil, k8serrors.NewNotFound(mcfgv1.GroupVersion.WithResource("osimagestreams").GroupResource(), name) } -// IsFeatureEnabled checks if the OSImageStream feature is enabled. -// Returns true only if the FeatureGateOSStreams is enabled and the cluster is not running SCOS or FCOS. +// IsFeatureEnabled checks if the OSImageStream feature is enabled. . func IsFeatureEnabled(fgHandler common.FeatureGatesHandler) bool { - return fgHandler.Enabled(features.FeatureGateOSStreams) && !version.IsSCOS() && !version.IsFCOS() + return fgHandler.Enabled(features.FeatureGateOSStreams) } // GetOSImageStreamSpecDefault returns the user-requested default stream override, or empty string if not set. diff --git a/pkg/osimagestream/stream_class_inspector.go b/pkg/osimagestream/stream_class_inspector.go new file mode 100644 index 0000000000..3347045a49 --- /dev/null +++ b/pkg/osimagestream/stream_class_inspector.go @@ -0,0 +1,60 @@ +package osimagestream + +import ( + "context" + "fmt" + "time" + + "github.com/openshift/machine-config-operator/pkg/imageutils" +) + +// StreamClassInspector inspects a container image URL and returns its OS Image +// Stream name. +type StreamClassInspector interface { + InspectStreamClass(ctx context.Context, imageURL string) (string, error) +} + +type streamClassInspector struct { + inspectorFactory ImagesInspectorFactory + sysCtxFactory imageutils.SysContextFactory +} + +// NewStreamClassInspector creates a StreamClassInspector that resolves the OS +// Image Stream name by inspecting image labels via the given factory. +func NewStreamClassInspector(inspectorFactory ImagesInspectorFactory, sysCtxFactory imageutils.SysContextFactory) StreamClassInspector { + return &streamClassInspector{ + inspectorFactory: inspectorFactory, + sysCtxFactory: sysCtxFactory, + } +} + +func (s *streamClassInspector) InspectStreamClass(ctx context.Context, imageURL string) (string, error) { + ctx, cancel := context.WithTimeout(ctx, 30*time.Second) + defer cancel() + + inspector := s.inspectorFactory.ForContext(s.sysCtxFactory) + return inspectStreamClass(ctx, inspector, imageURL) +} + +func inspectStreamClass(ctx context.Context, inspector ImagesInspector, imageURL string) (string, error) { + results, err := inspector.Inspect(ctx, imageURL) + if err != nil { + return "", fmt.Errorf("failed to inspect OS image: %w", err) + } + if len(results) == 0 { + return "", fmt.Errorf("no inspection result for OS image") + } + if results[0].Error != nil { + return "", fmt.Errorf("failed to inspect OS image: %w", results[0].Error) + } + if results[0].InspectInfo == nil { + return "", nil + } + + extractor := NewImageStreamExtractor() + imageData := extractor.GetImageData(imageURL, results[0].InspectInfo.Labels) + if imageData == nil { + return "", nil + } + return imageData.Stream, nil +} diff --git a/pkg/osimagestream/stream_class_inspector_test.go b/pkg/osimagestream/stream_class_inspector_test.go new file mode 100644 index 0000000000..d287037bf8 --- /dev/null +++ b/pkg/osimagestream/stream_class_inspector_test.go @@ -0,0 +1,128 @@ +package osimagestream + +import ( + "context" + "errors" + "testing" + + "github.com/containers/image/v5/types" + "github.com/openshift/machine-config-operator/pkg/imageutils" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func noopSysCtxFactory() (*imageutils.SysContext, error) { + return &imageutils.SysContext{}, nil +} + +func TestInspectStreamClass(t *testing.T) { + const testImage = "quay.io/openshift/rhcos@sha256:abc123" + + tests := []struct { + name string + inspector ImagesInspector + wantClass string + wantErr bool + }{ + { + name: "RHEL 10", + inspector: &mockImagesInspector{ + inspectData: map[string]*types.ImageInspectInfo{ + testImage: {Labels: map[string]string{ + "io.openshift.os.streamclass": "rhel-10", + "containers.bootc": "1", + }}, + }, + }, + wantClass: "rhel-10", + }, + { + name: "RHEL 9", + inspector: &mockImagesInspector{ + inspectData: map[string]*types.ImageInspectInfo{ + testImage: {Labels: map[string]string{ + "io.openshift.os.streamclass": "rhel-9", + "containers.bootc": "true", + }}, + }, + }, + wantClass: "rhel-9", + }, + { + name: "CentOS 10", + inspector: &mockImagesInspector{ + inspectData: map[string]*types.ImageInspectInfo{ + testImage: {Labels: map[string]string{ + "io.openshift.os.streamclass": "centos-10", + "containers.bootc": "1", + }}, + }, + }, + wantClass: "centos-10", + }, + { + name: "no stream class label", + inspector: &mockImagesInspector{ + inspectData: map[string]*types.ImageInspectInfo{ + testImage: {Labels: map[string]string{ + "containers.bootc": "true", + }}, + }, + }, + wantClass: "", + }, + { + name: "empty labels", + inspector: &mockImagesInspector{ + inspectData: map[string]*types.ImageInspectInfo{ + testImage: {Labels: map[string]string{}}, + }, + }, + wantClass: "", + }, + { + name: "nil InspectInfo", + inspector: &mockImagesInspector{ + results: []imageutils.BulkInspectResult{ + {Image: testImage, InspectInfo: nil}, + }, + }, + wantClass: "", + }, + { + name: "inspector returns error", + inspector: &mockImagesInspector{err: errors.New("connection refused")}, + wantErr: true, + }, + { + name: "result contains error", + inspector: &mockImagesInspector{ + results: []imageutils.BulkInspectResult{ + {Image: testImage, Error: errors.New("manifest unknown")}, + }, + }, + wantErr: true, + }, + { + name: "empty results", + inspector: &mockImagesInspector{ + results: []imageutils.BulkInspectResult{}, + }, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + factory := &mockImagesInspectorFactory{inspector: tt.inspector} + sci := NewStreamClassInspector(factory, noopSysCtxFactory) + sc, err := sci.InspectStreamClass(context.Background(), testImage) + if tt.wantErr { + require.Error(t, err) + return + } + require.NoError(t, err) + assert.Equal(t, tt.wantClass, sc) + }) + } +} diff --git a/test/e2e-bootstrap/bootstrap_test.go b/test/e2e-bootstrap/bootstrap_test.go index 7bb7996d3a..e56c3fc1f1 100644 --- a/test/e2e-bootstrap/bootstrap_test.go +++ b/test/e2e-bootstrap/bootstrap_test.go @@ -380,7 +380,7 @@ metadata: } // Run the bootstrap - bootstrapper := bootstrap.New(templatesDir, srcDir, filepath.Join(bootstrapTestDataDir, "/machineconfigcontroller-pull-secret")) + bootstrapper := bootstrap.New(templatesDir, srcDir, filepath.Join(bootstrapTestDataDir, "/machineconfigcontroller-pull-secret"), nil) err = bootstrapper.Run(destDir) require.NoError(t, err) @@ -613,9 +613,11 @@ func createControllers(ctx *ctrlcommon.ControllerContext) []ctrlcommon.Controlle ctx.InformerFactory.Machineconfiguration().V1().KubeletConfigs(), ctx.OperatorInformerFactory.Operator().V1().MachineConfigurations(), ctx.InformerFactory.Machineconfiguration().V1().OSImageStreams(), + nil, nil, nil, nil, nil, ctx.ClientBuilder.KubeClientOrDie("render-controller"), ctx.ClientBuilder.MachineConfigClientOrDie("render-controller"), ctx.FeatureGatesHandler, + nil, ), // The node controller consumes data written by the above node.New( diff --git a/test/e2e-ocl-2of2/onclusterlayering_test.go b/test/e2e-ocl-2of2/onclusterlayering_test.go index 3bd4189d53..3f4bc0c6cd 100644 --- a/test/e2e-ocl-2of2/onclusterlayering_test.go +++ b/test/e2e-ocl-2of2/onclusterlayering_test.go @@ -8,6 +8,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" "time" @@ -512,16 +513,12 @@ func TestImageBuildDegradedOnFailureAndClearedOnBuildStart(t *testing.T) { createMachineOSConfig(t, cs, mosc) - // Wait for the build to start and fail - firstMosb := waitForBuildToStartForPoolAndConfig(t, cs, layeredMCPName, mosc.Name) - t.Logf("Waiting for MachineOSBuild %s to fail", firstMosb.Name) - - // Force the build to fail faster by repeatedly deleting the build pods until - // the job reflects a failure status. - require.NoError(t, ocltesthelper.ForceMachineOSBuildToFail(ctx, t, cs, firstMosb)) + // The bad Containerfile fails validation before a build job is created, + // so the MOSB transitions directly to Failed without ever reaching Running. + firstMosb := waitForBuildToFailForPoolAndConfig(t, cs, ctx, layeredMCPName, mosc.Name) + t.Logf("MachineOSBuild %s failed as expected due to invalid Containerfile", firstMosb.Name) kubeassert := helpers.AssertClientSet(t, cs).WithContext(ctx) - kubeassert.Eventually().MachineOSBuildIsFailure(firstMosb) // Wait for and verify ImageBuildDegraded condition is set to True degradedCondition := waitForImageBuildDegradedCondition(ctx, t, cs, layeredMCPName, corev1.ConditionTrue) @@ -716,6 +713,81 @@ func TestImageBuildDegradedOnFailureAndClearedOnBuildStart(t *testing.T) { t.Logf("All MCP status transitions verified successfully across build failure, success, and subsequent new build") } +// TestPausedMCPSurfacesMachineOSBuildStatus verifies that a paused MachineConfigPool +// surfaces MachineOSBuild progress in its Updating condition and rolls out after unpause. +func TestPausedMCPSurfacesMachineOSBuildStatus(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + cs := framework.NewClientSet("") + + dockerfile, err := ocltesthelper.GetCowsayDockerfileForCluster(t, cs) + require.NoError(t, err) + + node := helpers.GetRandomNode(t, cs, "worker") + mosc := prepareForOnClusterLayeringTest(t, cs, onClusterLayeringTestOpts{ + poolName: layeredMCPName, + targetNode: &node, + customDockerfiles: map[string]string{ + layeredMCPName: dockerfile, + }, + }) + + setMachineConfigPoolPaused(ctx, t, cs, layeredMCPName, true) + t.Cleanup(func() { + setMachineConfigPoolPaused(ctx, t, cs, layeredMCPName, false) + }) + + createMachineOSConfig(t, cs, mosc) + + // waiting/prepared are short-lived transitional messages that the node + // controller may skip entirely if it doesn't reconcile during that exact + // window (e.g. if the MOSB is already Building by the first reconcile + // after pause). Only "in progress" is durable for the life of the build, + // so that's the one condition we require here. The others are logged as + // a bonus signal if we happen to observe them. + var sawWaiting, sawPrepared, sawInProgress bool + require.NoError(t, wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, 5*time.Minute, true, func(ctx context.Context) (bool, error) { + mcp, err := cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, layeredMCPName, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !mcp.Spec.Paused { + return false, fmt.Errorf("expected pool %s to remain paused", layeredMCPName) + } + // Check for starting conditions + cond := apihelpers.GetMachineConfigPoolCondition(mcp.Status, mcfgv1.MachineConfigPoolUpdating) + if cond != nil && cond.Status == corev1.ConditionTrue { + if strings.Contains(cond.Message, "waiting for a new OS image build to start") { + sawWaiting = true + assert.Contains(t, cond.Message, mosc.Name) + } + if strings.Contains(cond.Message, "prepared but will not rollout") { + sawPrepared = true + } + if strings.Contains(cond.Message, "OS image build in progress") { + sawInProgress = true + } + } + + return sawInProgress, nil + })) + t.Logf("Observed while paused: sawWaiting=%v sawPrepared=%v sawInProgress=%v", sawWaiting, sawPrepared, sawInProgress) + assert.True(t, sawInProgress, "MCP should surface build-in-progress while paused") + + mosb := waitForBuildToStartForPoolAndConfig(t, cs, layeredMCPName, mosc.Name) + + finishedBuild := waitForBuildToComplete(t, cs, mosb) + imagePullspec := string(finishedBuild.Status.DigestedImagePushSpec) + + updatingCond := waitForMCPUpdatingCondition(ctx, t, cs, layeredMCPName, corev1.ConditionFalse, "OS image build completed successfully") + assert.Contains(t, updatingCond.Message, mosb.Name) + + setMachineConfigPoolPaused(ctx, t, cs, layeredMCPName, false) + + require.NoError(t, helpers.WaitForNodeImageChange(t, cs, node, imagePullspec)) +} + // TestCurrentMachineOSBuildAnnotationHandling tests that the node controller correctly uses the // current-machine-os-build annotation on the MachineOSConfig to select the correct MOSB when // multiple MOSBs exist for the same rendered MachineConfig. This can happen during rapid updates @@ -1019,6 +1091,39 @@ func waitForBuildToStartForPoolAndConfig(t *testing.T, cs *framework.ClientSet, return waitForBuildToStart(t, cs, mosb) } +// Waits for a MachineOSBuild to exist and then transition directly to Failed +// (e.g. when Containerfile validation blocks the build before a job is created). +func waitForBuildToFailForPoolAndConfig(t *testing.T, cs *framework.ClientSet, ctx context.Context, poolName, moscName string) *mcfgv1.MachineOSBuild { + t.Helper() + + var mosbName string + + require.NoError(t, wait.PollImmediate(2*time.Second, 3*time.Minute, func() (bool, error) { + name, err := ocltesthelper.GetMachineOSBuildNameForPool(cs, poolName, moscName) + if err != nil { + return false, nil + } + mosbName = name + return true, nil + })) + + mosb := &mcfgv1.MachineOSBuild{ + ObjectMeta: metav1.ObjectMeta{ + Name: mosbName, + }, + } + + t.Logf("Waiting for MachineOSBuild %s to fail due to Containerfile validation", mosb.Name) + + kubeassert := helpers.AssertClientSet(t, cs).WithContext(ctx) + kubeassert.Eventually().MachineOSBuildExists(mosb) + kubeassert.Eventually().MachineOSBuildIsFailure(mosb) + + got, err := cs.MachineconfigurationV1Interface.MachineOSBuilds().Get(ctx, mosb.Name, metav1.GetOptions{}) + require.NoError(t, err) + return got +} + // Waits for a MachineOSBuild to start building. func waitForBuildToStart(t *testing.T, cs *framework.ClientSet, build *mcfgv1.MachineOSBuild) *mcfgv1.MachineOSBuild { t.Helper() @@ -1323,6 +1428,45 @@ func waitForJobToReachCondition(ctx context.Context, t *testing.T, cs *framework })) } +func setMachineConfigPoolPaused(ctx context.Context, t *testing.T, cs *framework.ClientSet, poolName string, paused bool) { + t.Helper() + + mcp, err := cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, poolName, metav1.GetOptions{}) + require.NoError(t, err) + + mcp.Spec.Paused = paused + _, err = cs.MachineconfigurationV1Interface.MachineConfigPools().Update(ctx, mcp, metav1.UpdateOptions{}) + require.NoError(t, err) + + t.Logf("MachineConfigPool %s paused=%v", poolName, paused) +} + +func waitForMCPUpdatingCondition(ctx context.Context, t *testing.T, cs *framework.ClientSet, poolName string, expectedStatus corev1.ConditionStatus, messageContains string) *mcfgv1.MachineConfigPoolCondition { + t.Helper() + + var condition *mcfgv1.MachineConfigPoolCondition + require.NoError(t, wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, 5*time.Minute, true, func(ctx context.Context) (bool, error) { + mcp, err := cs.MachineconfigurationV1Interface.MachineConfigPools().Get(ctx, poolName, metav1.GetOptions{}) + if err != nil { + return false, err + } + if !mcp.Spec.Paused { + return false, fmt.Errorf("expected pool %s to remain paused", poolName) + } + + condition = apihelpers.GetMachineConfigPoolCondition(mcp.Status, mcfgv1.MachineConfigPoolUpdating) + if condition == nil || condition.Status != expectedStatus { + return false, nil + } + if messageContains != "" && !strings.Contains(condition.Message, messageContains) { + return false, nil + } + return true, nil + })) + + return condition +} + // waitForImageBuildDegradedCondition waits for the ImageBuildDegraded condition to reach the expected state func waitForImageBuildDegradedCondition(ctx context.Context, t *testing.T, cs *framework.ClientSet, poolName string, expectedStatus corev1.ConditionStatus) *mcfgv1.MachineConfigPoolCondition { t.Helper() diff --git a/test/extended-priv/controlplanemachineset.go b/test/extended-priv/controlplanemachineset.go index f45361d7a5..0d02be870e 100644 --- a/test/extended-priv/controlplanemachineset.go +++ b/test/extended-priv/controlplanemachineset.go @@ -49,6 +49,23 @@ func NewControlPlaneMachineSetList(oc *exutil.CLI, namespace string) *ControlPla } // GetState returns the state of the ControlPlaneMachineSet (Active or Inactive) +// GetOSStreamLabel returns the value of the machineconfiguration.openshift.io/osstream label +func (cpms ControlPlaneMachineSet) GetOSStreamLabel() (string, error) { + return cpms.GetLabel("machineconfiguration.openshift.io/osstream") +} + +// GetOSStream returns the OS stream used by this ControlPlaneMachineSet. +// If the osstream label is set, it returns its value. Otherwise it defaults to rhel-9. +// The logic should be more complex. +// If the label is not set, then we should return the osstream used by the master pool +func (cpms ControlPlaneMachineSet) GetOSStream() string { + stream, err := cpms.GetOSStreamLabel() + if err != nil || stream == "" { + return OSImageStreamRHEL9 + } + return stream +} + func (cpms ControlPlaneMachineSet) GetState() (string, error) { return cpms.Get(`{.spec.state}`) } diff --git a/test/extended-priv/machineset.go b/test/extended-priv/machineset.go index 4d4093bdf4..08435ea0f5 100644 --- a/test/extended-priv/machineset.go +++ b/test/extended-priv/machineset.go @@ -67,6 +67,19 @@ func (ms MachineSet) GetReplicaOfSpec() string { return ms.GetOrFail(`{.spec.replicas}`) } +// GetOSStream returns the OS stream used by this MachineSet. +// If the osstream label is set, it returns its value. Otherwise it defaults to rhel-9. +// The logic should be more complex. To find the right osstream if the label does not exist we should: +// 1. Read the user-data secret and find out the pool configured for this machineset reading the ignition URL +// 2. Get the osstream used by this pool +func (ms MachineSet) GetOSStream() string { + stream, err := ms.GetOSStreamLabel() + if err != nil || stream == "" { + return OSImageStreamRHEL9 + } + return stream +} + // AddToScale scales the MachineSet adding the given value (positive or negative). func (ms MachineSet) AddToScale(delta int) error { currentReplicas, err := strconv.Atoi(ms.GetOrFail(`{.spec.replicas}`)) diff --git a/test/extended-priv/mco_bootimages.go b/test/extended-priv/mco_bootimages.go index fbc09deb5e..abe3f2e8b3 100644 --- a/test/extended-priv/mco_bootimages.go +++ b/test/extended-priv/mco_bootimages.go @@ -965,11 +965,11 @@ func getBackdatedBootImage(oc *exutil.CLI) string { rhcosHandler, err := GetRHCOSHandler(platform) o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the rhcos handler") - baseImage, err := rhcosHandler.GetBaseImageFromRHCOSImageInfo(imageVersion, arch, "") + baseImage, err := rhcosHandler.GetBaseImageFromRHCOSImageInfo(imageVersion, OSImageStreamRHEL9, arch, "") o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the base image") logger.Infof("Using base image %s", baseImage) - baseImageURL, err := rhcosHandler.GetBaseImageURLFromRHCOSImageInfo(imageVersion, arch) + baseImageURL, err := rhcosHandler.GetBaseImageURLFromRHCOSImageInfo(imageVersion, OSImageStreamRHEL9, arch) o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the base image URL") // To avoid collisions we will add prefix to identify our image diff --git a/test/extended-priv/mco_bootimages_aws_marketplace.go b/test/extended-priv/mco_bootimages_aws_marketplace.go index e87b72dc63..fd2deecbe3 100644 --- a/test/extended-priv/mco_bootimages_aws_marketplace.go +++ b/test/extended-priv/mco_bootimages_aws_marketplace.go @@ -59,7 +59,7 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/disruptive logger.Infof("OK!\n") }) - g.It("[OTP] MCO updates marketplace boot images to the correct product line", g.Label("Platform:aws"), func() { + g.It("[PolarionID:89828][OTP] MCO updates marketplace boot images to the correct product line", g.Label("Platform:aws"), func() { var ( testMS = NewMachineSetList(oc.AsAdmin(), MachineAPINamespace).GetAllOrFail()[0] region = getCurrentRegionOrFail(oc.AsAdmin()) diff --git a/test/extended-priv/mco_drain.go b/test/extended-priv/mco_drain.go index 2e4adca596..7a71686d67 100644 --- a/test/extended-priv/mco_drain.go +++ b/test/extended-priv/mco_drain.go @@ -281,8 +281,8 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati exutil.By("Set maxUnavailable value") maxUnavailable := 2 + defer mcp.SetSpec(mcp.GetSpecOrFail()) mcp.SetMaxUnavailable(maxUnavailable) - defer mcp.RemoveMaxUnavailable() exutil.By("Create a MC to deploy a config file") filePath := "/etc/TC-49672-mco-test-file-order" diff --git a/test/extended-priv/mco_machineconfigpool.go b/test/extended-priv/mco_machineconfigpool.go index 2a67c6fd34..85dd389e6d 100644 --- a/test/extended-priv/mco_machineconfigpool.go +++ b/test/extended-priv/mco_machineconfigpool.go @@ -339,8 +339,8 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati } exutil.By("Set the maxUnavailable value to 2") + defer mcp.SetSpec(mcp.GetSpecOrFail()) mcp.SetMaxUnavailable(2) - defer mcp.RemoveMaxUnavailable() logger.Infof("OK!\n") exutil.By("Manually cordon one of the nodes") diff --git a/test/extended-priv/mco_networkpolicies.go b/test/extended-priv/mco_networkpolicies.go new file mode 100644 index 0000000000..0876acfd4b --- /dev/null +++ b/test/extended-priv/mco_networkpolicies.go @@ -0,0 +1,119 @@ +package extended + +import ( + "fmt" + + g "github.com/onsi/ginkgo/v2" + o "github.com/onsi/gomega" + exutil "github.com/openshift/machine-config-operator/test/extended-priv/util" + logger "github.com/openshift/machine-config-operator/test/extended-priv/util/logext" +) + +var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longduration][Serial][Disruptive] MCO Network Policies", func() { + defer g.GinkgoRecover() + + var ( + oc = exutil.NewCLI("mco-network-policies", exutil.KubeConfigPath()) + ) + + g.JustBeforeEach(func() { + PreChecks(oc) + }) + + g.It("[PolarionID:89664] Verify MCO network policies enforce connectivity restrictions", func() { + testNum := GetCurrentTestPolarionIDNumber() + + testPodName := "nettest" + testPodImage := AlpineImage + testPodResource := NewNamespacedResource(oc.AsAdmin(), "pod", MachineConfigNamespace, testPodName) + + defer func() { + _ = testPodResource.Delete("--ignore-not-found=true") + }() + + exutil.By("Create test pod with HTTP listener for network policy verification") + err := oc.AsAdmin().WithoutNamespace().Run("run").Args( + testPodName, + fmt.Sprintf("--image=%s", testPodImage), + "-n", MachineConfigNamespace, + "--restart=Never", + "--command", "--", "sh", "-c", + "while true; do echo -e 'HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nok' | nc -l -p 8080; done", + ).Execute() + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to create test pod") + + exutil.AssertPodToBeReady(oc, testPodName, MachineConfigNamespace) + logger.Infof("OK!\n") + + exutil.By("Verify test pod listener is reachable from within the pod (positive control)") + output, err := exutil.RemoteShPod(oc.AsAdmin(), MachineConfigNamespace, testPodName, "wget", "-q", "-O-", "--timeout=5", "http://127.0.0.1:8080") + o.Expect(err).NotTo(o.HaveOccurred(), "Test pod HTTP listener is not working") + o.Expect(output).To(o.Equal("ok"), "Unexpected response from test pod listener") + logger.Infof("OK!\n") + + if !IsDisconnectedCluster(oc.AsAdmin()) { + exutil.By("Verify test pod can reach external internet (egress allowed)") + output, err = exutil.RemoteShPod(oc.AsAdmin(), MachineConfigNamespace, testPodName, "wget", "-q", "-O-", "--timeout=5", "https://google.com") + o.Expect(err).NotTo(o.HaveOccurred(), "Test pod cannot reach external internet") + o.Expect(output).NotTo(o.BeEmpty(), "Expected response from google.com") + logger.Infof("OK!\n") + } else { + logger.Infof("Skipping external internet connectivity test in disconnected cluster\n") + } + + exutil.By("Verify test pod can reach Kubernetes API server (egress allowed)") + output, err = exutil.RemoteShPod(oc.AsAdmin(), MachineConfigNamespace, testPodName, "wget", "-q", "-O-", "--timeout=5", "--no-check-certificate", "https://kubernetes.default.svc:443/healthz") + o.Expect(err).NotTo(o.HaveOccurred(), "Test pod cannot reach Kubernetes API") + o.Expect(output).To(o.Equal("ok"), "Unexpected response from Kubernetes API") + logger.Infof("OK!\n") + + exutil.By("Verify MCC pod cannot reach test pod (ingress blocked by default-deny)") + mccPodName, err := getMachineConfigControllerPod(oc.AsAdmin()) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get MCC pod") + o.Expect(mccPodName).NotTo(o.BeEmpty(), "MCC pod not found") + + testPodIP := testPodResource.GetOrFail("{.status.podIP}") + + // Listener is proven reachable (positive control above), so any failure here proves the NetworkPolicy blocks ingress + _, err = exutil.RemoteShContainer(oc.AsAdmin(), MachineConfigNamespace, mccPodName, "machine-config-controller", "curl", "-sk", "--connect-timeout", "5", fmt.Sprintf("http://%s:8080", testPodIP)) + o.Expect(err).To(o.HaveOccurred(), "MCC pod should not be able to reach test pod due to default-deny") + logger.Infof("OK!\n") + + exutil.By("Verify only port 9001 is allowed on MCC pod (other ports blocked)") + mccPod := NewNamespacedResource(oc.AsAdmin(), "pod", MachineConfigNamespace, mccPodName) + mccPodIP := mccPod.GetOrFail("{.status.podIP}") + + // Port 9001 is explicitly allowed by network policy — expect 401 Unauthorized proving connection succeeded + _, err = exutil.RemoteShPod(oc.AsAdmin(), MachineConfigNamespace, testPodName, "wget", "-q", "-O-", "--timeout=5", "--no-check-certificate", fmt.Sprintf("https://%s:9001/metrics", mccPodIP)) + o.Expect(err).To(o.HaveOccurred(), "Expected HTTP error on port 9001 (authentication required)") + o.Expect(err).To(o.BeAssignableToTypeOf(&exutil.ExitError{}), "Expected ExitError with stderr") + o.Expect(err.(*exutil.ExitError).StdErr).To(o.ContainSubstring("Unauthorized"), "Expected 401 Unauthorized response on port 9001 (proves connection succeeded)") + logger.Infof("OK!\n") + + // Port 8443 is not allowed by network policy — expect timeout + _, err = exutil.RemoteShPod(oc.AsAdmin(), MachineConfigNamespace, testPodName, "wget", "-q", "-O-", "--timeout=5", "--no-check-certificate", fmt.Sprintf("https://%s:8443/metrics", mccPodIP)) + o.Expect(err).To(o.HaveOccurred(), "Port 8443 should be blocked by network policy") + o.Expect(err).To(o.BeAssignableToTypeOf(&exutil.ExitError{}), "Expected ExitError with stderr") + o.Expect(err.(*exutil.ExitError).StdErr).To(o.ContainSubstring("download timed out"), "Expected timeout on port 8443 — NetworkPolicy must drop the traffic") + logger.Infof("OK!\n") + + // Port 443 is not allowed by network policy — expect timeout + _, err = exutil.RemoteShPod(oc.AsAdmin(), MachineConfigNamespace, testPodName, "wget", "-q", "-O-", "--timeout=5", "--no-check-certificate", fmt.Sprintf("https://%s:443", mccPodIP)) + o.Expect(err).To(o.HaveOccurred(), "Port 443 should be blocked by network policy") + o.Expect(err).To(o.BeAssignableToTypeOf(&exutil.ExitError{}), "Expected ExitError with stderr") + o.Expect(err.(*exutil.ExitError).StdErr).To(o.ContainSubstring("download timed out"), "Expected timeout on port 443 — NetworkPolicy must drop the traffic") + logger.Infof("OK!\n") + + exutil.By("Verify MCO pod can reach Kubernetes API (egress allowed)") + mcoPodName, err := getMachineConfigOperatorPod(oc.AsAdmin()) + o.Expect(err).NotTo(o.HaveOccurred(), "Failed to get MCO pod") + o.Expect(mcoPodName).NotTo(o.BeEmpty(), "MCO pod not found") + + output, err = exutil.RemoteShContainer(oc.AsAdmin(), MachineConfigNamespace, mcoPodName, "machine-config-operator", "curl", "-sk", "--connect-timeout", "5", "https://kubernetes.default.svc:443/healthz") + o.Expect(err).NotTo(o.HaveOccurred(), "MCO pod cannot reach Kubernetes API") + o.Expect(output).To(o.Equal("ok"), "Unexpected response from Kubernetes API") + logger.Infof("OK!\n") + + logger.Infof("Test %s completed successfully!\n", testNum) + }) +}) diff --git a/test/extended-priv/mco_password.go b/test/extended-priv/mco_password.go index cd63030145..1887b9d886 100644 --- a/test/extended-priv/mco_password.go +++ b/test/extended-priv/mco_password.go @@ -422,6 +422,108 @@ var _ = g.Describe("[sig-mco][Suite:openshift/machine-config-operator/longdurati checkAuthorizedKeyInNode(mcp.GetCoreOsNodesOrFail()[0], append(initialKeys, key)) logger.Infof("OK!\n") }) + + // https://issues.redhat.com/browse/OCPBUGS-83830 + g.It("[PolarionID:88940][OTP] Apply password only if changes exist [Disruptive]", func() { + var ( + mcName = "tc-88940-core-password-hash-change" + silentOC = oc.AsAdmin() + + password = exutil.GetRandomString() + passwordHash = OrFail[string](getHashPasswd(password)) + updatedPassword = password + "-2" + updatedPasswdHash = OrFail[string](getHashPasswd(updatedPassword)) + + _, sshPubKey = GenerateSSHKeyPairOrFail() + + pwdConfiguredLogMsg = "Password has been configured" + expectedReboot = false + ) + silentOC.NotShowInfo() + sshPubKey = strings.TrimSpace(sshPubKey) + + node := mcp.GetSortedNodesOrFail()[0] + mcc := NewController(oc.AsAdmin()).IgnoreLogsBeforeNowOrFail() + startTime := node.GetDateOrFail() + o.Expect(node.IgnoreEventsBeforeNow()).NotTo(o.HaveOccurred(), + "Error getting the latest event in node %s", node.GetName()) + + exutil.By("Apply MC with passwordHash for core user") + pwdUser := ign32PaswdUser{Name: user, PasswordHash: passwordHash} + mc := NewMachineConfig(silentOC, mcName, mcp.GetName()) + mc.parameters = []string{fmt.Sprintf(`PWDUSERS=[%s]`, MarshalOrFail(pwdUser))} + mc.skipWaitForMcp = true + + defer mc.DeleteWithWait() + mc.create() + mcp.waitForComplete() + logger.Infof("OK!\n") + + exutil.By("Check MCD logs to verify 'Password has been configured' is logged") + mcdLogs, err := node.GetMCDaemonLogs("") + o.Expect(err).NotTo(o.HaveOccurred(), "Error getting MCD logs for node %s", node.GetName()) + initialPasswordCount := strings.Count(mcdLogs, pwdConfiguredLogMsg) + o.Expect(initialPasswordCount).To(o.BeNumerically(">", 0), + "Expected '%s' in MCD logs after initial password application on node %s", pwdConfiguredLogMsg, node.GetName()) + logger.Infof("OK!\n") + + exutil.By("Check that drain and reboot are skipped for password-only change") + checkDrainAction(expectedReboot, node, mcc) + checkRebootAction(expectedReboot, node, startTime) + logger.Infof("OK!\n") + + exutil.By("Update the passwordHash in MC") + patchErr := mc.Patch("json", + fmt.Sprintf(`[{ "op": "replace", "path": "/spec/config/passwd/users/0/passwordHash", "value": "%s"}]`, updatedPasswdHash)) + o.Expect(patchErr).NotTo(o.HaveOccurred(), + "Error patching mc %s to update the 'core' user password", mc.GetName()) + mcp.waitForComplete() + logger.Infof("OK!\n") + + exutil.By("Check MCD logs to verify 'Password has been configured' is logged again after hash change") + mcdLogs, err = node.GetMCDaemonLogs("") + o.Expect(err).NotTo(o.HaveOccurred(), "Error getting MCD logs for node %s", node.GetName()) + updatedPasswordCount := strings.Count(mcdLogs, pwdConfiguredLogMsg) + o.Expect(updatedPasswordCount).To(o.BeNumerically(">", initialPasswordCount), + "Expected a new '%s' log entry after password hash change on node %s", pwdConfiguredLogMsg, node.GetName()) + logger.Infof("OK!\n") + + exutil.By("Get currently configured authorized keys before adding new SSH key") + currentMc, err := mcp.GetConfiguredMachineConfig() + o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the current configuration for %s pool", mcp.GetName()) + initialKeys, err := currentMc.GetAuthorizedKeysByUserAsList("core") + o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the current authorizedkeys for user 'core' in %s pool", mcp.GetName()) + logger.Infof("OK!\n") + + exutil.By("Add SSH key to MC without changing the password hash") + pwdUserWithSSH := ign32PaswdUser{Name: user, PasswordHash: updatedPasswdHash, SSHAuthorizedKeys: []string{sshPubKey}} + patchErr = mc.Patch("json", + fmt.Sprintf(`[{ "op": "replace", "path": "/spec/config/passwd/users", "value": [%s]}]`, MarshalOrFail(pwdUserWithSSH))) + o.Expect(patchErr).NotTo(o.HaveOccurred(), + "Error patching mc %s to add SSH key", mc.GetName()) + mcp.waitForComplete() + logger.Infof("OK!\n") + + exutil.By("Verify that the SSH key was correctly applied to the node") + checkAuthorizedKeyInNode(node, append(initialKeys, sshPubKey)) + logger.Infof("OK!\n") + + exutil.By("Check MCD logs to verify 'Password has been configured' is NOT logged for SSH-only change") + mcdLogs, err = node.GetMCDaemonLogs("") + o.Expect(err).NotTo(o.HaveOccurred(), "Error getting MCD logs for node %s", node.GetName()) + finalPasswordCount := strings.Count(mcdLogs, pwdConfiguredLogMsg) + o.Expect(finalPasswordCount).To(o.Equal(updatedPasswordCount), + "'%s' should NOT appear in MCD logs when only SSH keys are added (no password hash change) on node %s", + pwdConfiguredLogMsg, node.GetName()) + logger.Infof("OK!\n") + + exutil.By("Check events to make sure that drain and reboot events were not triggered") + nodeEvents, eErr := node.GetEvents() + o.Expect(eErr).ShouldNot(o.HaveOccurred(), "Error getting events for node %s", node.GetName()) + o.Expect(nodeEvents).NotTo(HaveEventsSequence("Drain"), "Error, a Drain event was triggered but it shouldn't") + o.Expect(nodeEvents).NotTo(HaveEventsSequence("Reboot"), "Error, a Reboot event was triggered but it shouldn't") + logger.Infof("OK!\n") + }) }) // getPasswdValidator returns the commands that need to be executed in an interactive expect shell to validate that a user can login diff --git a/test/extended-priv/mco_scale.go b/test/extended-priv/mco_scale.go index 412b50236a..51e895e400 100644 --- a/test/extended-priv/mco_scale.go +++ b/test/extended-priv/mco_scale.go @@ -335,11 +335,14 @@ func cloneMachineSet(oc *exutil.CLI, ms *MachineSet, newMsName, imageVersion, ig architecture, err := ms.GetArchitecture() o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the arechitecture from machineset %s", ms.GetName()) - baseImage, err := rhcosHandler.GetBaseImageFromRHCOSImageInfo(imageVersion, architecture, getCurrentRegionOrFail(oc.AsAdmin())) + stream := ms.GetOSStream() + logger.Infof("MachineSet %s is using OS stream %s", ms.GetName(), stream) + + baseImage, err := rhcosHandler.GetBaseImageFromRHCOSImageInfo(imageVersion, stream, architecture, getCurrentRegionOrFail(oc.AsAdmin())) o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the base image") logger.Infof("Using base image %s", baseImage) - baseImageURL, err := rhcosHandler.GetBaseImageURLFromRHCOSImageInfo(imageVersion, architecture) + baseImageURL, err := rhcosHandler.GetBaseImageURLFromRHCOSImageInfo(imageVersion, stream, architecture) o.Expect(err).NotTo(o.HaveOccurred(), "Error getting the base image URL") // In vshpere we will upload the image. To avoid collisions we will add prefix to identify our image @@ -397,18 +400,21 @@ func removeClonedMachineSet(ms *MachineSet, mcp *MachineConfigPool, expectedNumW } } -func getRHCOSImagesInfo(version string) (string, error) { +func getRHCOSImagesInfo(version, stream string) (string, error) { var ( err error resp *http.Response numRetries = 3 retryDelay = time.Minute - rhcosURL = fmt.Sprintf("https://raw.githubusercontent.com/openshift/installer/release-%s/data/data/rhcos.json", version) + rhcosURL = fmt.Sprintf("https://raw.githubusercontent.com/openshift/installer/release-%s/data/data/coreos/coreos-%s.json", version, stream) ) - if CompareVersions(version, ">=", "4.10") { + if CompareVersions(version, "<", "4.22") { rhcosURL = fmt.Sprintf("https://raw.githubusercontent.com/openshift/installer/release-%s/data/data/coreos/rhcos.json", version) } + if CompareVersions(version, "<", "4.10") { + rhcosURL = fmt.Sprintf("https://raw.githubusercontent.com/openshift/installer/release-%s/data/data/rhcos.json", version) + } // To mitigate network errors we will retry in case of failure logger.Infof("Getting rhcos image info from: %s", rhcosURL) @@ -529,20 +535,20 @@ func GetRHCOSHandler(platform string) (RHCOSHandler, error) { } type RHCOSHandler interface { - GetBaseImageFromRHCOSImageInfo(version string, arch architecture.Architecture, region string) (string, error) - GetBaseImageURLFromRHCOSImageInfo(version string, arch architecture.Architecture) (string, error) + GetBaseImageFromRHCOSImageInfo(version, stream string, arch architecture.Architecture, region string) (string, error) + GetBaseImageURLFromRHCOSImageInfo(version, stream string, arch architecture.Architecture) (string, error) } type AWSRHCOSHandler struct{} -func (aws AWSRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, arch architecture.Architecture, region string) (string, error) { +func (aws AWSRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version, stream string, arch architecture.Architecture, region string) (string, error) { var ( path string stringArch = arch.GNUString() platform = AWSPlatform ) - rhcosImageInfo, err := getRHCOSImagesInfo(version) + rhcosImageInfo, err := getRHCOSImagesInfo(version, stream) if err != nil { return "", err } @@ -567,13 +573,13 @@ func (aws AWSRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, arch a return baseImage.String(), nil } -func (aws AWSRHCOSHandler) GetBaseImageURLFromRHCOSImageInfo(version string, arch architecture.Architecture) (string, error) { - return getBaseImageURLFromRHCOSImageInfo(version, "aws", "vmdk.gz", arch.GNUString()) +func (aws AWSRHCOSHandler) GetBaseImageURLFromRHCOSImageInfo(version, stream string, arch architecture.Architecture) (string, error) { + return getBaseImageURLFromRHCOSImageInfo(version, stream, "aws", "vmdk.gz", arch.GNUString()) } type GCPRHCOSHandler struct{} -func (gcp GCPRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, arch architecture.Architecture, region string) (string, error) { +func (gcp GCPRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version, stream string, arch architecture.Architecture, region string) (string, error) { var ( imagePath string projectPath string @@ -585,7 +591,7 @@ func (gcp GCPRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, arch a return "", fmt.Errorf("there is no image base image supported for platform %s in version %s", platform, version) } - rhcosImageInfo, err := getRHCOSImagesInfo(version) + rhcosImageInfo, err := getRHCOSImagesInfo(version, stream) if err != nil { return "", err } @@ -617,14 +623,14 @@ func (gcp GCPRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, arch a return fmt.Sprintf("projects/%s/global/images/%s", project.String(), baseImage.String()), nil } -func (gcp GCPRHCOSHandler) GetBaseImageURLFromRHCOSImageInfo(version string, arch architecture.Architecture) (string, error) { - return getBaseImageURLFromRHCOSImageInfo(version, "gcp", "tar.gz", arch.GNUString()) +func (gcp GCPRHCOSHandler) GetBaseImageURLFromRHCOSImageInfo(version, stream string, arch architecture.Architecture) (string, error) { + return getBaseImageURLFromRHCOSImageInfo(version, stream, "gcp", "tar.gz", arch.GNUString()) } type VsphereRHCOSHandler struct{} -func (vsp VsphereRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, arch architecture.Architecture, _ string) (string, error) { - baseImageURL, err := vsp.GetBaseImageURLFromRHCOSImageInfo(version, arch) +func (vsp VsphereRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version, stream string, arch architecture.Architecture, _ string) (string, error) { + baseImageURL, err := vsp.GetBaseImageURLFromRHCOSImageInfo(version, stream, arch) if err != nil { return "", err } @@ -632,18 +638,18 @@ func (vsp VsphereRHCOSHandler) GetBaseImageFromRHCOSImageInfo(version string, ar return path.Base(baseImageURL), nil } -func (vsp VsphereRHCOSHandler) GetBaseImageURLFromRHCOSImageInfo(version string, arch architecture.Architecture) (string, error) { - return getBaseImageURLFromRHCOSImageInfo(version, "vmware", "ova", arch.GNUString()) +func (vsp VsphereRHCOSHandler) GetBaseImageURLFromRHCOSImageInfo(version, stream string, arch architecture.Architecture) (string, error) { + return getBaseImageURLFromRHCOSImageInfo(version, stream, "vmware", "ova", arch.GNUString()) } -func getBaseImageURLFromRHCOSImageInfo(version, platform, format, stringArch string) (string, error) { +func getBaseImageURLFromRHCOSImageInfo(version, stream, platform, format, stringArch string) (string, error) { var ( imagePath string baseURIPath string olderThan410 = CompareVersions(version, "<", "4.10") ) - rhcosImageInfo, err := getRHCOSImagesInfo(version) + rhcosImageInfo, err := getRHCOSImagesInfo(version, stream) if err != nil { return "", err } diff --git a/test/extended-priv/resource.go b/test/extended-priv/resource.go index f9c88b0b5c..79c1030c3b 100644 --- a/test/extended-priv/resource.go +++ b/test/extended-priv/resource.go @@ -620,3 +620,23 @@ func GetResourcesNamesSet(resources []ResourceInterface) sets.Set[string] { } return namesSet } + +// Delete removes all resources in the list from the cluster +func (l *ResourceList) Delete(extraParams ...string) error { + params := l.getCommonParams() + params = append(params, l.extraParams...) + params = append(params, extraParams...) + + _, err := l.oc.WithoutNamespace().Run("delete").Args(params...).Output() + if err != nil { + logger.Errorf("%v", err) + } + + return err +} + +// DeleteOrFail deletes all resources in the list, and if any error happens it fails the testcase +func (l *ResourceList) DeleteOrFail(extraParams ...string) { + err := l.Delete(extraParams...) + o.Expect(err).NotTo(o.HaveOccurred()) +} diff --git a/test/helpers/machineosbuildbuilder.go b/test/helpers/machineosbuildbuilder.go index 823c780d4c..aec3d22f28 100644 --- a/test/helpers/machineosbuildbuilder.go +++ b/test/helpers/machineosbuildbuilder.go @@ -98,6 +98,186 @@ func (m *MachineOSBuildBuilder) WithSuccessfulBuild() *MachineOSBuildBuilder { return m } +func (m *MachineOSBuildBuilder) WithBuildPrepared() *MachineOSBuildBuilder { + m.mosb.Status.Conditions = []metav1.Condition{ + { + Type: string(mcfgv1.MachineOSBuildPrepared), + Status: metav1.ConditionTrue, + Reason: "Prepared", + Message: "Build Prepared and Pending", + }, + { + Type: string(mcfgv1.MachineOSBuilding), + Status: metav1.ConditionFalse, + Reason: "Building", + Message: "Image Build In Progress", + }, + { + Type: string(mcfgv1.MachineOSBuildFailed), + Status: metav1.ConditionFalse, + Reason: "Failed", + Message: "Build Failed", + }, + { + Type: string(mcfgv1.MachineOSBuildInterrupted), + Status: metav1.ConditionFalse, + Reason: "Interrupted", + Message: "Build Interrupted", + }, + { + Type: string(mcfgv1.MachineOSBuildSucceeded), + Status: metav1.ConditionFalse, + Reason: "Ready", + Message: "Build Ready", + }, + } + return m +} + +func (m *MachineOSBuildBuilder) WithBuildInitialState() *MachineOSBuildBuilder { + m.mosb.Status.Conditions = []metav1.Condition{ + { + Type: string(mcfgv1.MachineOSBuildPrepared), + Status: metav1.ConditionFalse, + Reason: "Prepared", + Message: "Build Prepared and Pending", + }, + { + Type: string(mcfgv1.MachineOSBuilding), + Status: metav1.ConditionFalse, + Reason: "Building", + Message: "Image Build In Progress", + }, + { + Type: string(mcfgv1.MachineOSBuildFailed), + Status: metav1.ConditionFalse, + Reason: "Failed", + Message: "Build Failed", + }, + { + Type: string(mcfgv1.MachineOSBuildInterrupted), + Status: metav1.ConditionFalse, + Reason: "Interrupted", + Message: "Build Interrupted", + }, + { + Type: string(mcfgv1.MachineOSBuildSucceeded), + Status: metav1.ConditionFalse, + Reason: "Ready", + Message: "Build Ready", + }, + } + return m +} + +func (m *MachineOSBuildBuilder) WithBuildInProgress() *MachineOSBuildBuilder { + m.mosb.Status.Conditions = []metav1.Condition{ + { + Type: string(mcfgv1.MachineOSBuildPrepared), + Status: metav1.ConditionFalse, + Reason: "Prepared", + Message: "Build Prepared and Pending", + }, + { + Type: string(mcfgv1.MachineOSBuilding), + Status: metav1.ConditionTrue, + Reason: "Building", + Message: "Image Build In Progress", + }, + { + Type: string(mcfgv1.MachineOSBuildFailed), + Status: metav1.ConditionFalse, + Reason: "Failed", + Message: "Build Failed", + }, + { + Type: string(mcfgv1.MachineOSBuildInterrupted), + Status: metav1.ConditionFalse, + Reason: "Interrupted", + Message: "Build Interrupted", + }, + { + Type: string(mcfgv1.MachineOSBuildSucceeded), + Status: metav1.ConditionFalse, + Reason: "Ready", + Message: "Build Ready", + }, + } + return m +} + +func (m *MachineOSBuildBuilder) WithFailedBuild() *MachineOSBuildBuilder { + m.mosb.Status.Conditions = []metav1.Condition{ + { + Type: string(mcfgv1.MachineOSBuildPrepared), + Status: metav1.ConditionFalse, + Reason: "Prepared", + Message: "Build Prepared and Pending", + }, + { + Type: string(mcfgv1.MachineOSBuilding), + Status: metav1.ConditionFalse, + Reason: "Building", + Message: "Image Build In Progress", + }, + { + Type: string(mcfgv1.MachineOSBuildFailed), + Status: metav1.ConditionTrue, + Reason: "Failed", + Message: "Build Failed", + }, + { + Type: string(mcfgv1.MachineOSBuildInterrupted), + Status: metav1.ConditionFalse, + Reason: "Interrupted", + Message: "Build Interrupted", + }, + { + Type: string(mcfgv1.MachineOSBuildSucceeded), + Status: metav1.ConditionFalse, + Reason: "Ready", + Message: "Build Ready", + }, + } + return m +} + +func (m *MachineOSBuildBuilder) WithInterruptedBuild() *MachineOSBuildBuilder { + m.mosb.Status.Conditions = []metav1.Condition{ + { + Type: string(mcfgv1.MachineOSBuildPrepared), + Status: metav1.ConditionFalse, + Reason: "Prepared", + Message: "Build Prepared and Pending", + }, + { + Type: string(mcfgv1.MachineOSBuilding), + Status: metav1.ConditionFalse, + Reason: "Building", + Message: "Image Build In Progress", + }, + { + Type: string(mcfgv1.MachineOSBuildFailed), + Status: metav1.ConditionFalse, + Reason: "Failed", + Message: "Build Failed", + }, + { + Type: string(mcfgv1.MachineOSBuildInterrupted), + Status: metav1.ConditionTrue, + Reason: "Interrupted", + Message: "Build Interrupted", + }, + { + Type: string(mcfgv1.MachineOSBuildSucceeded), + Status: metav1.ConditionFalse, + Reason: "Ready", + Message: "Build Ready", + }, + } + return m +} + func (m *MachineOSBuildBuilder) WithAnnotations(annos map[string]string) *MachineOSBuildBuilder { for k, v := range annos { m.mosb.Annotations[k] = v diff --git a/vendor/github.com/asaskevich/govalidator/.gitignore b/vendor/github.com/asaskevich/govalidator/.gitignore deleted file mode 100644 index 8d69a9418a..0000000000 --- a/vendor/github.com/asaskevich/govalidator/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -bin/ -.idea/ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - diff --git a/vendor/github.com/asaskevich/govalidator/.travis.yml b/vendor/github.com/asaskevich/govalidator/.travis.yml deleted file mode 100644 index bb83c6670d..0000000000 --- a/vendor/github.com/asaskevich/govalidator/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: go -dist: xenial -go: - - '1.10' - - '1.11' - - '1.12' - - '1.13' - - 'tip' - -script: - - go test -coverpkg=./... -coverprofile=coverage.info -timeout=5s - - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/asaskevich/govalidator/CODE_OF_CONDUCT.md b/vendor/github.com/asaskevich/govalidator/CODE_OF_CONDUCT.md deleted file mode 100644 index 4b462b0d81..0000000000 --- a/vendor/github.com/asaskevich/govalidator/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,43 +0,0 @@ -# Contributor Code of Conduct - -This project adheres to [The Code Manifesto](http://codemanifesto.com) -as its guidelines for contributor interactions. - -## The Code Manifesto - -We want to work in an ecosystem that empowers developers to reach their -potential — one that encourages growth and effective collaboration. A space -that is safe for all. - -A space such as this benefits everyone that participates in it. It encourages -new developers to enter our field. It is through discussion and collaboration -that we grow, and through growth that we improve. - -In the effort to create such a place, we hold to these values: - -1. **Discrimination limits us.** This includes discrimination on the basis of - race, gender, sexual orientation, gender identity, age, nationality, - technology and any other arbitrary exclusion of a group of people. -2. **Boundaries honor us.** Your comfort levels are not everyone’s comfort - levels. Remember that, and if brought to your attention, heed it. -3. **We are our biggest assets.** None of us were born masters of our trade. - Each of us has been helped along the way. Return that favor, when and where - you can. -4. **We are resources for the future.** As an extension of #3, share what you - know. Make yourself a resource to help those that come after you. -5. **Respect defines us.** Treat others as you wish to be treated. Make your - discussions, criticisms and debates from a position of respectfulness. Ask - yourself, is it true? Is it necessary? Is it constructive? Anything less is - unacceptable. -6. **Reactions require grace.** Angry responses are valid, but abusive language - and vindictive actions are toxic. When something happens that offends you, - handle it assertively, but be respectful. Escalate reasonably, and try to - allow the offender an opportunity to explain themselves, and possibly - correct the issue. -7. **Opinions are just that: opinions.** Each and every one of us, due to our - background and upbringing, have varying opinions. That is perfectly - acceptable. Remember this: if you respect your own opinions, you should - respect the opinions of others. -8. **To err is human.** You might not intend it, but mistakes do happen and - contribute to build experience. Tolerate honest mistakes, and don't - hesitate to apologize if you make one yourself. diff --git a/vendor/github.com/asaskevich/govalidator/CONTRIBUTING.md b/vendor/github.com/asaskevich/govalidator/CONTRIBUTING.md deleted file mode 100644 index 7ed268a1ed..0000000000 --- a/vendor/github.com/asaskevich/govalidator/CONTRIBUTING.md +++ /dev/null @@ -1,63 +0,0 @@ -#### Support -If you do have a contribution to the package, feel free to create a Pull Request or an Issue. - -#### What to contribute -If you don't know what to do, there are some features and functions that need to be done - -- [ ] Refactor code -- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check -- [ ] Create actual list of contributors and projects that currently using this package -- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues) -- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions) -- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new -- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc -- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) -- [ ] Implement fuzzing testing -- [ ] Implement some struct/map/array utilities -- [ ] Implement map/array validation -- [ ] Implement benchmarking -- [ ] Implement batch of examples -- [ ] Look at forks for new features and fixes - -#### Advice -Feel free to create what you want, but keep in mind when you implement new features: -- Code must be clear and readable, names of variables/constants clearly describes what they are doing -- Public functions must be documented and described in source file and added to README.md to the list of available functions -- There are must be unit-tests for any new functions and improvements - -## Financial contributions - -We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/govalidator). -Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed. - - -## Credits - - -### Contributors - -Thank you to all the people who have already contributed to govalidator! - - - -### Backers - -Thank you to all our backers! [[Become a backer](https://opencollective.com/govalidator#backer)] - - - - -### Sponsors - -Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/govalidator#sponsor)) - - - - - - - - - - - \ No newline at end of file diff --git a/vendor/github.com/asaskevich/govalidator/LICENSE b/vendor/github.com/asaskevich/govalidator/LICENSE deleted file mode 100644 index cacba91024..0000000000 --- a/vendor/github.com/asaskevich/govalidator/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2014-2020 Alex Saskevich - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/asaskevich/govalidator/README.md b/vendor/github.com/asaskevich/govalidator/README.md deleted file mode 100644 index 2c3fc35eb6..0000000000 --- a/vendor/github.com/asaskevich/govalidator/README.md +++ /dev/null @@ -1,622 +0,0 @@ -govalidator -=========== -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/asaskevich/govalidator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![GoDoc](https://godoc.org/github.com/asaskevich/govalidator?status.png)](https://godoc.org/github.com/asaskevich/govalidator) -[![Build Status](https://travis-ci.org/asaskevich/govalidator.svg?branch=master)](https://travis-ci.org/asaskevich/govalidator) -[![Coverage](https://codecov.io/gh/asaskevich/govalidator/branch/master/graph/badge.svg)](https://codecov.io/gh/asaskevich/govalidator) [![Go Report Card](https://goreportcard.com/badge/github.com/asaskevich/govalidator)](https://goreportcard.com/report/github.com/asaskevich/govalidator) [![GoSearch](http://go-search.org/badge?id=github.com%2Fasaskevich%2Fgovalidator)](http://go-search.org/view?id=github.com%2Fasaskevich%2Fgovalidator) [![Backers on Open Collective](https://opencollective.com/govalidator/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/govalidator/sponsors/badge.svg)](#sponsors) [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_shield) - -A package of validators and sanitizers for strings, structs and collections. Based on [validator.js](https://github.com/chriso/validator.js). - -#### Installation -Make sure that Go is installed on your computer. -Type the following command in your terminal: - - go get github.com/asaskevich/govalidator - -or you can get specified release of the package with `gopkg.in`: - - go get gopkg.in/asaskevich/govalidator.v10 - -After it the package is ready to use. - - -#### Import package in your project -Add following line in your `*.go` file: -```go -import "github.com/asaskevich/govalidator" -``` -If you are unhappy to use long `govalidator`, you can do something like this: -```go -import ( - valid "github.com/asaskevich/govalidator" -) -``` - -#### Activate behavior to require all fields have a validation tag by default -`SetFieldsRequiredByDefault` causes validation to fail when struct fields do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). A good place to activate this is a package init function or the main() function. - -`SetNilPtrAllowedByRequired` causes validation to pass when struct fields marked by `required` are set to nil. This is disabled by default for consistency, but some packages that need to be able to determine between `nil` and `zero value` state can use this. If disabled, both `nil` and `zero` values cause validation errors. - -```go -import "github.com/asaskevich/govalidator" - -func init() { - govalidator.SetFieldsRequiredByDefault(true) -} -``` - -Here's some code to explain it: -```go -// this struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): -type exampleStruct struct { - Name string `` - Email string `valid:"email"` -} - -// this, however, will only fail when Email is empty or an invalid email address: -type exampleStruct2 struct { - Name string `valid:"-"` - Email string `valid:"email"` -} - -// lastly, this will only fail when Email is an invalid email address but not when it's empty: -type exampleStruct2 struct { - Name string `valid:"-"` - Email string `valid:"email,optional"` -} -``` - -#### Recent breaking changes (see [#123](https://github.com/asaskevich/govalidator/pull/123)) -##### Custom validator function signature -A context was added as the second parameter, for structs this is the object being validated – this makes dependent validation possible. -```go -import "github.com/asaskevich/govalidator" - -// old signature -func(i interface{}) bool - -// new signature -func(i interface{}, o interface{}) bool -``` - -##### Adding a custom validator -This was changed to prevent data races when accessing custom validators. -```go -import "github.com/asaskevich/govalidator" - -// before -govalidator.CustomTypeTagMap["customByteArrayValidator"] = func(i interface{}, o interface{}) bool { - // ... -} - -// after -govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, o interface{}) bool { - // ... -}) -``` - -#### List of functions: -```go -func Abs(value float64) float64 -func BlackList(str, chars string) string -func ByteLength(str string, params ...string) bool -func CamelCaseToUnderscore(str string) string -func Contains(str, substring string) bool -func Count(array []interface{}, iterator ConditionIterator) int -func Each(array []interface{}, iterator Iterator) -func ErrorByField(e error, field string) string -func ErrorsByField(e error) map[string]string -func Filter(array []interface{}, iterator ConditionIterator) []interface{} -func Find(array []interface{}, iterator ConditionIterator) interface{} -func GetLine(s string, index int) (string, error) -func GetLines(s string) []string -func HasLowerCase(str string) bool -func HasUpperCase(str string) bool -func HasWhitespace(str string) bool -func HasWhitespaceOnly(str string) bool -func InRange(value interface{}, left interface{}, right interface{}) bool -func InRangeFloat32(value, left, right float32) bool -func InRangeFloat64(value, left, right float64) bool -func InRangeInt(value, left, right interface{}) bool -func IsASCII(str string) bool -func IsAlpha(str string) bool -func IsAlphanumeric(str string) bool -func IsBase64(str string) bool -func IsByteLength(str string, min, max int) bool -func IsCIDR(str string) bool -func IsCRC32(str string) bool -func IsCRC32b(str string) bool -func IsCreditCard(str string) bool -func IsDNSName(str string) bool -func IsDataURI(str string) bool -func IsDialString(str string) bool -func IsDivisibleBy(str, num string) bool -func IsEmail(str string) bool -func IsExistingEmail(email string) bool -func IsFilePath(str string) (bool, int) -func IsFloat(str string) bool -func IsFullWidth(str string) bool -func IsHalfWidth(str string) bool -func IsHash(str string, algorithm string) bool -func IsHexadecimal(str string) bool -func IsHexcolor(str string) bool -func IsHost(str string) bool -func IsIP(str string) bool -func IsIPv4(str string) bool -func IsIPv6(str string) bool -func IsISBN(str string, version int) bool -func IsISBN10(str string) bool -func IsISBN13(str string) bool -func IsISO3166Alpha2(str string) bool -func IsISO3166Alpha3(str string) bool -func IsISO4217(str string) bool -func IsISO693Alpha2(str string) bool -func IsISO693Alpha3b(str string) bool -func IsIn(str string, params ...string) bool -func IsInRaw(str string, params ...string) bool -func IsInt(str string) bool -func IsJSON(str string) bool -func IsLatitude(str string) bool -func IsLongitude(str string) bool -func IsLowerCase(str string) bool -func IsMAC(str string) bool -func IsMD4(str string) bool -func IsMD5(str string) bool -func IsMagnetURI(str string) bool -func IsMongoID(str string) bool -func IsMultibyte(str string) bool -func IsNatural(value float64) bool -func IsNegative(value float64) bool -func IsNonNegative(value float64) bool -func IsNonPositive(value float64) bool -func IsNotNull(str string) bool -func IsNull(str string) bool -func IsNumeric(str string) bool -func IsPort(str string) bool -func IsPositive(value float64) bool -func IsPrintableASCII(str string) bool -func IsRFC3339(str string) bool -func IsRFC3339WithoutZone(str string) bool -func IsRGBcolor(str string) bool -func IsRegex(str string) bool -func IsRequestURI(rawurl string) bool -func IsRequestURL(rawurl string) bool -func IsRipeMD128(str string) bool -func IsRipeMD160(str string) bool -func IsRsaPub(str string, params ...string) bool -func IsRsaPublicKey(str string, keylen int) bool -func IsSHA1(str string) bool -func IsSHA256(str string) bool -func IsSHA384(str string) bool -func IsSHA512(str string) bool -func IsSSN(str string) bool -func IsSemver(str string) bool -func IsTiger128(str string) bool -func IsTiger160(str string) bool -func IsTiger192(str string) bool -func IsTime(str string, format string) bool -func IsType(v interface{}, params ...string) bool -func IsURL(str string) bool -func IsUTFDigit(str string) bool -func IsUTFLetter(str string) bool -func IsUTFLetterNumeric(str string) bool -func IsUTFNumeric(str string) bool -func IsUUID(str string) bool -func IsUUIDv3(str string) bool -func IsUUIDv4(str string) bool -func IsUUIDv5(str string) bool -func IsULID(str string) bool -func IsUnixTime(str string) bool -func IsUpperCase(str string) bool -func IsVariableWidth(str string) bool -func IsWhole(value float64) bool -func LeftTrim(str, chars string) string -func Map(array []interface{}, iterator ResultIterator) []interface{} -func Matches(str, pattern string) bool -func MaxStringLength(str string, params ...string) bool -func MinStringLength(str string, params ...string) bool -func NormalizeEmail(str string) (string, error) -func PadBoth(str string, padStr string, padLen int) string -func PadLeft(str string, padStr string, padLen int) string -func PadRight(str string, padStr string, padLen int) string -func PrependPathToErrors(err error, path string) error -func Range(str string, params ...string) bool -func RemoveTags(s string) string -func ReplacePattern(str, pattern, replace string) string -func Reverse(s string) string -func RightTrim(str, chars string) string -func RuneLength(str string, params ...string) bool -func SafeFileName(str string) string -func SetFieldsRequiredByDefault(value bool) -func SetNilPtrAllowedByRequired(value bool) -func Sign(value float64) float64 -func StringLength(str string, params ...string) bool -func StringMatches(s string, params ...string) bool -func StripLow(str string, keepNewLines bool) string -func ToBoolean(str string) (bool, error) -func ToFloat(str string) (float64, error) -func ToInt(value interface{}) (res int64, err error) -func ToJSON(obj interface{}) (string, error) -func ToString(obj interface{}) string -func Trim(str, chars string) string -func Truncate(str string, length int, ending string) string -func TruncatingErrorf(str string, args ...interface{}) error -func UnderscoreToCamelCase(s string) string -func ValidateMap(inputMap map[string]interface{}, validationMap map[string]interface{}) (bool, error) -func ValidateStruct(s interface{}) (bool, error) -func WhiteList(str, chars string) string -type ConditionIterator -type CustomTypeValidator -type Error -func (e Error) Error() string -type Errors -func (es Errors) Error() string -func (es Errors) Errors() []error -type ISO3166Entry -type ISO693Entry -type InterfaceParamValidator -type Iterator -type ParamValidator -type ResultIterator -type UnsupportedTypeError -func (e *UnsupportedTypeError) Error() string -type Validator -``` - -#### Examples -###### IsURL -```go -println(govalidator.IsURL(`http://user@pass:domain.com/path/page`)) -``` -###### IsType -```go -println(govalidator.IsType("Bob", "string")) -println(govalidator.IsType(1, "int")) -i := 1 -println(govalidator.IsType(&i, "*int")) -``` - -IsType can be used through the tag `type` which is essential for map validation: -```go -type User struct { - Name string `valid:"type(string)"` - Age int `valid:"type(int)"` - Meta interface{} `valid:"type(string)"` -} -result, err := govalidator.ValidateStruct(User{"Bob", 20, "meta"}) -if err != nil { - println("error: " + err.Error()) -} -println(result) -``` -###### ToString -```go -type User struct { - FirstName string - LastName string -} - -str := govalidator.ToString(&User{"John", "Juan"}) -println(str) -``` -###### Each, Map, Filter, Count for slices -Each iterates over the slice/array and calls Iterator for every item -```go -data := []interface{}{1, 2, 3, 4, 5} -var fn govalidator.Iterator = func(value interface{}, index int) { - println(value.(int)) -} -govalidator.Each(data, fn) -``` -```go -data := []interface{}{1, 2, 3, 4, 5} -var fn govalidator.ResultIterator = func(value interface{}, index int) interface{} { - return value.(int) * 3 -} -_ = govalidator.Map(data, fn) // result = []interface{}{1, 6, 9, 12, 15} -``` -```go -data := []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} -var fn govalidator.ConditionIterator = func(value interface{}, index int) bool { - return value.(int)%2 == 0 -} -_ = govalidator.Filter(data, fn) // result = []interface{}{2, 4, 6, 8, 10} -_ = govalidator.Count(data, fn) // result = 5 -``` -###### ValidateStruct [#2](https://github.com/asaskevich/govalidator/pull/2) -If you want to validate structs, you can use tag `valid` for any field in your structure. All validators used with this field in one tag are separated by comma. If you want to skip validation, place `-` in your tag. If you need a validator that is not on the list below, you can add it like this: -```go -govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool { - return str == "duck" -}) -``` -For completely custom validators (interface-based), see below. - -Here is a list of available validators for struct fields (validator - used function): -```go -"email": IsEmail, -"url": IsURL, -"dialstring": IsDialString, -"requrl": IsRequestURL, -"requri": IsRequestURI, -"alpha": IsAlpha, -"utfletter": IsUTFLetter, -"alphanum": IsAlphanumeric, -"utfletternum": IsUTFLetterNumeric, -"numeric": IsNumeric, -"utfnumeric": IsUTFNumeric, -"utfdigit": IsUTFDigit, -"hexadecimal": IsHexadecimal, -"hexcolor": IsHexcolor, -"rgbcolor": IsRGBcolor, -"lowercase": IsLowerCase, -"uppercase": IsUpperCase, -"int": IsInt, -"float": IsFloat, -"null": IsNull, -"uuid": IsUUID, -"uuidv3": IsUUIDv3, -"uuidv4": IsUUIDv4, -"uuidv5": IsUUIDv5, -"creditcard": IsCreditCard, -"isbn10": IsISBN10, -"isbn13": IsISBN13, -"json": IsJSON, -"multibyte": IsMultibyte, -"ascii": IsASCII, -"printableascii": IsPrintableASCII, -"fullwidth": IsFullWidth, -"halfwidth": IsHalfWidth, -"variablewidth": IsVariableWidth, -"base64": IsBase64, -"datauri": IsDataURI, -"ip": IsIP, -"port": IsPort, -"ipv4": IsIPv4, -"ipv6": IsIPv6, -"dns": IsDNSName, -"host": IsHost, -"mac": IsMAC, -"latitude": IsLatitude, -"longitude": IsLongitude, -"ssn": IsSSN, -"semver": IsSemver, -"rfc3339": IsRFC3339, -"rfc3339WithoutZone": IsRFC3339WithoutZone, -"ISO3166Alpha2": IsISO3166Alpha2, -"ISO3166Alpha3": IsISO3166Alpha3, -"ulid": IsULID, -``` -Validators with parameters - -```go -"range(min|max)": Range, -"length(min|max)": ByteLength, -"runelength(min|max)": RuneLength, -"stringlength(min|max)": StringLength, -"matches(pattern)": StringMatches, -"in(string1|string2|...|stringN)": IsIn, -"rsapub(keylength)" : IsRsaPub, -"minstringlength(int): MinStringLength, -"maxstringlength(int): MaxStringLength, -``` -Validators with parameters for any type - -```go -"type(type)": IsType, -``` - -And here is small example of usage: -```go -type Post struct { - Title string `valid:"alphanum,required"` - Message string `valid:"duck,ascii"` - Message2 string `valid:"animal(dog)"` - AuthorIP string `valid:"ipv4"` - Date string `valid:"-"` -} -post := &Post{ - Title: "My Example Post", - Message: "duck", - Message2: "dog", - AuthorIP: "123.234.54.3", -} - -// Add your own struct validation tags -govalidator.TagMap["duck"] = govalidator.Validator(func(str string) bool { - return str == "duck" -}) - -// Add your own struct validation tags with parameter -govalidator.ParamTagMap["animal"] = govalidator.ParamValidator(func(str string, params ...string) bool { - species := params[0] - return str == species -}) -govalidator.ParamTagRegexMap["animal"] = regexp.MustCompile("^animal\\((\\w+)\\)$") - -result, err := govalidator.ValidateStruct(post) -if err != nil { - println("error: " + err.Error()) -} -println(result) -``` -###### ValidateMap [#2](https://github.com/asaskevich/govalidator/pull/338) -If you want to validate maps, you can use the map to be validated and a validation map that contain the same tags used in ValidateStruct, both maps have to be in the form `map[string]interface{}` - -So here is small example of usage: -```go -var mapTemplate = map[string]interface{}{ - "name":"required,alpha", - "family":"required,alpha", - "email":"required,email", - "cell-phone":"numeric", - "address":map[string]interface{}{ - "line1":"required,alphanum", - "line2":"alphanum", - "postal-code":"numeric", - }, -} - -var inputMap = map[string]interface{}{ - "name":"Bob", - "family":"Smith", - "email":"foo@bar.baz", - "address":map[string]interface{}{ - "line1":"", - "line2":"", - "postal-code":"", - }, -} - -result, err := govalidator.ValidateMap(inputMap, mapTemplate) -if err != nil { - println("error: " + err.Error()) -} -println(result) -``` - -###### WhiteList -```go -// Remove all characters from string ignoring characters between "a" and "z" -println(govalidator.WhiteList("a3a43a5a4a3a2a23a4a5a4a3a4", "a-z") == "aaaaaaaaaaaa") -``` - -###### Custom validation functions -Custom validation using your own domain specific validators is also available - here's an example of how to use it: -```go -import "github.com/asaskevich/govalidator" - -type CustomByteArray [6]byte // custom types are supported and can be validated - -type StructWithCustomByteArray struct { - ID CustomByteArray `valid:"customByteArrayValidator,customMinLengthValidator"` // multiple custom validators are possible as well and will be evaluated in sequence - Email string `valid:"email"` - CustomMinLength int `valid:"-"` -} - -govalidator.CustomTypeTagMap.Set("customByteArrayValidator", func(i interface{}, context interface{}) bool { - switch v := context.(type) { // you can type switch on the context interface being validated - case StructWithCustomByteArray: - // you can check and validate against some other field in the context, - // return early or not validate against the context at all – your choice - case SomeOtherType: - // ... - default: - // expecting some other type? Throw/panic here or continue - } - - switch v := i.(type) { // type switch on the struct field being validated - case CustomByteArray: - for _, e := range v { // this validator checks that the byte array is not empty, i.e. not all zeroes - if e != 0 { - return true - } - } - } - return false -}) -govalidator.CustomTypeTagMap.Set("customMinLengthValidator", func(i interface{}, context interface{}) bool { - switch v := context.(type) { // this validates a field against the value in another field, i.e. dependent validation - case StructWithCustomByteArray: - return len(v.ID) >= v.CustomMinLength - } - return false -}) -``` - -###### Loop over Error() -By default .Error() returns all errors in a single String. To access each error you can do this: -```go - if err != nil { - errs := err.(govalidator.Errors).Errors() - for _, e := range errs { - fmt.Println(e.Error()) - } - } -``` - -###### Custom error messages -Custom error messages are supported via annotations by adding the `~` separator - here's an example of how to use it: -```go -type Ticket struct { - Id int64 `json:"id"` - FirstName string `json:"firstname" valid:"required~First name is blank"` -} -``` - -#### Notes -Documentation is available here: [godoc.org](https://godoc.org/github.com/asaskevich/govalidator). -Full information about code coverage is also available here: [govalidator on gocover.io](http://gocover.io/github.com/asaskevich/govalidator). - -#### Support -If you do have a contribution to the package, feel free to create a Pull Request or an Issue. - -#### What to contribute -If you don't know what to do, there are some features and functions that need to be done - -- [ ] Refactor code -- [ ] Edit docs and [README](https://github.com/asaskevich/govalidator/README.md): spellcheck, grammar and typo check -- [ ] Create actual list of contributors and projects that currently using this package -- [ ] Resolve [issues and bugs](https://github.com/asaskevich/govalidator/issues) -- [ ] Update actual [list of functions](https://github.com/asaskevich/govalidator#list-of-functions) -- [ ] Update [list of validators](https://github.com/asaskevich/govalidator#validatestruct-2) that available for `ValidateStruct` and add new -- [ ] Implement new validators: `IsFQDN`, `IsIMEI`, `IsPostalCode`, `IsISIN`, `IsISRC` etc -- [x] Implement [validation by maps](https://github.com/asaskevich/govalidator/issues/224) -- [ ] Implement fuzzing testing -- [ ] Implement some struct/map/array utilities -- [ ] Implement map/array validation -- [ ] Implement benchmarking -- [ ] Implement batch of examples -- [ ] Look at forks for new features and fixes - -#### Advice -Feel free to create what you want, but keep in mind when you implement new features: -- Code must be clear and readable, names of variables/constants clearly describes what they are doing -- Public functions must be documented and described in source file and added to README.md to the list of available functions -- There are must be unit-tests for any new functions and improvements - -## Credits -### Contributors - -This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. - -#### Special thanks to [contributors](https://github.com/asaskevich/govalidator/graphs/contributors) -* [Daniel Lohse](https://github.com/annismckenzie) -* [Attila Oláh](https://github.com/attilaolah) -* [Daniel Korner](https://github.com/Dadie) -* [Steven Wilkin](https://github.com/stevenwilkin) -* [Deiwin Sarjas](https://github.com/deiwin) -* [Noah Shibley](https://github.com/slugmobile) -* [Nathan Davies](https://github.com/nathj07) -* [Matt Sanford](https://github.com/mzsanford) -* [Simon ccl1115](https://github.com/ccl1115) - - - - -### Backers - -Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/govalidator#backer)] - - - - -### Sponsors - -Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/govalidator#sponsor)] - - - - - - - - - - - - - - - -## License -[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fasaskevich%2Fgovalidator?ref=badge_large) diff --git a/vendor/github.com/asaskevich/govalidator/arrays.go b/vendor/github.com/asaskevich/govalidator/arrays.go deleted file mode 100644 index 3e1da7cb48..0000000000 --- a/vendor/github.com/asaskevich/govalidator/arrays.go +++ /dev/null @@ -1,87 +0,0 @@ -package govalidator - -// Iterator is the function that accepts element of slice/array and its index -type Iterator func(interface{}, int) - -// ResultIterator is the function that accepts element of slice/array and its index and returns any result -type ResultIterator func(interface{}, int) interface{} - -// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean -type ConditionIterator func(interface{}, int) bool - -// ReduceIterator is the function that accepts two element of slice/array and returns result of merging those values -type ReduceIterator func(interface{}, interface{}) interface{} - -// Some validates that any item of array corresponds to ConditionIterator. Returns boolean. -func Some(array []interface{}, iterator ConditionIterator) bool { - res := false - for index, data := range array { - res = res || iterator(data, index) - } - return res -} - -// Every validates that every item of array corresponds to ConditionIterator. Returns boolean. -func Every(array []interface{}, iterator ConditionIterator) bool { - res := true - for index, data := range array { - res = res && iterator(data, index) - } - return res -} - -// Reduce boils down a list of values into a single value by ReduceIterator -func Reduce(array []interface{}, iterator ReduceIterator, initialValue interface{}) interface{} { - for _, data := range array { - initialValue = iterator(initialValue, data) - } - return initialValue -} - -// Each iterates over the slice and apply Iterator to every item -func Each(array []interface{}, iterator Iterator) { - for index, data := range array { - iterator(data, index) - } -} - -// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result. -func Map(array []interface{}, iterator ResultIterator) []interface{} { - var result = make([]interface{}, len(array)) - for index, data := range array { - result[index] = iterator(data, index) - } - return result -} - -// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise. -func Find(array []interface{}, iterator ConditionIterator) interface{} { - for index, data := range array { - if iterator(data, index) { - return data - } - } - return nil -} - -// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice. -func Filter(array []interface{}, iterator ConditionIterator) []interface{} { - var result = make([]interface{}, 0) - for index, data := range array { - if iterator(data, index) { - result = append(result, data) - } - } - return result -} - -// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator. -func Count(array []interface{}, iterator ConditionIterator) int { - count := 0 - for index, data := range array { - if iterator(data, index) { - count = count + 1 - } - } - return count -} diff --git a/vendor/github.com/asaskevich/govalidator/converter.go b/vendor/github.com/asaskevich/govalidator/converter.go deleted file mode 100644 index d68e990fc2..0000000000 --- a/vendor/github.com/asaskevich/govalidator/converter.go +++ /dev/null @@ -1,81 +0,0 @@ -package govalidator - -import ( - "encoding/json" - "fmt" - "reflect" - "strconv" -) - -// ToString convert the input to a string. -func ToString(obj interface{}) string { - res := fmt.Sprintf("%v", obj) - return res -} - -// ToJSON convert the input to a valid JSON string -func ToJSON(obj interface{}) (string, error) { - res, err := json.Marshal(obj) - if err != nil { - res = []byte("") - } - return string(res), err -} - -// ToFloat convert the input string to a float, or 0.0 if the input is not a float. -func ToFloat(value interface{}) (res float64, err error) { - val := reflect.ValueOf(value) - - switch value.(type) { - case int, int8, int16, int32, int64: - res = float64(val.Int()) - case uint, uint8, uint16, uint32, uint64: - res = float64(val.Uint()) - case float32, float64: - res = val.Float() - case string: - res, err = strconv.ParseFloat(val.String(), 64) - if err != nil { - res = 0 - } - default: - err = fmt.Errorf("ToInt: unknown interface type %T", value) - res = 0 - } - - return -} - -// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer. -func ToInt(value interface{}) (res int64, err error) { - val := reflect.ValueOf(value) - - switch value.(type) { - case int, int8, int16, int32, int64: - res = val.Int() - case uint, uint8, uint16, uint32, uint64: - res = int64(val.Uint()) - case float32, float64: - res = int64(val.Float()) - case string: - if IsInt(val.String()) { - res, err = strconv.ParseInt(val.String(), 0, 64) - if err != nil { - res = 0 - } - } else { - err = fmt.Errorf("ToInt: invalid numeric format %g", value) - res = 0 - } - default: - err = fmt.Errorf("ToInt: unknown interface type %T", value) - res = 0 - } - - return -} - -// ToBoolean convert the input string to a boolean. -func ToBoolean(str string) (bool, error) { - return strconv.ParseBool(str) -} diff --git a/vendor/github.com/asaskevich/govalidator/doc.go b/vendor/github.com/asaskevich/govalidator/doc.go deleted file mode 100644 index 55dce62dc8..0000000000 --- a/vendor/github.com/asaskevich/govalidator/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -package govalidator - -// A package of validators and sanitizers for strings, structures and collections. diff --git a/vendor/github.com/asaskevich/govalidator/error.go b/vendor/github.com/asaskevich/govalidator/error.go deleted file mode 100644 index 1da2336f47..0000000000 --- a/vendor/github.com/asaskevich/govalidator/error.go +++ /dev/null @@ -1,47 +0,0 @@ -package govalidator - -import ( - "sort" - "strings" -) - -// Errors is an array of multiple errors and conforms to the error interface. -type Errors []error - -// Errors returns itself. -func (es Errors) Errors() []error { - return es -} - -func (es Errors) Error() string { - var errs []string - for _, e := range es { - errs = append(errs, e.Error()) - } - sort.Strings(errs) - return strings.Join(errs, ";") -} - -// Error encapsulates a name, an error and whether there's a custom error message or not. -type Error struct { - Name string - Err error - CustomErrorMessageExists bool - - // Validator indicates the name of the validator that failed - Validator string - Path []string -} - -func (e Error) Error() string { - if e.CustomErrorMessageExists { - return e.Err.Error() - } - - errName := e.Name - if len(e.Path) > 0 { - errName = strings.Join(append(e.Path, e.Name), ".") - } - - return errName + ": " + e.Err.Error() -} diff --git a/vendor/github.com/asaskevich/govalidator/numerics.go b/vendor/github.com/asaskevich/govalidator/numerics.go deleted file mode 100644 index 5041d9e868..0000000000 --- a/vendor/github.com/asaskevich/govalidator/numerics.go +++ /dev/null @@ -1,100 +0,0 @@ -package govalidator - -import ( - "math" -) - -// Abs returns absolute value of number -func Abs(value float64) float64 { - return math.Abs(value) -} - -// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise -func Sign(value float64) float64 { - if value > 0 { - return 1 - } else if value < 0 { - return -1 - } else { - return 0 - } -} - -// IsNegative returns true if value < 0 -func IsNegative(value float64) bool { - return value < 0 -} - -// IsPositive returns true if value > 0 -func IsPositive(value float64) bool { - return value > 0 -} - -// IsNonNegative returns true if value >= 0 -func IsNonNegative(value float64) bool { - return value >= 0 -} - -// IsNonPositive returns true if value <= 0 -func IsNonPositive(value float64) bool { - return value <= 0 -} - -// InRangeInt returns true if value lies between left and right border -func InRangeInt(value, left, right interface{}) bool { - value64, _ := ToInt(value) - left64, _ := ToInt(left) - right64, _ := ToInt(right) - if left64 > right64 { - left64, right64 = right64, left64 - } - return value64 >= left64 && value64 <= right64 -} - -// InRangeFloat32 returns true if value lies between left and right border -func InRangeFloat32(value, left, right float32) bool { - if left > right { - left, right = right, left - } - return value >= left && value <= right -} - -// InRangeFloat64 returns true if value lies between left and right border -func InRangeFloat64(value, left, right float64) bool { - if left > right { - left, right = right, left - } - return value >= left && value <= right -} - -// InRange returns true if value lies between left and right border, generic type to handle int, float32, float64 and string. -// All types must the same type. -// False if value doesn't lie in range or if it incompatible or not comparable -func InRange(value interface{}, left interface{}, right interface{}) bool { - switch value.(type) { - case int: - intValue, _ := ToInt(value) - intLeft, _ := ToInt(left) - intRight, _ := ToInt(right) - return InRangeInt(intValue, intLeft, intRight) - case float32, float64: - intValue, _ := ToFloat(value) - intLeft, _ := ToFloat(left) - intRight, _ := ToFloat(right) - return InRangeFloat64(intValue, intLeft, intRight) - case string: - return value.(string) >= left.(string) && value.(string) <= right.(string) - default: - return false - } -} - -// IsWhole returns true if value is whole number -func IsWhole(value float64) bool { - return math.Remainder(value, 1) == 0 -} - -// IsNatural returns true if value is natural number (positive and whole) -func IsNatural(value float64) bool { - return IsWhole(value) && IsPositive(value) -} diff --git a/vendor/github.com/asaskevich/govalidator/patterns.go b/vendor/github.com/asaskevich/govalidator/patterns.go deleted file mode 100644 index bafc3765ea..0000000000 --- a/vendor/github.com/asaskevich/govalidator/patterns.go +++ /dev/null @@ -1,113 +0,0 @@ -package govalidator - -import "regexp" - -// Basic regular expressions for validating strings -const ( - Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" - CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$" - ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$" - ISBN13 string = "^(?:[0-9]{13})$" - UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" - UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" - UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" - Alpha string = "^[a-zA-Z]+$" - Alphanumeric string = "^[a-zA-Z0-9]+$" - Numeric string = "^[0-9]+$" - Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$" - Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$" - Hexadecimal string = "^[0-9a-fA-F]+$" - Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" - RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" - ASCII string = "^[\x00-\x7F]+$" - Multibyte string = "[^\x00-\x7F]" - FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" - HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" - Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" - PrintableASCII string = "^[\x20-\x7E]+$" - DataURI string = "^data:.+\\/(.+);base64$" - MagnetURI string = "^magnet:\\?xt=urn:[a-zA-Z0-9]+:[a-zA-Z0-9]{32,40}&dn=.+&tr=.+$" - Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" - Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" - DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$` - IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))` - URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)` - URLUsername string = `(\S+(:\S*)?@)` - URLPath string = `((\/|\?|#)[^\s]*)` - URLPort string = `(:(\d{1,5}))` - URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3]|24\d|25[0-5])(\.(\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-5]))` - URLSubdomain string = `((www\.)|([a-zA-Z0-9]+([-_\.]?[a-zA-Z0-9])*[a-zA-Z0-9]\.[a-zA-Z0-9]+))` - URL = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$` - SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` - WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` - UnixPath string = `^(/[^/\x00]*)+/?$` - WinARPath string = `^(?:(?:[a-zA-Z]:|\\\\[a-z0-9_.$●-]+\\[a-z0-9_.$●-]+)\\|\\?[^\\/:*?"<>|\r\n]+\\?)(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` - UnixARPath string = `^((\.{0,2}/)?([^/\x00]*))+/?$` - Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$" - tagName string = "valid" - hasLowerCase string = ".*[[:lower:]]" - hasUpperCase string = ".*[[:upper:]]" - hasWhitespace string = ".*[[:space:]]" - hasWhitespaceOnly string = "^[[:space:]]+$" - IMEI string = "^[0-9a-f]{14}$|^\\d{15}$|^\\d{18}$" - IMSI string = "^\\d{14,15}$" - E164 string = `^\+?[1-9]\d{1,14}$` -) - -// Used by IsFilePath func -const ( - // Unknown is unresolved OS type - Unknown = iota - // Win is Windows type - Win - // Unix is *nix OS types - Unix -) - -var ( - userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$") - hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$") - userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})") - rxEmail = regexp.MustCompile(Email) - rxCreditCard = regexp.MustCompile(CreditCard) - rxISBN10 = regexp.MustCompile(ISBN10) - rxISBN13 = regexp.MustCompile(ISBN13) - rxUUID3 = regexp.MustCompile(UUID3) - rxUUID4 = regexp.MustCompile(UUID4) - rxUUID5 = regexp.MustCompile(UUID5) - rxUUID = regexp.MustCompile(UUID) - rxAlpha = regexp.MustCompile(Alpha) - rxAlphanumeric = regexp.MustCompile(Alphanumeric) - rxNumeric = regexp.MustCompile(Numeric) - rxInt = regexp.MustCompile(Int) - rxFloat = regexp.MustCompile(Float) - rxHexadecimal = regexp.MustCompile(Hexadecimal) - rxHexcolor = regexp.MustCompile(Hexcolor) - rxRGBcolor = regexp.MustCompile(RGBcolor) - rxASCII = regexp.MustCompile(ASCII) - rxPrintableASCII = regexp.MustCompile(PrintableASCII) - rxMultibyte = regexp.MustCompile(Multibyte) - rxFullWidth = regexp.MustCompile(FullWidth) - rxHalfWidth = regexp.MustCompile(HalfWidth) - rxBase64 = regexp.MustCompile(Base64) - rxDataURI = regexp.MustCompile(DataURI) - rxMagnetURI = regexp.MustCompile(MagnetURI) - rxLatitude = regexp.MustCompile(Latitude) - rxLongitude = regexp.MustCompile(Longitude) - rxDNSName = regexp.MustCompile(DNSName) - rxURL = regexp.MustCompile(URL) - rxSSN = regexp.MustCompile(SSN) - rxWinPath = regexp.MustCompile(WinPath) - rxUnixPath = regexp.MustCompile(UnixPath) - rxARWinPath = regexp.MustCompile(WinARPath) - rxARUnixPath = regexp.MustCompile(UnixARPath) - rxSemver = regexp.MustCompile(Semver) - rxHasLowerCase = regexp.MustCompile(hasLowerCase) - rxHasUpperCase = regexp.MustCompile(hasUpperCase) - rxHasWhitespace = regexp.MustCompile(hasWhitespace) - rxHasWhitespaceOnly = regexp.MustCompile(hasWhitespaceOnly) - rxIMEI = regexp.MustCompile(IMEI) - rxIMSI = regexp.MustCompile(IMSI) - rxE164 = regexp.MustCompile(E164) -) diff --git a/vendor/github.com/asaskevich/govalidator/types.go b/vendor/github.com/asaskevich/govalidator/types.go deleted file mode 100644 index c573abb51a..0000000000 --- a/vendor/github.com/asaskevich/govalidator/types.go +++ /dev/null @@ -1,656 +0,0 @@ -package govalidator - -import ( - "reflect" - "regexp" - "sort" - "sync" -) - -// Validator is a wrapper for a validator function that returns bool and accepts string. -type Validator func(str string) bool - -// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type. -// The second parameter should be the context (in the case of validating a struct: the whole object being validated). -type CustomTypeValidator func(i interface{}, o interface{}) bool - -// ParamValidator is a wrapper for validator functions that accept additional parameters. -type ParamValidator func(str string, params ...string) bool - -// InterfaceParamValidator is a wrapper for functions that accept variants parameters for an interface value -type InterfaceParamValidator func(in interface{}, params ...string) bool -type tagOptionsMap map[string]tagOption - -func (t tagOptionsMap) orderedKeys() []string { - var keys []string - for k := range t { - keys = append(keys, k) - } - - sort.Slice(keys, func(a, b int) bool { - return t[keys[a]].order < t[keys[b]].order - }) - - return keys -} - -type tagOption struct { - name string - customErrorMessage string - order int -} - -// UnsupportedTypeError is a wrapper for reflect.Type -type UnsupportedTypeError struct { - Type reflect.Type -} - -// stringValues is a slice of reflect.Value holding *reflect.StringValue. -// It implements the methods to sort by string. -type stringValues []reflect.Value - -// InterfaceParamTagMap is a map of functions accept variants parameters for an interface value -var InterfaceParamTagMap = map[string]InterfaceParamValidator{ - "type": IsType, -} - -// InterfaceParamTagRegexMap maps interface param tags to their respective regexes. -var InterfaceParamTagRegexMap = map[string]*regexp.Regexp{ - "type": regexp.MustCompile(`^type\((.*)\)$`), -} - -// ParamTagMap is a map of functions accept variants parameters -var ParamTagMap = map[string]ParamValidator{ - "length": ByteLength, - "range": Range, - "runelength": RuneLength, - "stringlength": StringLength, - "matches": StringMatches, - "in": IsInRaw, - "rsapub": IsRsaPub, - "minstringlength": MinStringLength, - "maxstringlength": MaxStringLength, -} - -// ParamTagRegexMap maps param tags to their respective regexes. -var ParamTagRegexMap = map[string]*regexp.Regexp{ - "range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"), - "length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"), - "runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"), - "stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"), - "in": regexp.MustCompile(`^in\((.*)\)`), - "matches": regexp.MustCompile(`^matches\((.+)\)$`), - "rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"), - "minstringlength": regexp.MustCompile("^minstringlength\\((\\d+)\\)$"), - "maxstringlength": regexp.MustCompile("^maxstringlength\\((\\d+)\\)$"), -} - -type customTypeTagMap struct { - validators map[string]CustomTypeValidator - - sync.RWMutex -} - -func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) { - tm.RLock() - defer tm.RUnlock() - v, ok := tm.validators[name] - return v, ok -} - -func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) { - tm.Lock() - defer tm.Unlock() - tm.validators[name] = ctv -} - -// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function. -// Use this to validate compound or custom types that need to be handled as a whole, e.g. -// `type UUID [16]byte` (this would be handled as an array of bytes). -var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)} - -// TagMap is a map of functions, that can be used as tags for ValidateStruct function. -var TagMap = map[string]Validator{ - "email": IsEmail, - "url": IsURL, - "dialstring": IsDialString, - "requrl": IsRequestURL, - "requri": IsRequestURI, - "alpha": IsAlpha, - "utfletter": IsUTFLetter, - "alphanum": IsAlphanumeric, - "utfletternum": IsUTFLetterNumeric, - "numeric": IsNumeric, - "utfnumeric": IsUTFNumeric, - "utfdigit": IsUTFDigit, - "hexadecimal": IsHexadecimal, - "hexcolor": IsHexcolor, - "rgbcolor": IsRGBcolor, - "lowercase": IsLowerCase, - "uppercase": IsUpperCase, - "int": IsInt, - "float": IsFloat, - "null": IsNull, - "notnull": IsNotNull, - "uuid": IsUUID, - "uuidv3": IsUUIDv3, - "uuidv4": IsUUIDv4, - "uuidv5": IsUUIDv5, - "creditcard": IsCreditCard, - "isbn10": IsISBN10, - "isbn13": IsISBN13, - "json": IsJSON, - "multibyte": IsMultibyte, - "ascii": IsASCII, - "printableascii": IsPrintableASCII, - "fullwidth": IsFullWidth, - "halfwidth": IsHalfWidth, - "variablewidth": IsVariableWidth, - "base64": IsBase64, - "datauri": IsDataURI, - "ip": IsIP, - "port": IsPort, - "ipv4": IsIPv4, - "ipv6": IsIPv6, - "dns": IsDNSName, - "host": IsHost, - "mac": IsMAC, - "latitude": IsLatitude, - "longitude": IsLongitude, - "ssn": IsSSN, - "semver": IsSemver, - "rfc3339": IsRFC3339, - "rfc3339WithoutZone": IsRFC3339WithoutZone, - "ISO3166Alpha2": IsISO3166Alpha2, - "ISO3166Alpha3": IsISO3166Alpha3, - "ISO4217": IsISO4217, - "IMEI": IsIMEI, - "ulid": IsULID, -} - -// ISO3166Entry stores country codes -type ISO3166Entry struct { - EnglishShortName string - FrenchShortName string - Alpha2Code string - Alpha3Code string - Numeric string -} - -//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes" -var ISO3166List = []ISO3166Entry{ - {"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"}, - {"Albania", "Albanie (l')", "AL", "ALB", "008"}, - {"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"}, - {"Algeria", "Algérie (l')", "DZ", "DZA", "012"}, - {"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"}, - {"Andorra", "Andorre (l')", "AD", "AND", "020"}, - {"Angola", "Angola (l')", "AO", "AGO", "024"}, - {"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"}, - {"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"}, - {"Argentina", "Argentine (l')", "AR", "ARG", "032"}, - {"Australia", "Australie (l')", "AU", "AUS", "036"}, - {"Austria", "Autriche (l')", "AT", "AUT", "040"}, - {"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"}, - {"Bahrain", "Bahreïn", "BH", "BHR", "048"}, - {"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"}, - {"Armenia", "Arménie (l')", "AM", "ARM", "051"}, - {"Barbados", "Barbade (la)", "BB", "BRB", "052"}, - {"Belgium", "Belgique (la)", "BE", "BEL", "056"}, - {"Bermuda", "Bermudes (les)", "BM", "BMU", "060"}, - {"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"}, - {"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"}, - {"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"}, - {"Botswana", "Botswana (le)", "BW", "BWA", "072"}, - {"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"}, - {"Brazil", "Brésil (le)", "BR", "BRA", "076"}, - {"Belize", "Belize (le)", "BZ", "BLZ", "084"}, - {"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"}, - {"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"}, - {"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"}, - {"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"}, - {"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"}, - {"Myanmar", "Myanmar (le)", "MM", "MMR", "104"}, - {"Burundi", "Burundi (le)", "BI", "BDI", "108"}, - {"Belarus", "Bélarus (le)", "BY", "BLR", "112"}, - {"Cambodia", "Cambodge (le)", "KH", "KHM", "116"}, - {"Cameroon", "Cameroun (le)", "CM", "CMR", "120"}, - {"Canada", "Canada (le)", "CA", "CAN", "124"}, - {"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"}, - {"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"}, - {"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"}, - {"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"}, - {"Chad", "Tchad (le)", "TD", "TCD", "148"}, - {"Chile", "Chili (le)", "CL", "CHL", "152"}, - {"China", "Chine (la)", "CN", "CHN", "156"}, - {"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"}, - {"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"}, - {"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"}, - {"Colombia", "Colombie (la)", "CO", "COL", "170"}, - {"Comoros (the)", "Comores (les)", "KM", "COM", "174"}, - {"Mayotte", "Mayotte", "YT", "MYT", "175"}, - {"Congo (the)", "Congo (le)", "CG", "COG", "178"}, - {"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"}, - {"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"}, - {"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"}, - {"Croatia", "Croatie (la)", "HR", "HRV", "191"}, - {"Cuba", "Cuba", "CU", "CUB", "192"}, - {"Cyprus", "Chypre", "CY", "CYP", "196"}, - {"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"}, - {"Benin", "Bénin (le)", "BJ", "BEN", "204"}, - {"Denmark", "Danemark (le)", "DK", "DNK", "208"}, - {"Dominica", "Dominique (la)", "DM", "DMA", "212"}, - {"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"}, - {"Ecuador", "Équateur (l')", "EC", "ECU", "218"}, - {"El Salvador", "El Salvador", "SV", "SLV", "222"}, - {"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"}, - {"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"}, - {"Eritrea", "Érythrée (l')", "ER", "ERI", "232"}, - {"Estonia", "Estonie (l')", "EE", "EST", "233"}, - {"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"}, - {"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"}, - {"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"}, - {"Fiji", "Fidji (les)", "FJ", "FJI", "242"}, - {"Finland", "Finlande (la)", "FI", "FIN", "246"}, - {"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"}, - {"France", "France (la)", "FR", "FRA", "250"}, - {"French Guiana", "Guyane française (la )", "GF", "GUF", "254"}, - {"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"}, - {"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"}, - {"Djibouti", "Djibouti", "DJ", "DJI", "262"}, - {"Gabon", "Gabon (le)", "GA", "GAB", "266"}, - {"Georgia", "Géorgie (la)", "GE", "GEO", "268"}, - {"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"}, - {"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"}, - {"Germany", "Allemagne (l')", "DE", "DEU", "276"}, - {"Ghana", "Ghana (le)", "GH", "GHA", "288"}, - {"Gibraltar", "Gibraltar", "GI", "GIB", "292"}, - {"Kiribati", "Kiribati", "KI", "KIR", "296"}, - {"Greece", "Grèce (la)", "GR", "GRC", "300"}, - {"Greenland", "Groenland (le)", "GL", "GRL", "304"}, - {"Grenada", "Grenade (la)", "GD", "GRD", "308"}, - {"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"}, - {"Guam", "Guam", "GU", "GUM", "316"}, - {"Guatemala", "Guatemala (le)", "GT", "GTM", "320"}, - {"Guinea", "Guinée (la)", "GN", "GIN", "324"}, - {"Guyana", "Guyana (le)", "GY", "GUY", "328"}, - {"Haiti", "Haïti", "HT", "HTI", "332"}, - {"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"}, - {"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"}, - {"Honduras", "Honduras (le)", "HN", "HND", "340"}, - {"Hong Kong", "Hong Kong", "HK", "HKG", "344"}, - {"Hungary", "Hongrie (la)", "HU", "HUN", "348"}, - {"Iceland", "Islande (l')", "IS", "ISL", "352"}, - {"India", "Inde (l')", "IN", "IND", "356"}, - {"Indonesia", "Indonésie (l')", "ID", "IDN", "360"}, - {"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"}, - {"Iraq", "Iraq (l')", "IQ", "IRQ", "368"}, - {"Ireland", "Irlande (l')", "IE", "IRL", "372"}, - {"Israel", "Israël", "IL", "ISR", "376"}, - {"Italy", "Italie (l')", "IT", "ITA", "380"}, - {"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"}, - {"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"}, - {"Japan", "Japon (le)", "JP", "JPN", "392"}, - {"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"}, - {"Jordan", "Jordanie (la)", "JO", "JOR", "400"}, - {"Kenya", "Kenya (le)", "KE", "KEN", "404"}, - {"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"}, - {"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"}, - {"Kuwait", "Koweït (le)", "KW", "KWT", "414"}, - {"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"}, - {"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"}, - {"Lebanon", "Liban (le)", "LB", "LBN", "422"}, - {"Lesotho", "Lesotho (le)", "LS", "LSO", "426"}, - {"Latvia", "Lettonie (la)", "LV", "LVA", "428"}, - {"Liberia", "Libéria (le)", "LR", "LBR", "430"}, - {"Libya", "Libye (la)", "LY", "LBY", "434"}, - {"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"}, - {"Lithuania", "Lituanie (la)", "LT", "LTU", "440"}, - {"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"}, - {"Macao", "Macao", "MO", "MAC", "446"}, - {"Madagascar", "Madagascar", "MG", "MDG", "450"}, - {"Malawi", "Malawi (le)", "MW", "MWI", "454"}, - {"Malaysia", "Malaisie (la)", "MY", "MYS", "458"}, - {"Maldives", "Maldives (les)", "MV", "MDV", "462"}, - {"Mali", "Mali (le)", "ML", "MLI", "466"}, - {"Malta", "Malte", "MT", "MLT", "470"}, - {"Martinique", "Martinique (la)", "MQ", "MTQ", "474"}, - {"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"}, - {"Mauritius", "Maurice", "MU", "MUS", "480"}, - {"Mexico", "Mexique (le)", "MX", "MEX", "484"}, - {"Monaco", "Monaco", "MC", "MCO", "492"}, - {"Mongolia", "Mongolie (la)", "MN", "MNG", "496"}, - {"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"}, - {"Montenegro", "Monténégro (le)", "ME", "MNE", "499"}, - {"Montserrat", "Montserrat", "MS", "MSR", "500"}, - {"Morocco", "Maroc (le)", "MA", "MAR", "504"}, - {"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"}, - {"Oman", "Oman", "OM", "OMN", "512"}, - {"Namibia", "Namibie (la)", "NA", "NAM", "516"}, - {"Nauru", "Nauru", "NR", "NRU", "520"}, - {"Nepal", "Népal (le)", "NP", "NPL", "524"}, - {"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"}, - {"Curaçao", "Curaçao", "CW", "CUW", "531"}, - {"Aruba", "Aruba", "AW", "ABW", "533"}, - {"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"}, - {"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"}, - {"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"}, - {"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"}, - {"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"}, - {"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"}, - {"Niger (the)", "Niger (le)", "NE", "NER", "562"}, - {"Nigeria", "Nigéria (le)", "NG", "NGA", "566"}, - {"Niue", "Niue", "NU", "NIU", "570"}, - {"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"}, - {"Norway", "Norvège (la)", "NO", "NOR", "578"}, - {"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"}, - {"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"}, - {"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"}, - {"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"}, - {"Palau", "Palaos (les)", "PW", "PLW", "585"}, - {"Pakistan", "Pakistan (le)", "PK", "PAK", "586"}, - {"Panama", "Panama (le)", "PA", "PAN", "591"}, - {"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"}, - {"Paraguay", "Paraguay (le)", "PY", "PRY", "600"}, - {"Peru", "Pérou (le)", "PE", "PER", "604"}, - {"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"}, - {"Pitcairn", "Pitcairn", "PN", "PCN", "612"}, - {"Poland", "Pologne (la)", "PL", "POL", "616"}, - {"Portugal", "Portugal (le)", "PT", "PRT", "620"}, - {"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"}, - {"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"}, - {"Puerto Rico", "Porto Rico", "PR", "PRI", "630"}, - {"Qatar", "Qatar (le)", "QA", "QAT", "634"}, - {"Réunion", "Réunion (La)", "RE", "REU", "638"}, - {"Romania", "Roumanie (la)", "RO", "ROU", "642"}, - {"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"}, - {"Rwanda", "Rwanda (le)", "RW", "RWA", "646"}, - {"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"}, - {"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"}, - {"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"}, - {"Anguilla", "Anguilla", "AI", "AIA", "660"}, - {"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"}, - {"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"}, - {"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"}, - {"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"}, - {"San Marino", "Saint-Marin", "SM", "SMR", "674"}, - {"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"}, - {"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"}, - {"Senegal", "Sénégal (le)", "SN", "SEN", "686"}, - {"Serbia", "Serbie (la)", "RS", "SRB", "688"}, - {"Seychelles", "Seychelles (les)", "SC", "SYC", "690"}, - {"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"}, - {"Singapore", "Singapour", "SG", "SGP", "702"}, - {"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"}, - {"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"}, - {"Slovenia", "Slovénie (la)", "SI", "SVN", "705"}, - {"Somalia", "Somalie (la)", "SO", "SOM", "706"}, - {"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"}, - {"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"}, - {"Spain", "Espagne (l')", "ES", "ESP", "724"}, - {"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"}, - {"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"}, - {"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"}, - {"Suriname", "Suriname (le)", "SR", "SUR", "740"}, - {"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"}, - {"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"}, - {"Sweden", "Suède (la)", "SE", "SWE", "752"}, - {"Switzerland", "Suisse (la)", "CH", "CHE", "756"}, - {"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"}, - {"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"}, - {"Thailand", "Thaïlande (la)", "TH", "THA", "764"}, - {"Togo", "Togo (le)", "TG", "TGO", "768"}, - {"Tokelau", "Tokelau (les)", "TK", "TKL", "772"}, - {"Tonga", "Tonga (les)", "TO", "TON", "776"}, - {"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"}, - {"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"}, - {"Tunisia", "Tunisie (la)", "TN", "TUN", "788"}, - {"Turkey", "Turquie (la)", "TR", "TUR", "792"}, - {"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"}, - {"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"}, - {"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"}, - {"Uganda", "Ouganda (l')", "UG", "UGA", "800"}, - {"Ukraine", "Ukraine (l')", "UA", "UKR", "804"}, - {"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'ex‑République yougoslave de)", "MK", "MKD", "807"}, - {"Egypt", "Égypte (l')", "EG", "EGY", "818"}, - {"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"}, - {"Guernsey", "Guernesey", "GG", "GGY", "831"}, - {"Jersey", "Jersey", "JE", "JEY", "832"}, - {"Isle of Man", "Île de Man", "IM", "IMN", "833"}, - {"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"}, - {"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"}, - {"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"}, - {"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"}, - {"Uruguay", "Uruguay (l')", "UY", "URY", "858"}, - {"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"}, - {"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"}, - {"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"}, - {"Samoa", "Samoa (le)", "WS", "WSM", "882"}, - {"Yemen", "Yémen (le)", "YE", "YEM", "887"}, - {"Zambia", "Zambie (la)", "ZM", "ZMB", "894"}, -} - -// ISO4217List is the list of ISO currency codes -var ISO4217List = []string{ - "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", - "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", - "CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK", - "DJF", "DKK", "DOP", "DZD", - "EGP", "ERN", "ETB", "EUR", - "FJD", "FKP", - "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", - "HKD", "HNL", "HRK", "HTG", "HUF", - "IDR", "ILS", "INR", "IQD", "IRR", "ISK", - "JMD", "JOD", "JPY", - "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", - "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", - "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", - "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", - "OMR", - "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", - "QAR", - "RON", "RSD", "RUB", "RWF", - "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "STN", "SVC", "SYP", "SZL", - "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", - "UAH", "UGX", "USD", "USN", "UYI", "UYU", "UYW", "UZS", - "VEF", "VES", "VND", "VUV", - "WST", - "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX", - "YER", - "ZAR", "ZMW", "ZWL", -} - -// ISO693Entry stores ISO language codes -type ISO693Entry struct { - Alpha3bCode string - Alpha2Code string - English string -} - -//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json -var ISO693List = []ISO693Entry{ - {Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"}, - {Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"}, - {Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"}, - {Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"}, - {Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"}, - {Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"}, - {Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"}, - {Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"}, - {Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"}, - {Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"}, - {Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"}, - {Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"}, - {Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"}, - {Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"}, - {Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"}, - {Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"}, - {Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"}, - {Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"}, - {Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"}, - {Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"}, - {Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"}, - {Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"}, - {Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"}, - {Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"}, - {Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"}, - {Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"}, - {Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"}, - {Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"}, - {Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"}, - {Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"}, - {Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"}, - {Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"}, - {Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"}, - {Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"}, - {Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"}, - {Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"}, - {Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"}, - {Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"}, - {Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"}, - {Alpha3bCode: "eng", Alpha2Code: "en", English: "English"}, - {Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"}, - {Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"}, - {Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"}, - {Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"}, - {Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"}, - {Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"}, - {Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"}, - {Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"}, - {Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"}, - {Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"}, - {Alpha3bCode: "ger", Alpha2Code: "de", English: "German"}, - {Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"}, - {Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"}, - {Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"}, - {Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"}, - {Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"}, - {Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"}, - {Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"}, - {Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"}, - {Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"}, - {Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"}, - {Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"}, - {Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"}, - {Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"}, - {Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"}, - {Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"}, - {Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"}, - {Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"}, - {Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"}, - {Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"}, - {Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"}, - {Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"}, - {Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"}, - {Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"}, - {Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"}, - {Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"}, - {Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"}, - {Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"}, - {Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"}, - {Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"}, - {Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"}, - {Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"}, - {Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"}, - {Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"}, - {Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"}, - {Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"}, - {Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"}, - {Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"}, - {Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"}, - {Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"}, - {Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"}, - {Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"}, - {Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"}, - {Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"}, - {Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"}, - {Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"}, - {Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"}, - {Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"}, - {Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"}, - {Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"}, - {Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"}, - {Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"}, - {Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"}, - {Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"}, - {Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"}, - {Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"}, - {Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"}, - {Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"}, - {Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"}, - {Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"}, - {Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"}, - {Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"}, - {Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"}, - {Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"}, - {Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"}, - {Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"}, - {Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"}, - {Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"}, - {Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"}, - {Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"}, - {Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"}, - {Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"}, - {Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"}, - {Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"}, - {Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"}, - {Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"}, - {Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"}, - {Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"}, - {Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"}, - {Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"}, - {Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"}, - {Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"}, - {Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"}, - {Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"}, - {Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"}, - {Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"}, - {Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"}, - {Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"}, - {Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"}, - {Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"}, - {Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"}, - {Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"}, - {Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"}, - {Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"}, - {Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"}, - {Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"}, - {Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"}, - {Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"}, - {Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"}, - {Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"}, - {Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"}, - {Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"}, - {Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"}, - {Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"}, - {Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"}, - {Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"}, - {Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"}, - {Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"}, - {Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"}, - {Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"}, - {Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"}, - {Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"}, - {Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"}, - {Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"}, - {Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"}, - {Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"}, - {Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"}, - {Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"}, - {Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"}, - {Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"}, - {Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"}, - {Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"}, - {Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"}, - {Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"}, - {Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"}, - {Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"}, - {Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"}, - {Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"}, - {Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"}, - {Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"}, - {Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"}, - {Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"}, - {Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"}, - {Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"}, -} diff --git a/vendor/github.com/asaskevich/govalidator/utils.go b/vendor/github.com/asaskevich/govalidator/utils.go deleted file mode 100644 index f4c30f824a..0000000000 --- a/vendor/github.com/asaskevich/govalidator/utils.go +++ /dev/null @@ -1,270 +0,0 @@ -package govalidator - -import ( - "errors" - "fmt" - "html" - "math" - "path" - "regexp" - "strings" - "unicode" - "unicode/utf8" -) - -// Contains checks if the string contains the substring. -func Contains(str, substring string) bool { - return strings.Contains(str, substring) -} - -// Matches checks if string matches the pattern (pattern is regular expression) -// In case of error return false -func Matches(str, pattern string) bool { - match, _ := regexp.MatchString(pattern, str) - return match -} - -// LeftTrim trims characters from the left side of the input. -// If second argument is empty, it will remove leading spaces. -func LeftTrim(str, chars string) string { - if chars == "" { - return strings.TrimLeftFunc(str, unicode.IsSpace) - } - r, _ := regexp.Compile("^[" + chars + "]+") - return r.ReplaceAllString(str, "") -} - -// RightTrim trims characters from the right side of the input. -// If second argument is empty, it will remove trailing spaces. -func RightTrim(str, chars string) string { - if chars == "" { - return strings.TrimRightFunc(str, unicode.IsSpace) - } - r, _ := regexp.Compile("[" + chars + "]+$") - return r.ReplaceAllString(str, "") -} - -// Trim trims characters from both sides of the input. -// If second argument is empty, it will remove spaces. -func Trim(str, chars string) string { - return LeftTrim(RightTrim(str, chars), chars) -} - -// WhiteList removes characters that do not appear in the whitelist. -func WhiteList(str, chars string) string { - pattern := "[^" + chars + "]+" - r, _ := regexp.Compile(pattern) - return r.ReplaceAllString(str, "") -} - -// BlackList removes characters that appear in the blacklist. -func BlackList(str, chars string) string { - pattern := "[" + chars + "]+" - r, _ := regexp.Compile(pattern) - return r.ReplaceAllString(str, "") -} - -// StripLow removes characters with a numerical value < 32 and 127, mostly control characters. -// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD). -func StripLow(str string, keepNewLines bool) string { - chars := "" - if keepNewLines { - chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F" - } else { - chars = "\x00-\x1F\x7F" - } - return BlackList(str, chars) -} - -// ReplacePattern replaces regular expression pattern in string -func ReplacePattern(str, pattern, replace string) string { - r, _ := regexp.Compile(pattern) - return r.ReplaceAllString(str, replace) -} - -// Escape replaces <, >, & and " with HTML entities. -var Escape = html.EscapeString - -func addSegment(inrune, segment []rune) []rune { - if len(segment) == 0 { - return inrune - } - if len(inrune) != 0 { - inrune = append(inrune, '_') - } - inrune = append(inrune, segment...) - return inrune -} - -// UnderscoreToCamelCase converts from underscore separated form to camel case form. -// Ex.: my_func => MyFunc -func UnderscoreToCamelCase(s string) string { - return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1) -} - -// CamelCaseToUnderscore converts from camel case form to underscore separated form. -// Ex.: MyFunc => my_func -func CamelCaseToUnderscore(str string) string { - var output []rune - var segment []rune - for _, r := range str { - - // not treat number as separate segment - if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) { - output = addSegment(output, segment) - segment = nil - } - segment = append(segment, unicode.ToLower(r)) - } - output = addSegment(output, segment) - return string(output) -} - -// Reverse returns reversed string -func Reverse(s string) string { - r := []rune(s) - for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { - r[i], r[j] = r[j], r[i] - } - return string(r) -} - -// GetLines splits string by "\n" and return array of lines -func GetLines(s string) []string { - return strings.Split(s, "\n") -} - -// GetLine returns specified line of multiline string -func GetLine(s string, index int) (string, error) { - lines := GetLines(s) - if index < 0 || index >= len(lines) { - return "", errors.New("line index out of bounds") - } - return lines[index], nil -} - -// RemoveTags removes all tags from HTML string -func RemoveTags(s string) string { - return ReplacePattern(s, "<[^>]*>", "") -} - -// SafeFileName returns safe string that can be used in file names -func SafeFileName(str string) string { - name := strings.ToLower(str) - name = path.Clean(path.Base(name)) - name = strings.Trim(name, " ") - separators, err := regexp.Compile(`[ &_=+:]`) - if err == nil { - name = separators.ReplaceAllString(name, "-") - } - legal, err := regexp.Compile(`[^[:alnum:]-.]`) - if err == nil { - name = legal.ReplaceAllString(name, "") - } - for strings.Contains(name, "--") { - name = strings.Replace(name, "--", "-", -1) - } - return name -} - -// NormalizeEmail canonicalize an email address. -// The local part of the email address is lowercased for all domains; the hostname is always lowercased and -// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail). -// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and -// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are -// normalized to @gmail.com. -func NormalizeEmail(str string) (string, error) { - if !IsEmail(str) { - return "", fmt.Errorf("%s is not an email", str) - } - parts := strings.Split(str, "@") - parts[0] = strings.ToLower(parts[0]) - parts[1] = strings.ToLower(parts[1]) - if parts[1] == "gmail.com" || parts[1] == "googlemail.com" { - parts[1] = "gmail.com" - parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0] - } - return strings.Join(parts, "@"), nil -} - -// Truncate a string to the closest length without breaking words. -func Truncate(str string, length int, ending string) string { - var aftstr, befstr string - if len(str) > length { - words := strings.Fields(str) - before, present := 0, 0 - for i := range words { - befstr = aftstr - before = present - aftstr = aftstr + words[i] + " " - present = len(aftstr) - if present > length && i != 0 { - if (length - before) < (present - length) { - return Trim(befstr, " /\\.,\"'#!?&@+-") + ending - } - return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending - } - } - } - - return str -} - -// PadLeft pads left side of a string if size of string is less then indicated pad length -func PadLeft(str string, padStr string, padLen int) string { - return buildPadStr(str, padStr, padLen, true, false) -} - -// PadRight pads right side of a string if size of string is less then indicated pad length -func PadRight(str string, padStr string, padLen int) string { - return buildPadStr(str, padStr, padLen, false, true) -} - -// PadBoth pads both sides of a string if size of string is less then indicated pad length -func PadBoth(str string, padStr string, padLen int) string { - return buildPadStr(str, padStr, padLen, true, true) -} - -// PadString either left, right or both sides. -// Note that padding string can be unicode and more then one character -func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string { - - // When padded length is less then the current string size - if padLen < utf8.RuneCountInString(str) { - return str - } - - padLen -= utf8.RuneCountInString(str) - - targetLen := padLen - - targetLenLeft := targetLen - targetLenRight := targetLen - if padLeft && padRight { - targetLenLeft = padLen / 2 - targetLenRight = padLen - targetLenLeft - } - - strToRepeatLen := utf8.RuneCountInString(padStr) - - repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen))) - repeatedString := strings.Repeat(padStr, repeatTimes) - - leftSide := "" - if padLeft { - leftSide = repeatedString[0:targetLenLeft] - } - - rightSide := "" - if padRight { - rightSide = repeatedString[0:targetLenRight] - } - - return leftSide + str + rightSide -} - -// TruncatingErrorf removes extra args from fmt.Errorf if not formatted in the str object -func TruncatingErrorf(str string, args ...interface{}) error { - n := strings.Count(str, "%s") - return fmt.Errorf(str, args[:n]...) -} diff --git a/vendor/github.com/asaskevich/govalidator/validator.go b/vendor/github.com/asaskevich/govalidator/validator.go deleted file mode 100644 index c9c4fac065..0000000000 --- a/vendor/github.com/asaskevich/govalidator/validator.go +++ /dev/null @@ -1,1768 +0,0 @@ -// Package govalidator is package of validators and sanitizers for strings, structs and collections. -package govalidator - -import ( - "bytes" - "crypto/rsa" - "crypto/x509" - "encoding/base64" - "encoding/json" - "encoding/pem" - "fmt" - "io/ioutil" - "net" - "net/url" - "reflect" - "regexp" - "sort" - "strconv" - "strings" - "time" - "unicode" - "unicode/utf8" -) - -var ( - fieldsRequiredByDefault bool - nilPtrAllowedByRequired = false - notNumberRegexp = regexp.MustCompile("[^0-9]+") - whiteSpacesAndMinus = regexp.MustCompile(`[\s-]+`) - paramsRegexp = regexp.MustCompile(`\(.*\)$`) -) - -const maxURLRuneCount = 2083 -const minURLRuneCount = 3 -const rfc3339WithoutZone = "2006-01-02T15:04:05" - -// SetFieldsRequiredByDefault causes validation to fail when struct fields -// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). -// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): -// type exampleStruct struct { -// Name string `` -// Email string `valid:"email"` -// This, however, will only fail when Email is empty or an invalid email address: -// type exampleStruct2 struct { -// Name string `valid:"-"` -// Email string `valid:"email"` -// Lastly, this will only fail when Email is an invalid email address but not when it's empty: -// type exampleStruct2 struct { -// Name string `valid:"-"` -// Email string `valid:"email,optional"` -func SetFieldsRequiredByDefault(value bool) { - fieldsRequiredByDefault = value -} - -// SetNilPtrAllowedByRequired causes validation to pass for nil ptrs when a field is set to required. -// The validation will still reject ptr fields in their zero value state. Example with this enabled: -// type exampleStruct struct { -// Name *string `valid:"required"` -// With `Name` set to "", this will be considered invalid input and will cause a validation error. -// With `Name` set to nil, this will be considered valid by validation. -// By default this is disabled. -func SetNilPtrAllowedByRequired(value bool) { - nilPtrAllowedByRequired = value -} - -// IsEmail checks if the string is an email. -func IsEmail(str string) bool { - // TODO uppercase letters are not supported - return rxEmail.MatchString(str) -} - -// IsExistingEmail checks if the string is an email of existing domain -func IsExistingEmail(email string) bool { - - if len(email) < 6 || len(email) > 254 { - return false - } - at := strings.LastIndex(email, "@") - if at <= 0 || at > len(email)-3 { - return false - } - user := email[:at] - host := email[at+1:] - if len(user) > 64 { - return false - } - switch host { - case "localhost", "example.com": - return true - } - if userDotRegexp.MatchString(user) || !userRegexp.MatchString(user) || !hostRegexp.MatchString(host) { - return false - } - if _, err := net.LookupMX(host); err != nil { - if _, err := net.LookupIP(host); err != nil { - return false - } - } - - return true -} - -// IsURL checks if the string is an URL. -func IsURL(str string) bool { - if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") { - return false - } - strTemp := str - if strings.Contains(str, ":") && !strings.Contains(str, "://") { - // support no indicated urlscheme but with colon for port number - // http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString - strTemp = "http://" + str - } - u, err := url.Parse(strTemp) - if err != nil { - return false - } - if strings.HasPrefix(u.Host, ".") { - return false - } - if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) { - return false - } - return rxURL.MatchString(str) -} - -// IsRequestURL checks if the string rawurl, assuming -// it was received in an HTTP request, is a valid -// URL confirm to RFC 3986 -func IsRequestURL(rawurl string) bool { - url, err := url.ParseRequestURI(rawurl) - if err != nil { - return false //Couldn't even parse the rawurl - } - if len(url.Scheme) == 0 { - return false //No Scheme found - } - return true -} - -// IsRequestURI checks if the string rawurl, assuming -// it was received in an HTTP request, is an -// absolute URI or an absolute path. -func IsRequestURI(rawurl string) bool { - _, err := url.ParseRequestURI(rawurl) - return err == nil -} - -// IsAlpha checks if the string contains only letters (a-zA-Z). Empty string is valid. -func IsAlpha(str string) bool { - if IsNull(str) { - return true - } - return rxAlpha.MatchString(str) -} - -//IsUTFLetter checks if the string contains only unicode letter characters. -//Similar to IsAlpha but for all languages. Empty string is valid. -func IsUTFLetter(str string) bool { - if IsNull(str) { - return true - } - - for _, c := range str { - if !unicode.IsLetter(c) { - return false - } - } - return true - -} - -// IsAlphanumeric checks if the string contains only letters and numbers. Empty string is valid. -func IsAlphanumeric(str string) bool { - if IsNull(str) { - return true - } - return rxAlphanumeric.MatchString(str) -} - -// IsUTFLetterNumeric checks if the string contains only unicode letters and numbers. Empty string is valid. -func IsUTFLetterNumeric(str string) bool { - if IsNull(str) { - return true - } - for _, c := range str { - if !unicode.IsLetter(c) && !unicode.IsNumber(c) { //letters && numbers are ok - return false - } - } - return true - -} - -// IsNumeric checks if the string contains only numbers. Empty string is valid. -func IsNumeric(str string) bool { - if IsNull(str) { - return true - } - return rxNumeric.MatchString(str) -} - -// IsUTFNumeric checks if the string contains only unicode numbers of any kind. -// Numbers can be 0-9 but also Fractions ¾,Roman Ⅸ and Hangzhou 〩. Empty string is valid. -func IsUTFNumeric(str string) bool { - if IsNull(str) { - return true - } - if strings.IndexAny(str, "+-") > 0 { - return false - } - if len(str) > 1 { - str = strings.TrimPrefix(str, "-") - str = strings.TrimPrefix(str, "+") - } - for _, c := range str { - if !unicode.IsNumber(c) { //numbers && minus sign are ok - return false - } - } - return true - -} - -// IsUTFDigit checks if the string contains only unicode radix-10 decimal digits. Empty string is valid. -func IsUTFDigit(str string) bool { - if IsNull(str) { - return true - } - if strings.IndexAny(str, "+-") > 0 { - return false - } - if len(str) > 1 { - str = strings.TrimPrefix(str, "-") - str = strings.TrimPrefix(str, "+") - } - for _, c := range str { - if !unicode.IsDigit(c) { //digits && minus sign are ok - return false - } - } - return true - -} - -// IsHexadecimal checks if the string is a hexadecimal number. -func IsHexadecimal(str string) bool { - return rxHexadecimal.MatchString(str) -} - -// IsHexcolor checks if the string is a hexadecimal color. -func IsHexcolor(str string) bool { - return rxHexcolor.MatchString(str) -} - -// IsRGBcolor checks if the string is a valid RGB color in form rgb(RRR, GGG, BBB). -func IsRGBcolor(str string) bool { - return rxRGBcolor.MatchString(str) -} - -// IsLowerCase checks if the string is lowercase. Empty string is valid. -func IsLowerCase(str string) bool { - if IsNull(str) { - return true - } - return str == strings.ToLower(str) -} - -// IsUpperCase checks if the string is uppercase. Empty string is valid. -func IsUpperCase(str string) bool { - if IsNull(str) { - return true - } - return str == strings.ToUpper(str) -} - -// HasLowerCase checks if the string contains at least 1 lowercase. Empty string is valid. -func HasLowerCase(str string) bool { - if IsNull(str) { - return true - } - return rxHasLowerCase.MatchString(str) -} - -// HasUpperCase checks if the string contains as least 1 uppercase. Empty string is valid. -func HasUpperCase(str string) bool { - if IsNull(str) { - return true - } - return rxHasUpperCase.MatchString(str) -} - -// IsInt checks if the string is an integer. Empty string is valid. -func IsInt(str string) bool { - if IsNull(str) { - return true - } - return rxInt.MatchString(str) -} - -// IsFloat checks if the string is a float. -func IsFloat(str string) bool { - return str != "" && rxFloat.MatchString(str) -} - -// IsDivisibleBy checks if the string is a number that's divisible by another. -// If second argument is not valid integer or zero, it's return false. -// Otherwise, if first argument is not valid integer or zero, it's return true (Invalid string converts to zero). -func IsDivisibleBy(str, num string) bool { - f, _ := ToFloat(str) - p := int64(f) - q, _ := ToInt(num) - if q == 0 { - return false - } - return (p == 0) || (p%q == 0) -} - -// IsNull checks if the string is null. -func IsNull(str string) bool { - return len(str) == 0 -} - -// IsNotNull checks if the string is not null. -func IsNotNull(str string) bool { - return !IsNull(str) -} - -// HasWhitespaceOnly checks the string only contains whitespace -func HasWhitespaceOnly(str string) bool { - return len(str) > 0 && rxHasWhitespaceOnly.MatchString(str) -} - -// HasWhitespace checks if the string contains any whitespace -func HasWhitespace(str string) bool { - return len(str) > 0 && rxHasWhitespace.MatchString(str) -} - -// IsByteLength checks if the string's length (in bytes) falls in a range. -func IsByteLength(str string, min, max int) bool { - return len(str) >= min && len(str) <= max -} - -// IsUUIDv3 checks if the string is a UUID version 3. -func IsUUIDv3(str string) bool { - return rxUUID3.MatchString(str) -} - -// IsUUIDv4 checks if the string is a UUID version 4. -func IsUUIDv4(str string) bool { - return rxUUID4.MatchString(str) -} - -// IsUUIDv5 checks if the string is a UUID version 5. -func IsUUIDv5(str string) bool { - return rxUUID5.MatchString(str) -} - -// IsUUID checks if the string is a UUID (version 3, 4 or 5). -func IsUUID(str string) bool { - return rxUUID.MatchString(str) -} - -// Byte to index table for O(1) lookups when unmarshaling. -// We use 0xFF as sentinel value for invalid indexes. -var ulidDec = [...]byte{ - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x01, - 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, - 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14, 0x15, 0xFF, - 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, 0x1D, 0x1E, - 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0A, 0x0B, 0x0C, - 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0xFF, 0x12, 0x13, 0xFF, 0x14, - 0x15, 0xFF, 0x16, 0x17, 0x18, 0x19, 0x1A, 0xFF, 0x1B, 0x1C, - 0x1D, 0x1E, 0x1F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, -} - -// EncodedSize is the length of a text encoded ULID. -const ulidEncodedSize = 26 - -// IsULID checks if the string is a ULID. -// -// Implementation got from: -// https://github.com/oklog/ulid (Apache-2.0 License) -// -func IsULID(str string) bool { - // Check if a base32 encoded ULID is the right length. - if len(str) != ulidEncodedSize { - return false - } - - // Check if all the characters in a base32 encoded ULID are part of the - // expected base32 character set. - if ulidDec[str[0]] == 0xFF || - ulidDec[str[1]] == 0xFF || - ulidDec[str[2]] == 0xFF || - ulidDec[str[3]] == 0xFF || - ulidDec[str[4]] == 0xFF || - ulidDec[str[5]] == 0xFF || - ulidDec[str[6]] == 0xFF || - ulidDec[str[7]] == 0xFF || - ulidDec[str[8]] == 0xFF || - ulidDec[str[9]] == 0xFF || - ulidDec[str[10]] == 0xFF || - ulidDec[str[11]] == 0xFF || - ulidDec[str[12]] == 0xFF || - ulidDec[str[13]] == 0xFF || - ulidDec[str[14]] == 0xFF || - ulidDec[str[15]] == 0xFF || - ulidDec[str[16]] == 0xFF || - ulidDec[str[17]] == 0xFF || - ulidDec[str[18]] == 0xFF || - ulidDec[str[19]] == 0xFF || - ulidDec[str[20]] == 0xFF || - ulidDec[str[21]] == 0xFF || - ulidDec[str[22]] == 0xFF || - ulidDec[str[23]] == 0xFF || - ulidDec[str[24]] == 0xFF || - ulidDec[str[25]] == 0xFF { - return false - } - - // Check if the first character in a base32 encoded ULID will overflow. This - // happens because the base32 representation encodes 130 bits, while the - // ULID is only 128 bits. - // - // See https://github.com/oklog/ulid/issues/9 for details. - if str[0] > '7' { - return false - } - return true -} - -// IsCreditCard checks if the string is a credit card. -func IsCreditCard(str string) bool { - sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "") - if !rxCreditCard.MatchString(sanitized) { - return false - } - - number, _ := ToInt(sanitized) - number, lastDigit := number / 10, number % 10 - - var sum int64 - for i:=0; number > 0; i++ { - digit := number % 10 - - if i % 2 == 0 { - digit *= 2 - if digit > 9 { - digit -= 9 - } - } - - sum += digit - number = number / 10 - } - - return (sum + lastDigit) % 10 == 0 -} - -// IsISBN10 checks if the string is an ISBN version 10. -func IsISBN10(str string) bool { - return IsISBN(str, 10) -} - -// IsISBN13 checks if the string is an ISBN version 13. -func IsISBN13(str string) bool { - return IsISBN(str, 13) -} - -// IsISBN checks if the string is an ISBN (version 10 or 13). -// If version value is not equal to 10 or 13, it will be checks both variants. -func IsISBN(str string, version int) bool { - sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "") - var checksum int32 - var i int32 - if version == 10 { - if !rxISBN10.MatchString(sanitized) { - return false - } - for i = 0; i < 9; i++ { - checksum += (i + 1) * int32(sanitized[i]-'0') - } - if sanitized[9] == 'X' { - checksum += 10 * 10 - } else { - checksum += 10 * int32(sanitized[9]-'0') - } - if checksum%11 == 0 { - return true - } - return false - } else if version == 13 { - if !rxISBN13.MatchString(sanitized) { - return false - } - factor := []int32{1, 3} - for i = 0; i < 12; i++ { - checksum += factor[i%2] * int32(sanitized[i]-'0') - } - return (int32(sanitized[12]-'0'))-((10-(checksum%10))%10) == 0 - } - return IsISBN(str, 10) || IsISBN(str, 13) -} - -// IsJSON checks if the string is valid JSON (note: uses json.Unmarshal). -func IsJSON(str string) bool { - var js json.RawMessage - return json.Unmarshal([]byte(str), &js) == nil -} - -// IsMultibyte checks if the string contains one or more multibyte chars. Empty string is valid. -func IsMultibyte(str string) bool { - if IsNull(str) { - return true - } - return rxMultibyte.MatchString(str) -} - -// IsASCII checks if the string contains ASCII chars only. Empty string is valid. -func IsASCII(str string) bool { - if IsNull(str) { - return true - } - return rxASCII.MatchString(str) -} - -// IsPrintableASCII checks if the string contains printable ASCII chars only. Empty string is valid. -func IsPrintableASCII(str string) bool { - if IsNull(str) { - return true - } - return rxPrintableASCII.MatchString(str) -} - -// IsFullWidth checks if the string contains any full-width chars. Empty string is valid. -func IsFullWidth(str string) bool { - if IsNull(str) { - return true - } - return rxFullWidth.MatchString(str) -} - -// IsHalfWidth checks if the string contains any half-width chars. Empty string is valid. -func IsHalfWidth(str string) bool { - if IsNull(str) { - return true - } - return rxHalfWidth.MatchString(str) -} - -// IsVariableWidth checks if the string contains a mixture of full and half-width chars. Empty string is valid. -func IsVariableWidth(str string) bool { - if IsNull(str) { - return true - } - return rxHalfWidth.MatchString(str) && rxFullWidth.MatchString(str) -} - -// IsBase64 checks if a string is base64 encoded. -func IsBase64(str string) bool { - return rxBase64.MatchString(str) -} - -// IsFilePath checks is a string is Win or Unix file path and returns it's type. -func IsFilePath(str string) (bool, int) { - if rxWinPath.MatchString(str) { - //check windows path limit see: - // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath - if len(str[3:]) > 32767 { - return false, Win - } - return true, Win - } else if rxUnixPath.MatchString(str) { - return true, Unix - } - return false, Unknown -} - -//IsWinFilePath checks both relative & absolute paths in Windows -func IsWinFilePath(str string) bool { - if rxARWinPath.MatchString(str) { - //check windows path limit see: - // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath - if len(str[3:]) > 32767 { - return false - } - return true - } - return false -} - -//IsUnixFilePath checks both relative & absolute paths in Unix -func IsUnixFilePath(str string) bool { - if rxARUnixPath.MatchString(str) { - return true - } - return false -} - -// IsDataURI checks if a string is base64 encoded data URI such as an image -func IsDataURI(str string) bool { - dataURI := strings.Split(str, ",") - if !rxDataURI.MatchString(dataURI[0]) { - return false - } - return IsBase64(dataURI[1]) -} - -// IsMagnetURI checks if a string is valid magnet URI -func IsMagnetURI(str string) bool { - return rxMagnetURI.MatchString(str) -} - -// IsISO3166Alpha2 checks if a string is valid two-letter country code -func IsISO3166Alpha2(str string) bool { - for _, entry := range ISO3166List { - if str == entry.Alpha2Code { - return true - } - } - return false -} - -// IsISO3166Alpha3 checks if a string is valid three-letter country code -func IsISO3166Alpha3(str string) bool { - for _, entry := range ISO3166List { - if str == entry.Alpha3Code { - return true - } - } - return false -} - -// IsISO693Alpha2 checks if a string is valid two-letter language code -func IsISO693Alpha2(str string) bool { - for _, entry := range ISO693List { - if str == entry.Alpha2Code { - return true - } - } - return false -} - -// IsISO693Alpha3b checks if a string is valid three-letter language code -func IsISO693Alpha3b(str string) bool { - for _, entry := range ISO693List { - if str == entry.Alpha3bCode { - return true - } - } - return false -} - -// IsDNSName will validate the given string as a DNS name -func IsDNSName(str string) bool { - if str == "" || len(strings.Replace(str, ".", "", -1)) > 255 { - // constraints already violated - return false - } - return !IsIP(str) && rxDNSName.MatchString(str) -} - -// IsHash checks if a string is a hash of type algorithm. -// Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b'] -func IsHash(str string, algorithm string) bool { - var len string - algo := strings.ToLower(algorithm) - - if algo == "crc32" || algo == "crc32b" { - len = "8" - } else if algo == "md5" || algo == "md4" || algo == "ripemd128" || algo == "tiger128" { - len = "32" - } else if algo == "sha1" || algo == "ripemd160" || algo == "tiger160" { - len = "40" - } else if algo == "tiger192" { - len = "48" - } else if algo == "sha3-224" { - len = "56" - } else if algo == "sha256" || algo == "sha3-256" { - len = "64" - } else if algo == "sha384" || algo == "sha3-384" { - len = "96" - } else if algo == "sha512" || algo == "sha3-512" { - len = "128" - } else { - return false - } - - return Matches(str, "^[a-f0-9]{"+len+"}$") -} - -// IsSHA3224 checks is a string is a SHA3-224 hash. Alias for `IsHash(str, "sha3-224")` -func IsSHA3224(str string) bool { - return IsHash(str, "sha3-224") -} - -// IsSHA3256 checks is a string is a SHA3-256 hash. Alias for `IsHash(str, "sha3-256")` -func IsSHA3256(str string) bool { - return IsHash(str, "sha3-256") -} - -// IsSHA3384 checks is a string is a SHA3-384 hash. Alias for `IsHash(str, "sha3-384")` -func IsSHA3384(str string) bool { - return IsHash(str, "sha3-384") -} - -// IsSHA3512 checks is a string is a SHA3-512 hash. Alias for `IsHash(str, "sha3-512")` -func IsSHA3512(str string) bool { - return IsHash(str, "sha3-512") -} - -// IsSHA512 checks is a string is a SHA512 hash. Alias for `IsHash(str, "sha512")` -func IsSHA512(str string) bool { - return IsHash(str, "sha512") -} - -// IsSHA384 checks is a string is a SHA384 hash. Alias for `IsHash(str, "sha384")` -func IsSHA384(str string) bool { - return IsHash(str, "sha384") -} - -// IsSHA256 checks is a string is a SHA256 hash. Alias for `IsHash(str, "sha256")` -func IsSHA256(str string) bool { - return IsHash(str, "sha256") -} - -// IsTiger192 checks is a string is a Tiger192 hash. Alias for `IsHash(str, "tiger192")` -func IsTiger192(str string) bool { - return IsHash(str, "tiger192") -} - -// IsTiger160 checks is a string is a Tiger160 hash. Alias for `IsHash(str, "tiger160")` -func IsTiger160(str string) bool { - return IsHash(str, "tiger160") -} - -// IsRipeMD160 checks is a string is a RipeMD160 hash. Alias for `IsHash(str, "ripemd160")` -func IsRipeMD160(str string) bool { - return IsHash(str, "ripemd160") -} - -// IsSHA1 checks is a string is a SHA-1 hash. Alias for `IsHash(str, "sha1")` -func IsSHA1(str string) bool { - return IsHash(str, "sha1") -} - -// IsTiger128 checks is a string is a Tiger128 hash. Alias for `IsHash(str, "tiger128")` -func IsTiger128(str string) bool { - return IsHash(str, "tiger128") -} - -// IsRipeMD128 checks is a string is a RipeMD128 hash. Alias for `IsHash(str, "ripemd128")` -func IsRipeMD128(str string) bool { - return IsHash(str, "ripemd128") -} - -// IsCRC32 checks is a string is a CRC32 hash. Alias for `IsHash(str, "crc32")` -func IsCRC32(str string) bool { - return IsHash(str, "crc32") -} - -// IsCRC32b checks is a string is a CRC32b hash. Alias for `IsHash(str, "crc32b")` -func IsCRC32b(str string) bool { - return IsHash(str, "crc32b") -} - -// IsMD5 checks is a string is a MD5 hash. Alias for `IsHash(str, "md5")` -func IsMD5(str string) bool { - return IsHash(str, "md5") -} - -// IsMD4 checks is a string is a MD4 hash. Alias for `IsHash(str, "md4")` -func IsMD4(str string) bool { - return IsHash(str, "md4") -} - -// IsDialString validates the given string for usage with the various Dial() functions -func IsDialString(str string) bool { - if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) { - return true - } - - return false -} - -// IsIP checks if a string is either IP version 4 or 6. Alias for `net.ParseIP` -func IsIP(str string) bool { - return net.ParseIP(str) != nil -} - -// IsPort checks if a string represents a valid port -func IsPort(str string) bool { - if i, err := strconv.Atoi(str); err == nil && i > 0 && i < 65536 { - return true - } - return false -} - -// IsIPv4 checks if the string is an IP version 4. -func IsIPv4(str string) bool { - ip := net.ParseIP(str) - return ip != nil && strings.Contains(str, ".") -} - -// IsIPv6 checks if the string is an IP version 6. -func IsIPv6(str string) bool { - ip := net.ParseIP(str) - return ip != nil && strings.Contains(str, ":") -} - -// IsCIDR checks if the string is an valid CIDR notiation (IPV4 & IPV6) -func IsCIDR(str string) bool { - _, _, err := net.ParseCIDR(str) - return err == nil -} - -// IsMAC checks if a string is valid MAC address. -// Possible MAC formats: -// 01:23:45:67:89:ab -// 01:23:45:67:89:ab:cd:ef -// 01-23-45-67-89-ab -// 01-23-45-67-89-ab-cd-ef -// 0123.4567.89ab -// 0123.4567.89ab.cdef -func IsMAC(str string) bool { - _, err := net.ParseMAC(str) - return err == nil -} - -// IsHost checks if the string is a valid IP (both v4 and v6) or a valid DNS name -func IsHost(str string) bool { - return IsIP(str) || IsDNSName(str) -} - -// IsMongoID checks if the string is a valid hex-encoded representation of a MongoDB ObjectId. -func IsMongoID(str string) bool { - return rxHexadecimal.MatchString(str) && (len(str) == 24) -} - -// IsLatitude checks if a string is valid latitude. -func IsLatitude(str string) bool { - return rxLatitude.MatchString(str) -} - -// IsLongitude checks if a string is valid longitude. -func IsLongitude(str string) bool { - return rxLongitude.MatchString(str) -} - -// IsIMEI checks if a string is valid IMEI -func IsIMEI(str string) bool { - return rxIMEI.MatchString(str) -} - -// IsIMSI checks if a string is valid IMSI -func IsIMSI(str string) bool { - if !rxIMSI.MatchString(str) { - return false - } - - mcc, err := strconv.ParseInt(str[0:3], 10, 32) - if err != nil { - return false - } - - switch mcc { - case 202, 204, 206, 208, 212, 213, 214, 216, 218, 219: - case 220, 221, 222, 226, 228, 230, 231, 232, 234, 235: - case 238, 240, 242, 244, 246, 247, 248, 250, 255, 257: - case 259, 260, 262, 266, 268, 270, 272, 274, 276, 278: - case 280, 282, 283, 284, 286, 288, 289, 290, 292, 293: - case 294, 295, 297, 302, 308, 310, 311, 312, 313, 314: - case 315, 316, 330, 332, 334, 338, 340, 342, 344, 346: - case 348, 350, 352, 354, 356, 358, 360, 362, 363, 364: - case 365, 366, 368, 370, 372, 374, 376, 400, 401, 402: - case 404, 405, 406, 410, 412, 413, 414, 415, 416, 417: - case 418, 419, 420, 421, 422, 424, 425, 426, 427, 428: - case 429, 430, 431, 432, 434, 436, 437, 438, 440, 441: - case 450, 452, 454, 455, 456, 457, 460, 461, 466, 467: - case 470, 472, 502, 505, 510, 514, 515, 520, 525, 528: - case 530, 536, 537, 539, 540, 541, 542, 543, 544, 545: - case 546, 547, 548, 549, 550, 551, 552, 553, 554, 555: - case 602, 603, 604, 605, 606, 607, 608, 609, 610, 611: - case 612, 613, 614, 615, 616, 617, 618, 619, 620, 621: - case 622, 623, 624, 625, 626, 627, 628, 629, 630, 631: - case 632, 633, 634, 635, 636, 637, 638, 639, 640, 641: - case 642, 643, 645, 646, 647, 648, 649, 650, 651, 652: - case 653, 654, 655, 657, 658, 659, 702, 704, 706, 708: - case 710, 712, 714, 716, 722, 724, 730, 732, 734, 736: - case 738, 740, 742, 744, 746, 748, 750, 995: - return true - default: - return false - } - return true -} - -// IsRsaPublicKey checks if a string is valid public key with provided length -func IsRsaPublicKey(str string, keylen int) bool { - bb := bytes.NewBufferString(str) - pemBytes, err := ioutil.ReadAll(bb) - if err != nil { - return false - } - block, _ := pem.Decode(pemBytes) - if block != nil && block.Type != "PUBLIC KEY" { - return false - } - var der []byte - - if block != nil { - der = block.Bytes - } else { - der, err = base64.StdEncoding.DecodeString(str) - if err != nil { - return false - } - } - - key, err := x509.ParsePKIXPublicKey(der) - if err != nil { - return false - } - pubkey, ok := key.(*rsa.PublicKey) - if !ok { - return false - } - bitlen := len(pubkey.N.Bytes()) * 8 - return bitlen == int(keylen) -} - -// IsRegex checks if a give string is a valid regex with RE2 syntax or not -func IsRegex(str string) bool { - if _, err := regexp.Compile(str); err == nil { - return true - } - return false -} - -func toJSONName(tag string) string { - if tag == "" { - return "" - } - - // JSON name always comes first. If there's no options then split[0] is - // JSON name, if JSON name is not set, then split[0] is an empty string. - split := strings.SplitN(tag, ",", 2) - - name := split[0] - - // However it is possible that the field is skipped when - // (de-)serializing from/to JSON, in which case assume that there is no - // tag name to use - if name == "-" { - return "" - } - return name -} - -func prependPathToErrors(err error, path string) error { - switch err2 := err.(type) { - case Error: - err2.Path = append([]string{path}, err2.Path...) - return err2 - case Errors: - errors := err2.Errors() - for i, err3 := range errors { - errors[i] = prependPathToErrors(err3, path) - } - return err2 - } - return err -} - -// ValidateArray performs validation according to condition iterator that validates every element of the array -func ValidateArray(array []interface{}, iterator ConditionIterator) bool { - return Every(array, iterator) -} - -// ValidateMap use validation map for fields. -// result will be equal to `false` if there are any errors. -// s is the map containing the data to be validated. -// m is the validation map in the form: -// map[string]interface{}{"name":"required,alpha","address":map[string]interface{}{"line1":"required,alphanum"}} -func ValidateMap(s map[string]interface{}, m map[string]interface{}) (bool, error) { - if s == nil { - return true, nil - } - result := true - var err error - var errs Errors - var index int - val := reflect.ValueOf(s) - for key, value := range s { - presentResult := true - validator, ok := m[key] - if !ok { - presentResult = false - var err error - err = fmt.Errorf("all map keys has to be present in the validation map; got %s", key) - err = prependPathToErrors(err, key) - errs = append(errs, err) - } - valueField := reflect.ValueOf(value) - mapResult := true - typeResult := true - structResult := true - resultField := true - switch subValidator := validator.(type) { - case map[string]interface{}: - var err error - if v, ok := value.(map[string]interface{}); !ok { - mapResult = false - err = fmt.Errorf("map validator has to be for the map type only; got %s", valueField.Type().String()) - err = prependPathToErrors(err, key) - errs = append(errs, err) - } else { - mapResult, err = ValidateMap(v, subValidator) - if err != nil { - mapResult = false - err = prependPathToErrors(err, key) - errs = append(errs, err) - } - } - case string: - if (valueField.Kind() == reflect.Struct || - (valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) && - subValidator != "-" { - var err error - structResult, err = ValidateStruct(valueField.Interface()) - if err != nil { - err = prependPathToErrors(err, key) - errs = append(errs, err) - } - } - resultField, err = typeCheck(valueField, reflect.StructField{ - Name: key, - PkgPath: "", - Type: val.Type(), - Tag: reflect.StructTag(fmt.Sprintf("%s:%q", tagName, subValidator)), - Offset: 0, - Index: []int{index}, - Anonymous: false, - }, val, nil) - if err != nil { - errs = append(errs, err) - } - case nil: - // already handlerd when checked before - default: - typeResult = false - err = fmt.Errorf("map validator has to be either map[string]interface{} or string; got %s", valueField.Type().String()) - err = prependPathToErrors(err, key) - errs = append(errs, err) - } - result = result && presentResult && typeResult && resultField && structResult && mapResult - index++ - } - // checks required keys - requiredResult := true - for key, value := range m { - if schema, ok := value.(string); ok { - tags := parseTagIntoMap(schema) - if required, ok := tags["required"]; ok { - if _, ok := s[key]; !ok { - requiredResult = false - if required.customErrorMessage != "" { - err = Error{key, fmt.Errorf(required.customErrorMessage), true, "required", []string{}} - } else { - err = Error{key, fmt.Errorf("required field missing"), false, "required", []string{}} - } - errs = append(errs, err) - } - } - } - } - - if len(errs) > 0 { - err = errs - } - return result && requiredResult, err -} - -// ValidateStruct use tags for fields. -// result will be equal to `false` if there are any errors. -// todo currently there is no guarantee that errors will be returned in predictable order (tests may to fail) -func ValidateStruct(s interface{}) (bool, error) { - if s == nil { - return true, nil - } - result := true - var err error - val := reflect.ValueOf(s) - if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { - val = val.Elem() - } - // we only accept structs - if val.Kind() != reflect.Struct { - return false, fmt.Errorf("function only accepts structs; got %s", val.Kind()) - } - var errs Errors - for i := 0; i < val.NumField(); i++ { - valueField := val.Field(i) - typeField := val.Type().Field(i) - if typeField.PkgPath != "" { - continue // Private field - } - structResult := true - if valueField.Kind() == reflect.Interface { - valueField = valueField.Elem() - } - if (valueField.Kind() == reflect.Struct || - (valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) && - typeField.Tag.Get(tagName) != "-" { - var err error - structResult, err = ValidateStruct(valueField.Interface()) - if err != nil { - err = prependPathToErrors(err, typeField.Name) - errs = append(errs, err) - } - } - resultField, err2 := typeCheck(valueField, typeField, val, nil) - if err2 != nil { - - // Replace structure name with JSON name if there is a tag on the variable - jsonTag := toJSONName(typeField.Tag.Get("json")) - if jsonTag != "" { - switch jsonError := err2.(type) { - case Error: - jsonError.Name = jsonTag - err2 = jsonError - case Errors: - for i2, err3 := range jsonError { - switch customErr := err3.(type) { - case Error: - customErr.Name = jsonTag - jsonError[i2] = customErr - } - } - - err2 = jsonError - } - } - - errs = append(errs, err2) - } - result = result && resultField && structResult - } - if len(errs) > 0 { - err = errs - } - return result, err -} - -// ValidateStructAsync performs async validation of the struct and returns results through the channels -func ValidateStructAsync(s interface{}) (<-chan bool, <-chan error) { - res := make(chan bool) - errors := make(chan error) - - go func() { - defer close(res) - defer close(errors) - - isValid, isFailed := ValidateStruct(s) - - res <- isValid - errors <- isFailed - }() - - return res, errors -} - -// ValidateMapAsync performs async validation of the map and returns results through the channels -func ValidateMapAsync(s map[string]interface{}, m map[string]interface{}) (<-chan bool, <-chan error) { - res := make(chan bool) - errors := make(chan error) - - go func() { - defer close(res) - defer close(errors) - - isValid, isFailed := ValidateMap(s, m) - - res <- isValid - errors <- isFailed - }() - - return res, errors -} - -// parseTagIntoMap parses a struct tag `valid:required~Some error message,length(2|3)` into map[string]string{"required": "Some error message", "length(2|3)": ""} -func parseTagIntoMap(tag string) tagOptionsMap { - optionsMap := make(tagOptionsMap) - options := strings.Split(tag, ",") - - for i, option := range options { - option = strings.TrimSpace(option) - - validationOptions := strings.Split(option, "~") - if !isValidTag(validationOptions[0]) { - continue - } - if len(validationOptions) == 2 { - optionsMap[validationOptions[0]] = tagOption{validationOptions[0], validationOptions[1], i} - } else { - optionsMap[validationOptions[0]] = tagOption{validationOptions[0], "", i} - } - } - return optionsMap -} - -func isValidTag(s string) bool { - if s == "" { - return false - } - for _, c := range s { - switch { - case strings.ContainsRune("\\'\"!#$%&()*+-./:<=>?@[]^_{|}~ ", c): - // Backslash and quote chars are reserved, but - // otherwise any punctuation chars are allowed - // in a tag name. - default: - if !unicode.IsLetter(c) && !unicode.IsDigit(c) { - return false - } - } - } - return true -} - -// IsSSN will validate the given string as a U.S. Social Security Number -func IsSSN(str string) bool { - if str == "" || len(str) != 11 { - return false - } - return rxSSN.MatchString(str) -} - -// IsSemver checks if string is valid semantic version -func IsSemver(str string) bool { - return rxSemver.MatchString(str) -} - -// IsType checks if interface is of some type -func IsType(v interface{}, params ...string) bool { - if len(params) == 1 { - typ := params[0] - return strings.Replace(reflect.TypeOf(v).String(), " ", "", -1) == strings.Replace(typ, " ", "", -1) - } - return false -} - -// IsTime checks if string is valid according to given format -func IsTime(str string, format string) bool { - _, err := time.Parse(format, str) - return err == nil -} - -// IsUnixTime checks if string is valid unix timestamp value -func IsUnixTime(str string) bool { - if _, err := strconv.Atoi(str); err == nil { - return true - } - return false -} - -// IsRFC3339 checks if string is valid timestamp value according to RFC3339 -func IsRFC3339(str string) bool { - return IsTime(str, time.RFC3339) -} - -// IsRFC3339WithoutZone checks if string is valid timestamp value according to RFC3339 which excludes the timezone. -func IsRFC3339WithoutZone(str string) bool { - return IsTime(str, rfc3339WithoutZone) -} - -// IsISO4217 checks if string is valid ISO currency code -func IsISO4217(str string) bool { - for _, currency := range ISO4217List { - if str == currency { - return true - } - } - - return false -} - -// ByteLength checks string's length -func ByteLength(str string, params ...string) bool { - if len(params) == 2 { - min, _ := ToInt(params[0]) - max, _ := ToInt(params[1]) - return len(str) >= int(min) && len(str) <= int(max) - } - - return false -} - -// RuneLength checks string's length -// Alias for StringLength -func RuneLength(str string, params ...string) bool { - return StringLength(str, params...) -} - -// IsRsaPub checks whether string is valid RSA key -// Alias for IsRsaPublicKey -func IsRsaPub(str string, params ...string) bool { - if len(params) == 1 { - len, _ := ToInt(params[0]) - return IsRsaPublicKey(str, int(len)) - } - - return false -} - -// StringMatches checks if a string matches a given pattern. -func StringMatches(s string, params ...string) bool { - if len(params) == 1 { - pattern := params[0] - return Matches(s, pattern) - } - return false -} - -// StringLength checks string's length (including multi byte strings) -func StringLength(str string, params ...string) bool { - - if len(params) == 2 { - strLength := utf8.RuneCountInString(str) - min, _ := ToInt(params[0]) - max, _ := ToInt(params[1]) - return strLength >= int(min) && strLength <= int(max) - } - - return false -} - -// MinStringLength checks string's minimum length (including multi byte strings) -func MinStringLength(str string, params ...string) bool { - - if len(params) == 1 { - strLength := utf8.RuneCountInString(str) - min, _ := ToInt(params[0]) - return strLength >= int(min) - } - - return false -} - -// MaxStringLength checks string's maximum length (including multi byte strings) -func MaxStringLength(str string, params ...string) bool { - - if len(params) == 1 { - strLength := utf8.RuneCountInString(str) - max, _ := ToInt(params[0]) - return strLength <= int(max) - } - - return false -} - -// Range checks string's length -func Range(str string, params ...string) bool { - if len(params) == 2 { - value, _ := ToFloat(str) - min, _ := ToFloat(params[0]) - max, _ := ToFloat(params[1]) - return InRange(value, min, max) - } - - return false -} - -// IsInRaw checks if string is in list of allowed values -func IsInRaw(str string, params ...string) bool { - if len(params) == 1 { - rawParams := params[0] - - parsedParams := strings.Split(rawParams, "|") - - return IsIn(str, parsedParams...) - } - - return false -} - -// IsIn checks if string str is a member of the set of strings params -func IsIn(str string, params ...string) bool { - for _, param := range params { - if str == param { - return true - } - } - - return false -} - -func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) { - if nilPtrAllowedByRequired { - k := v.Kind() - if (k == reflect.Ptr || k == reflect.Interface) && v.IsNil() { - return true, nil - } - } - - if requiredOption, isRequired := options["required"]; isRequired { - if len(requiredOption.customErrorMessage) > 0 { - return false, Error{t.Name, fmt.Errorf(requiredOption.customErrorMessage), true, "required", []string{}} - } - return false, Error{t.Name, fmt.Errorf("non zero value required"), false, "required", []string{}} - } else if _, isOptional := options["optional"]; fieldsRequiredByDefault && !isOptional { - return false, Error{t.Name, fmt.Errorf("Missing required field"), false, "required", []string{}} - } - // not required and empty is valid - return true, nil -} - -func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options tagOptionsMap) (isValid bool, resultErr error) { - if !v.IsValid() { - return false, nil - } - - tag := t.Tag.Get(tagName) - - // checks if the field should be ignored - switch tag { - case "": - if v.Kind() != reflect.Slice && v.Kind() != reflect.Map { - if !fieldsRequiredByDefault { - return true, nil - } - return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false, "required", []string{}} - } - case "-": - return true, nil - } - - isRootType := false - if options == nil { - isRootType = true - options = parseTagIntoMap(tag) - } - - if isEmptyValue(v) { - // an empty value is not validated, checks only required - isValid, resultErr = checkRequired(v, t, options) - for key := range options { - delete(options, key) - } - return isValid, resultErr - } - - var customTypeErrors Errors - optionsOrder := options.orderedKeys() - for _, validatorName := range optionsOrder { - validatorStruct := options[validatorName] - if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok { - delete(options, validatorName) - - if result := validatefunc(v.Interface(), o.Interface()); !result { - if len(validatorStruct.customErrorMessage) > 0 { - customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: TruncatingErrorf(validatorStruct.customErrorMessage, fmt.Sprint(v), validatorName), CustomErrorMessageExists: true, Validator: stripParams(validatorName)}) - continue - } - customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), validatorName), CustomErrorMessageExists: false, Validator: stripParams(validatorName)}) - } - } - } - - if len(customTypeErrors.Errors()) > 0 { - return false, customTypeErrors - } - - if isRootType { - // Ensure that we've checked the value by all specified validators before report that the value is valid - defer func() { - delete(options, "optional") - delete(options, "required") - - if isValid && resultErr == nil && len(options) != 0 { - optionsOrder := options.orderedKeys() - for _, validator := range optionsOrder { - isValid = false - resultErr = Error{t.Name, fmt.Errorf( - "The following validator is invalid or can't be applied to the field: %q", validator), false, stripParams(validator), []string{}} - return - } - } - }() - } - - for _, validatorSpec := range optionsOrder { - validatorStruct := options[validatorSpec] - var negate bool - validator := validatorSpec - customMsgExists := len(validatorStruct.customErrorMessage) > 0 - - // checks whether the tag looks like '!something' or 'something' - if validator[0] == '!' { - validator = validator[1:] - negate = true - } - - // checks for interface param validators - for key, value := range InterfaceParamTagRegexMap { - ps := value.FindStringSubmatch(validator) - if len(ps) == 0 { - continue - } - - validatefunc, ok := InterfaceParamTagMap[key] - if !ok { - continue - } - - delete(options, validatorSpec) - - field := fmt.Sprint(v) - if result := validatefunc(v.Interface(), ps[1:]...); (!result && !negate) || (result && negate) { - if customMsgExists { - return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - if negate { - return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - } - } - - switch v.Kind() { - case reflect.Bool, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, - reflect.Float32, reflect.Float64, - reflect.String: - // for each tag option checks the map of validator functions - for _, validatorSpec := range optionsOrder { - validatorStruct := options[validatorSpec] - var negate bool - validator := validatorSpec - customMsgExists := len(validatorStruct.customErrorMessage) > 0 - - // checks whether the tag looks like '!something' or 'something' - if validator[0] == '!' { - validator = validator[1:] - negate = true - } - - // checks for param validators - for key, value := range ParamTagRegexMap { - ps := value.FindStringSubmatch(validator) - if len(ps) == 0 { - continue - } - - validatefunc, ok := ParamTagMap[key] - if !ok { - continue - } - - delete(options, validatorSpec) - - switch v.Kind() { - case reflect.String, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Float32, reflect.Float64: - - field := fmt.Sprint(v) // make value into string, then validate with regex - if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) { - if customMsgExists { - return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - if negate { - return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - default: - // type not yet supported, fail - return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false, stripParams(validatorSpec), []string{}} - } - } - - if validatefunc, ok := TagMap[validator]; ok { - delete(options, validatorSpec) - - switch v.Kind() { - case reflect.String, - reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, - reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, - reflect.Float32, reflect.Float64: - field := fmt.Sprint(v) // make value into string, then validate with regex - if result := validatefunc(field); !result && !negate || result && negate { - if customMsgExists { - return false, Error{t.Name, TruncatingErrorf(validatorStruct.customErrorMessage, field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - if negate { - return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec), []string{}} - } - default: - //Not Yet Supported Types (Fail here!) - err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", validator, v.Kind(), v) - return false, Error{t.Name, err, false, stripParams(validatorSpec), []string{}} - } - } - } - return true, nil - case reflect.Map: - if v.Type().Key().Kind() != reflect.String { - return false, &UnsupportedTypeError{v.Type()} - } - var sv stringValues - sv = v.MapKeys() - sort.Sort(sv) - result := true - for i, k := range sv { - var resultItem bool - var err error - if v.MapIndex(k).Kind() != reflect.Struct { - resultItem, err = typeCheck(v.MapIndex(k), t, o, options) - if err != nil { - return false, err - } - } else { - resultItem, err = ValidateStruct(v.MapIndex(k).Interface()) - if err != nil { - err = prependPathToErrors(err, t.Name+"."+sv[i].Interface().(string)) - return false, err - } - } - result = result && resultItem - } - return result, nil - case reflect.Slice, reflect.Array: - result := true - for i := 0; i < v.Len(); i++ { - var resultItem bool - var err error - if v.Index(i).Kind() != reflect.Struct { - resultItem, err = typeCheck(v.Index(i), t, o, options) - if err != nil { - return false, err - } - } else { - resultItem, err = ValidateStruct(v.Index(i).Interface()) - if err != nil { - err = prependPathToErrors(err, t.Name+"."+strconv.Itoa(i)) - return false, err - } - } - result = result && resultItem - } - return result, nil - case reflect.Interface: - // If the value is an interface then encode its element - if v.IsNil() { - return true, nil - } - return ValidateStruct(v.Interface()) - case reflect.Ptr: - // If the value is a pointer then checks its element - if v.IsNil() { - return true, nil - } - return typeCheck(v.Elem(), t, o, options) - case reflect.Struct: - return true, nil - default: - return false, &UnsupportedTypeError{v.Type()} - } -} - -func stripParams(validatorString string) string { - return paramsRegexp.ReplaceAllString(validatorString, "") -} - -// isEmptyValue checks whether value empty or not -func isEmptyValue(v reflect.Value) bool { - switch v.Kind() { - case reflect.String, reflect.Array: - return v.Len() == 0 - case reflect.Map, reflect.Slice: - return v.Len() == 0 || v.IsNil() - case reflect.Bool: - return !v.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return v.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return v.Uint() == 0 - case reflect.Float32, reflect.Float64: - return v.Float() == 0 - case reflect.Interface, reflect.Ptr: - return v.IsNil() - } - - return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) -} - -// ErrorByField returns error for specified field of the struct -// validated by ValidateStruct or empty string if there are no errors -// or this field doesn't exists or doesn't have any errors. -func ErrorByField(e error, field string) string { - if e == nil { - return "" - } - return ErrorsByField(e)[field] -} - -// ErrorsByField returns map of errors of the struct validated -// by ValidateStruct or empty map if there are no errors. -func ErrorsByField(e error) map[string]string { - m := make(map[string]string) - if e == nil { - return m - } - // prototype for ValidateStruct - - switch e := e.(type) { - case Error: - m[e.Name] = e.Err.Error() - case Errors: - for _, item := range e.Errors() { - n := ErrorsByField(item) - for k, v := range n { - m[k] = v - } - } - } - - return m -} - -// Error returns string equivalent for reflect.Type -func (e *UnsupportedTypeError) Error() string { - return "validator: unsupported type: " + e.Type.String() -} - -func (sv stringValues) Len() int { return len(sv) } -func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } -func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) } -func (sv stringValues) get(i int) string { return sv[i].String() } - -func IsE164(str string) bool { - return rxE164.MatchString(str) -} diff --git a/vendor/github.com/asaskevich/govalidator/wercker.yml b/vendor/github.com/asaskevich/govalidator/wercker.yml deleted file mode 100644 index bc5f7b0864..0000000000 --- a/vendor/github.com/asaskevich/govalidator/wercker.yml +++ /dev/null @@ -1,15 +0,0 @@ -box: golang -build: - steps: - - setup-go-workspace - - - script: - name: go get - code: | - go version - go get -t ./... - - - script: - name: go test - code: | - go test -race -v ./... diff --git a/vendor/github.com/cenkalti/backoff/v4/context.go b/vendor/github.com/cenkalti/backoff/v4/context.go deleted file mode 100644 index 48482330eb..0000000000 --- a/vendor/github.com/cenkalti/backoff/v4/context.go +++ /dev/null @@ -1,62 +0,0 @@ -package backoff - -import ( - "context" - "time" -) - -// BackOffContext is a backoff policy that stops retrying after the context -// is canceled. -type BackOffContext interface { // nolint: golint - BackOff - Context() context.Context -} - -type backOffContext struct { - BackOff - ctx context.Context -} - -// WithContext returns a BackOffContext with context ctx -// -// ctx must not be nil -func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint - if ctx == nil { - panic("nil context") - } - - if b, ok := b.(*backOffContext); ok { - return &backOffContext{ - BackOff: b.BackOff, - ctx: ctx, - } - } - - return &backOffContext{ - BackOff: b, - ctx: ctx, - } -} - -func getContext(b BackOff) context.Context { - if cb, ok := b.(BackOffContext); ok { - return cb.Context() - } - if tb, ok := b.(*backOffTries); ok { - return getContext(tb.delegate) - } - return context.Background() -} - -func (b *backOffContext) Context() context.Context { - return b.ctx -} - -func (b *backOffContext) NextBackOff() time.Duration { - select { - case <-b.ctx.Done(): - return Stop - default: - return b.BackOff.NextBackOff() - } -} diff --git a/vendor/github.com/cenkalti/backoff/v4/exponential.go b/vendor/github.com/cenkalti/backoff/v4/exponential.go deleted file mode 100644 index aac99f196a..0000000000 --- a/vendor/github.com/cenkalti/backoff/v4/exponential.go +++ /dev/null @@ -1,216 +0,0 @@ -package backoff - -import ( - "math/rand" - "time" -) - -/* -ExponentialBackOff is a backoff implementation that increases the backoff -period for each retry attempt using a randomization function that grows exponentially. - -NextBackOff() is calculated using the following formula: - - randomized interval = - RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) - -In other words NextBackOff() will range between the randomization factor -percentage below and above the retry interval. - -For example, given the following parameters: - - RetryInterval = 2 - RandomizationFactor = 0.5 - Multiplier = 2 - -the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, -multiplied by the exponential, that is, between 2 and 6 seconds. - -Note: MaxInterval caps the RetryInterval and not the randomized interval. - -If the time elapsed since an ExponentialBackOff instance is created goes past the -MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop. - -The elapsed time can be reset by calling Reset(). - -Example: Given the following default arguments, for 10 tries the sequence will be, -and assuming we go over the MaxElapsedTime on the 10th try: - - Request # RetryInterval (seconds) Randomized Interval (seconds) - - 1 0.5 [0.25, 0.75] - 2 0.75 [0.375, 1.125] - 3 1.125 [0.562, 1.687] - 4 1.687 [0.8435, 2.53] - 5 2.53 [1.265, 3.795] - 6 3.795 [1.897, 5.692] - 7 5.692 [2.846, 8.538] - 8 8.538 [4.269, 12.807] - 9 12.807 [6.403, 19.210] - 10 19.210 backoff.Stop - -Note: Implementation is not thread-safe. -*/ -type ExponentialBackOff struct { - InitialInterval time.Duration - RandomizationFactor float64 - Multiplier float64 - MaxInterval time.Duration - // After MaxElapsedTime the ExponentialBackOff returns Stop. - // It never stops if MaxElapsedTime == 0. - MaxElapsedTime time.Duration - Stop time.Duration - Clock Clock - - currentInterval time.Duration - startTime time.Time -} - -// Clock is an interface that returns current time for BackOff. -type Clock interface { - Now() time.Time -} - -// ExponentialBackOffOpts is a function type used to configure ExponentialBackOff options. -type ExponentialBackOffOpts func(*ExponentialBackOff) - -// Default values for ExponentialBackOff. -const ( - DefaultInitialInterval = 500 * time.Millisecond - DefaultRandomizationFactor = 0.5 - DefaultMultiplier = 1.5 - DefaultMaxInterval = 60 * time.Second - DefaultMaxElapsedTime = 15 * time.Minute -) - -// NewExponentialBackOff creates an instance of ExponentialBackOff using default values. -func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff { - b := &ExponentialBackOff{ - InitialInterval: DefaultInitialInterval, - RandomizationFactor: DefaultRandomizationFactor, - Multiplier: DefaultMultiplier, - MaxInterval: DefaultMaxInterval, - MaxElapsedTime: DefaultMaxElapsedTime, - Stop: Stop, - Clock: SystemClock, - } - for _, fn := range opts { - fn(b) - } - b.Reset() - return b -} - -// WithInitialInterval sets the initial interval between retries. -func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.InitialInterval = duration - } -} - -// WithRandomizationFactor sets the randomization factor to add jitter to intervals. -func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.RandomizationFactor = randomizationFactor - } -} - -// WithMultiplier sets the multiplier for increasing the interval after each retry. -func WithMultiplier(multiplier float64) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.Multiplier = multiplier - } -} - -// WithMaxInterval sets the maximum interval between retries. -func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.MaxInterval = duration - } -} - -// WithMaxElapsedTime sets the maximum total time for retries. -func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.MaxElapsedTime = duration - } -} - -// WithRetryStopDuration sets the duration after which retries should stop. -func WithRetryStopDuration(duration time.Duration) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.Stop = duration - } -} - -// WithClockProvider sets the clock used to measure time. -func WithClockProvider(clock Clock) ExponentialBackOffOpts { - return func(ebo *ExponentialBackOff) { - ebo.Clock = clock - } -} - -type systemClock struct{} - -func (t systemClock) Now() time.Time { - return time.Now() -} - -// SystemClock implements Clock interface that uses time.Now(). -var SystemClock = systemClock{} - -// Reset the interval back to the initial retry interval and restarts the timer. -// Reset must be called before using b. -func (b *ExponentialBackOff) Reset() { - b.currentInterval = b.InitialInterval - b.startTime = b.Clock.Now() -} - -// NextBackOff calculates the next backoff interval using the formula: -// Randomized interval = RetryInterval * (1 ± RandomizationFactor) -func (b *ExponentialBackOff) NextBackOff() time.Duration { - // Make sure we have not gone over the maximum elapsed time. - elapsed := b.GetElapsedTime() - next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) - b.incrementCurrentInterval() - if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime { - return b.Stop - } - return next -} - -// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance -// is created and is reset when Reset() is called. -// -// The elapsed time is computed using time.Now().UnixNano(). It is -// safe to call even while the backoff policy is used by a running -// ticker. -func (b *ExponentialBackOff) GetElapsedTime() time.Duration { - return b.Clock.Now().Sub(b.startTime) -} - -// Increments the current interval by multiplying it with the multiplier. -func (b *ExponentialBackOff) incrementCurrentInterval() { - // Check for overflow, if overflow is detected set the current interval to the max interval. - if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { - b.currentInterval = b.MaxInterval - } else { - b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) - } -} - -// Returns a random value from the following interval: -// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval]. -func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { - if randomizationFactor == 0 { - return currentInterval // make sure no randomness is used when randomizationFactor is 0. - } - var delta = randomizationFactor * float64(currentInterval) - var minInterval = float64(currentInterval) - delta - var maxInterval = float64(currentInterval) + delta - - // Get a random value from the range [minInterval, maxInterval]. - // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then - // we want a 33% chance for selecting either 1, 2 or 3. - return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) -} diff --git a/vendor/github.com/cenkalti/backoff/v4/retry.go b/vendor/github.com/cenkalti/backoff/v4/retry.go deleted file mode 100644 index b9c0c51cd7..0000000000 --- a/vendor/github.com/cenkalti/backoff/v4/retry.go +++ /dev/null @@ -1,146 +0,0 @@ -package backoff - -import ( - "errors" - "time" -) - -// An OperationWithData is executing by RetryWithData() or RetryNotifyWithData(). -// The operation will be retried using a backoff policy if it returns an error. -type OperationWithData[T any] func() (T, error) - -// An Operation is executing by Retry() or RetryNotify(). -// The operation will be retried using a backoff policy if it returns an error. -type Operation func() error - -func (o Operation) withEmptyData() OperationWithData[struct{}] { - return func() (struct{}, error) { - return struct{}{}, o() - } -} - -// Notify is a notify-on-error function. It receives an operation error and -// backoff delay if the operation failed (with an error). -// -// NOTE that if the backoff policy stated to stop retrying, -// the notify function isn't called. -type Notify func(error, time.Duration) - -// Retry the operation o until it does not return error or BackOff stops. -// o is guaranteed to be run at least once. -// -// If o returns a *PermanentError, the operation is not retried, and the -// wrapped error is returned. -// -// Retry sleeps the goroutine for the duration returned by BackOff after a -// failed operation returns. -func Retry(o Operation, b BackOff) error { - return RetryNotify(o, b, nil) -} - -// RetryWithData is like Retry but returns data in the response too. -func RetryWithData[T any](o OperationWithData[T], b BackOff) (T, error) { - return RetryNotifyWithData(o, b, nil) -} - -// RetryNotify calls notify function with the error and wait duration -// for each failed attempt before sleep. -func RetryNotify(operation Operation, b BackOff, notify Notify) error { - return RetryNotifyWithTimer(operation, b, notify, nil) -} - -// RetryNotifyWithData is like RetryNotify but returns data in the response too. -func RetryNotifyWithData[T any](operation OperationWithData[T], b BackOff, notify Notify) (T, error) { - return doRetryNotify(operation, b, notify, nil) -} - -// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer -// for each failed attempt before sleep. -// A default timer that uses system timer is used when nil is passed. -func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error { - _, err := doRetryNotify(operation.withEmptyData(), b, notify, t) - return err -} - -// RetryNotifyWithTimerAndData is like RetryNotifyWithTimer but returns data in the response too. -func RetryNotifyWithTimerAndData[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) { - return doRetryNotify(operation, b, notify, t) -} - -func doRetryNotify[T any](operation OperationWithData[T], b BackOff, notify Notify, t Timer) (T, error) { - var ( - err error - next time.Duration - res T - ) - if t == nil { - t = &defaultTimer{} - } - - defer func() { - t.Stop() - }() - - ctx := getContext(b) - - b.Reset() - for { - res, err = operation() - if err == nil { - return res, nil - } - - var permanent *PermanentError - if errors.As(err, &permanent) { - return res, permanent.Err - } - - if next = b.NextBackOff(); next == Stop { - if cerr := ctx.Err(); cerr != nil { - return res, cerr - } - - return res, err - } - - if notify != nil { - notify(err, next) - } - - t.Start(next) - - select { - case <-ctx.Done(): - return res, ctx.Err() - case <-t.C(): - } - } -} - -// PermanentError signals that the operation should not be retried. -type PermanentError struct { - Err error -} - -func (e *PermanentError) Error() string { - return e.Err.Error() -} - -func (e *PermanentError) Unwrap() error { - return e.Err -} - -func (e *PermanentError) Is(target error) bool { - _, ok := target.(*PermanentError) - return ok -} - -// Permanent wraps the given err in a *PermanentError. -func Permanent(err error) error { - if err == nil { - return nil - } - return &PermanentError{ - Err: err, - } -} diff --git a/vendor/github.com/cenkalti/backoff/v4/tries.go b/vendor/github.com/cenkalti/backoff/v4/tries.go deleted file mode 100644 index 28d58ca37c..0000000000 --- a/vendor/github.com/cenkalti/backoff/v4/tries.go +++ /dev/null @@ -1,38 +0,0 @@ -package backoff - -import "time" - -/* -WithMaxRetries creates a wrapper around another BackOff, which will -return Stop if NextBackOff() has been called too many times since -the last time Reset() was called - -Note: Implementation is not thread-safe. -*/ -func WithMaxRetries(b BackOff, max uint64) BackOff { - return &backOffTries{delegate: b, maxTries: max} -} - -type backOffTries struct { - delegate BackOff - maxTries uint64 - numTries uint64 -} - -func (b *backOffTries) NextBackOff() time.Duration { - if b.maxTries == 0 { - return Stop - } - if b.maxTries > 0 { - if b.maxTries <= b.numTries { - return Stop - } - b.numTries++ - } - return b.delegate.NextBackOff() -} - -func (b *backOffTries) Reset() { - b.numTries = 0 - b.delegate.Reset() -} diff --git a/vendor/github.com/cenkalti/backoff/v4/.gitignore b/vendor/github.com/cenkalti/backoff/v5/.gitignore similarity index 100% rename from vendor/github.com/cenkalti/backoff/v4/.gitignore rename to vendor/github.com/cenkalti/backoff/v5/.gitignore diff --git a/vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md b/vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md new file mode 100644 index 0000000000..658c37436d --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md @@ -0,0 +1,29 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [5.0.0] - 2024-12-19 + +### Added + +- RetryAfterError can be returned from an operation to indicate how long to wait before the next retry. + +### Changed + +- Retry function now accepts additional options for specifying max number of tries and max elapsed time. +- Retry function now accepts a context.Context. +- Operation function signature changed to return result (any type) and error. + +### Removed + +- RetryNotify* and RetryWithData functions. Only single Retry function remains. +- Optional arguments from ExponentialBackoff constructor. +- Clock and Timer interfaces. + +### Fixed + +- The original error is returned from Retry if there's a PermanentError. (#144) +- The Retry function respects the wrapped PermanentError. (#140) diff --git a/vendor/github.com/cenkalti/backoff/v4/LICENSE b/vendor/github.com/cenkalti/backoff/v5/LICENSE similarity index 100% rename from vendor/github.com/cenkalti/backoff/v4/LICENSE rename to vendor/github.com/cenkalti/backoff/v5/LICENSE diff --git a/vendor/github.com/cenkalti/backoff/v4/README.md b/vendor/github.com/cenkalti/backoff/v5/README.md similarity index 64% rename from vendor/github.com/cenkalti/backoff/v4/README.md rename to vendor/github.com/cenkalti/backoff/v5/README.md index 9433004a28..4611b1d170 100644 --- a/vendor/github.com/cenkalti/backoff/v4/README.md +++ b/vendor/github.com/cenkalti/backoff/v5/README.md @@ -1,4 +1,4 @@ -# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Coverage Status][coveralls image]][coveralls] +# Exponential Backoff [![GoDoc][godoc image]][godoc] This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client]. @@ -9,9 +9,11 @@ The retries exponentially increase and stop increasing when a certain threshold ## Usage -Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end. +Import path is `github.com/cenkalti/backoff/v5`. Please note the version part at the end. -Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation. +For most cases, use `Retry` function. See [example_test.go][example] for an example. + +If you have specific needs, copy `Retry` function (from [retry.go][retry-src]) into your code and modify it as needed. ## Contributing @@ -19,12 +21,11 @@ Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation. * Please don't send a PR without opening an issue and discussing it first. * If proposed change is not a common use case, I will probably not accept it. -[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4 +[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v5 [godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png -[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master -[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master [google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java [exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff -[advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples +[retry-src]: https://github.com/cenkalti/backoff/blob/v5/retry.go +[example]: https://github.com/cenkalti/backoff/blob/v5/example_test.go diff --git a/vendor/github.com/cenkalti/backoff/v4/backoff.go b/vendor/github.com/cenkalti/backoff/v5/backoff.go similarity index 87% rename from vendor/github.com/cenkalti/backoff/v4/backoff.go rename to vendor/github.com/cenkalti/backoff/v5/backoff.go index 3676ee405d..dd2b24ca73 100644 --- a/vendor/github.com/cenkalti/backoff/v4/backoff.go +++ b/vendor/github.com/cenkalti/backoff/v5/backoff.go @@ -15,16 +15,16 @@ import "time" // BackOff is a backoff policy for retrying an operation. type BackOff interface { // NextBackOff returns the duration to wait before retrying the operation, - // or backoff. Stop to indicate that no more retries should be made. + // backoff.Stop to indicate that no more retries should be made. // // Example usage: // - // duration := backoff.NextBackOff(); - // if (duration == backoff.Stop) { - // // Do not retry operation. - // } else { - // // Sleep for duration and retry operation. - // } + // duration := backoff.NextBackOff() + // if duration == backoff.Stop { + // // Do not retry operation. + // } else { + // // Sleep for duration and retry operation. + // } // NextBackOff() time.Duration diff --git a/vendor/github.com/cenkalti/backoff/v5/error.go b/vendor/github.com/cenkalti/backoff/v5/error.go new file mode 100644 index 0000000000..beb2b38a23 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/v5/error.go @@ -0,0 +1,46 @@ +package backoff + +import ( + "fmt" + "time" +) + +// PermanentError signals that the operation should not be retried. +type PermanentError struct { + Err error +} + +// Permanent wraps the given err in a *PermanentError. +func Permanent(err error) error { + if err == nil { + return nil + } + return &PermanentError{ + Err: err, + } +} + +// Error returns a string representation of the Permanent error. +func (e *PermanentError) Error() string { + return e.Err.Error() +} + +// Unwrap returns the wrapped error. +func (e *PermanentError) Unwrap() error { + return e.Err +} + +// RetryAfterError signals that the operation should be retried after the given duration. +type RetryAfterError struct { + Duration time.Duration +} + +// RetryAfter returns a RetryAfter error that specifies how long to wait before retrying. +func RetryAfter(seconds int) error { + return &RetryAfterError{Duration: time.Duration(seconds) * time.Second} +} + +// Error returns a string representation of the RetryAfter error. +func (e *RetryAfterError) Error() string { + return fmt.Sprintf("retry after %s", e.Duration) +} diff --git a/vendor/github.com/cenkalti/backoff/v5/exponential.go b/vendor/github.com/cenkalti/backoff/v5/exponential.go new file mode 100644 index 0000000000..79d425e874 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/v5/exponential.go @@ -0,0 +1,118 @@ +package backoff + +import ( + "math/rand/v2" + "time" +) + +/* +ExponentialBackOff is a backoff implementation that increases the backoff +period for each retry attempt using a randomization function that grows exponentially. + +NextBackOff() is calculated using the following formula: + + randomized interval = + RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) + +In other words NextBackOff() will range between the randomization factor +percentage below and above the retry interval. + +For example, given the following parameters: + + RetryInterval = 2 + RandomizationFactor = 0.5 + Multiplier = 2 + +the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, +multiplied by the exponential, that is, between 2 and 6 seconds. + +Note: MaxInterval caps the RetryInterval and not the randomized interval. + +Example: Given the following default arguments, for 9 tries the sequence will be: + + Request # RetryInterval (seconds) Randomized Interval (seconds) + + 1 0.5 [0.25, 0.75] + 2 0.75 [0.375, 1.125] + 3 1.125 [0.562, 1.687] + 4 1.687 [0.8435, 2.53] + 5 2.53 [1.265, 3.795] + 6 3.795 [1.897, 5.692] + 7 5.692 [2.846, 8.538] + 8 8.538 [4.269, 12.807] + 9 12.807 [6.403, 19.210] + +Note: Implementation is not thread-safe. +*/ +type ExponentialBackOff struct { + InitialInterval time.Duration + RandomizationFactor float64 + Multiplier float64 + MaxInterval time.Duration + + currentInterval time.Duration +} + +// Default values for ExponentialBackOff. +const ( + DefaultInitialInterval = 500 * time.Millisecond + DefaultRandomizationFactor = 0.5 + DefaultMultiplier = 1.5 + DefaultMaxInterval = 60 * time.Second +) + +// NewExponentialBackOff creates an instance of ExponentialBackOff using default values. +func NewExponentialBackOff() *ExponentialBackOff { + return &ExponentialBackOff{ + InitialInterval: DefaultInitialInterval, + RandomizationFactor: DefaultRandomizationFactor, + Multiplier: DefaultMultiplier, + MaxInterval: DefaultMaxInterval, + } +} + +// Reset the interval back to the initial retry interval and restarts the timer. +// Reset must be called before using b. +func (b *ExponentialBackOff) Reset() { + b.currentInterval = b.InitialInterval +} + +// NextBackOff calculates the next backoff interval using the formula: +// +// Randomized interval = RetryInterval * (1 ± RandomizationFactor) +func (b *ExponentialBackOff) NextBackOff() time.Duration { + if b.currentInterval == 0 { + b.currentInterval = b.InitialInterval + } + + next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval) + b.incrementCurrentInterval() + return next +} + +// Increments the current interval by multiplying it with the multiplier. +func (b *ExponentialBackOff) incrementCurrentInterval() { + // Check for overflow, if overflow is detected set the current interval to the max interval. + if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier { + b.currentInterval = b.MaxInterval + } else { + b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier) + } +} + +// Returns a random value from the following interval: +// +// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval]. +func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { + if randomizationFactor == 0 { + return currentInterval // make sure no randomness is used when randomizationFactor is 0. + } + var delta = randomizationFactor * float64(currentInterval) + var minInterval = float64(currentInterval) - delta + var maxInterval = float64(currentInterval) + delta + + // Get a random value from the range [minInterval, maxInterval]. + // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then + // we want a 33% chance for selecting either 1, 2 or 3. + return time.Duration(minInterval + (random * (maxInterval - minInterval + 1))) +} diff --git a/vendor/github.com/cenkalti/backoff/v5/retry.go b/vendor/github.com/cenkalti/backoff/v5/retry.go new file mode 100644 index 0000000000..32a7f98834 --- /dev/null +++ b/vendor/github.com/cenkalti/backoff/v5/retry.go @@ -0,0 +1,139 @@ +package backoff + +import ( + "context" + "errors" + "time" +) + +// DefaultMaxElapsedTime sets a default limit for the total retry duration. +const DefaultMaxElapsedTime = 15 * time.Minute + +// Operation is a function that attempts an operation and may be retried. +type Operation[T any] func() (T, error) + +// Notify is a function called on operation error with the error and backoff duration. +type Notify func(error, time.Duration) + +// retryOptions holds configuration settings for the retry mechanism. +type retryOptions struct { + BackOff BackOff // Strategy for calculating backoff periods. + Timer timer // Timer to manage retry delays. + Notify Notify // Optional function to notify on each retry error. + MaxTries uint // Maximum number of retry attempts. + MaxElapsedTime time.Duration // Maximum total time for all retries. +} + +type RetryOption func(*retryOptions) + +// WithBackOff configures a custom backoff strategy. +func WithBackOff(b BackOff) RetryOption { + return func(args *retryOptions) { + args.BackOff = b + } +} + +// withTimer sets a custom timer for managing delays between retries. +func withTimer(t timer) RetryOption { + return func(args *retryOptions) { + args.Timer = t + } +} + +// WithNotify sets a notification function to handle retry errors. +func WithNotify(n Notify) RetryOption { + return func(args *retryOptions) { + args.Notify = n + } +} + +// WithMaxTries limits the number of all attempts. +func WithMaxTries(n uint) RetryOption { + return func(args *retryOptions) { + args.MaxTries = n + } +} + +// WithMaxElapsedTime limits the total duration for retry attempts. +func WithMaxElapsedTime(d time.Duration) RetryOption { + return func(args *retryOptions) { + args.MaxElapsedTime = d + } +} + +// Retry attempts the operation until success, a permanent error, or backoff completion. +// It ensures the operation is executed at least once. +// +// Returns the operation result or error if retries are exhausted or context is cancelled. +func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOption) (T, error) { + // Initialize default retry options. + args := &retryOptions{ + BackOff: NewExponentialBackOff(), + Timer: &defaultTimer{}, + MaxElapsedTime: DefaultMaxElapsedTime, + } + + // Apply user-provided options to the default settings. + for _, opt := range opts { + opt(args) + } + + defer args.Timer.Stop() + + startedAt := time.Now() + args.BackOff.Reset() + for numTries := uint(1); ; numTries++ { + // Execute the operation. + res, err := operation() + if err == nil { + return res, nil + } + + // Stop retrying if maximum tries exceeded. + if args.MaxTries > 0 && numTries >= args.MaxTries { + return res, err + } + + // Handle permanent errors without retrying. + var permanent *PermanentError + if errors.As(err, &permanent) { + return res, permanent.Unwrap() + } + + // Stop retrying if context is cancelled. + if cerr := context.Cause(ctx); cerr != nil { + return res, cerr + } + + // Calculate next backoff duration. + next := args.BackOff.NextBackOff() + if next == Stop { + return res, err + } + + // Reset backoff if RetryAfterError is encountered. + var retryAfter *RetryAfterError + if errors.As(err, &retryAfter) { + next = retryAfter.Duration + args.BackOff.Reset() + } + + // Stop retrying if maximum elapsed time exceeded. + if args.MaxElapsedTime > 0 && time.Since(startedAt)+next > args.MaxElapsedTime { + return res, err + } + + // Notify on error if a notifier function is provided. + if args.Notify != nil { + args.Notify(err, next) + } + + // Wait for the next backoff period or context cancellation. + args.Timer.Start(next) + select { + case <-args.Timer.C(): + case <-ctx.Done(): + return res, context.Cause(ctx) + } + } +} diff --git a/vendor/github.com/cenkalti/backoff/v4/ticker.go b/vendor/github.com/cenkalti/backoff/v5/ticker.go similarity index 80% rename from vendor/github.com/cenkalti/backoff/v4/ticker.go rename to vendor/github.com/cenkalti/backoff/v5/ticker.go index df9d68bce5..f0d4b2ae72 100644 --- a/vendor/github.com/cenkalti/backoff/v4/ticker.go +++ b/vendor/github.com/cenkalti/backoff/v5/ticker.go @@ -1,7 +1,6 @@ package backoff import ( - "context" "sync" "time" ) @@ -14,8 +13,7 @@ type Ticker struct { C <-chan time.Time c chan time.Time b BackOff - ctx context.Context - timer Timer + timer timer stop chan struct{} stopOnce sync.Once } @@ -27,22 +25,12 @@ type Ticker struct { // provided backoff policy (notably calling NextBackOff or Reset) // while the ticker is running. func NewTicker(b BackOff) *Ticker { - return NewTickerWithTimer(b, &defaultTimer{}) -} - -// NewTickerWithTimer returns a new Ticker with a custom timer. -// A default timer that uses system timer is used when nil is passed. -func NewTickerWithTimer(b BackOff, timer Timer) *Ticker { - if timer == nil { - timer = &defaultTimer{} - } c := make(chan time.Time) t := &Ticker{ C: c, c: c, b: b, - ctx: getContext(b), - timer: timer, + timer: &defaultTimer{}, stop: make(chan struct{}), } t.b.Reset() @@ -73,8 +61,6 @@ func (t *Ticker) run() { case <-t.stop: t.c = nil // Prevent future ticks from being sent to the channel. return - case <-t.ctx.Done(): - return } } } diff --git a/vendor/github.com/cenkalti/backoff/v4/timer.go b/vendor/github.com/cenkalti/backoff/v5/timer.go similarity index 96% rename from vendor/github.com/cenkalti/backoff/v4/timer.go rename to vendor/github.com/cenkalti/backoff/v5/timer.go index 8120d0213c..a895309747 100644 --- a/vendor/github.com/cenkalti/backoff/v4/timer.go +++ b/vendor/github.com/cenkalti/backoff/v5/timer.go @@ -2,7 +2,7 @@ package backoff import "time" -type Timer interface { +type timer interface { Start(duration time.Duration) Stop() C() <-chan time.Time diff --git a/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go b/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go index 5a939100d2..1f165141a9 100644 --- a/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go +++ b/vendor/github.com/cloudflare/circl/ecc/goldilocks/curve.go @@ -18,6 +18,9 @@ func (Curve) Identity() *Point { func (Curve) IsOnCurve(P *Point) bool { x2, y2, t, t2, z2 := &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{}, &fp.Elt{} rhs, lhs := &fp.Elt{}, &fp.Elt{} + // Check z != 0 + eq0 := !fp.IsZero(&P.z) + fp.Mul(t, &P.ta, &P.tb) // t = ta*tb fp.Sqr(x2, &P.x) // x^2 fp.Sqr(y2, &P.y) // y^2 @@ -27,13 +30,14 @@ func (Curve) IsOnCurve(P *Point) bool { fp.Mul(rhs, t2, ¶mD) // dt^2 fp.Add(rhs, rhs, z2) // z^2 + dt^2 fp.Sub(lhs, lhs, rhs) // x^2 + y^2 - (z^2 + dt^2) - eq0 := fp.IsZero(lhs) + eq1 := fp.IsZero(lhs) fp.Mul(lhs, &P.x, &P.y) // xy fp.Mul(rhs, t, &P.z) // tz fp.Sub(lhs, lhs, rhs) // xy - tz - eq1 := fp.IsZero(lhs) - return eq0 && eq1 + eq2 := fp.IsZero(lhs) + + return eq0 && eq1 && eq2 } // Generator returns the generator point. diff --git a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go index 052fc8d32d..0910613465 100644 --- a/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go +++ b/vendor/github.com/cloudflare/circl/internal/sha3/xor_unaligned.go @@ -14,14 +14,14 @@ import "unsafe" type storageBuf [maxRate / 8]uint64 func (b *storageBuf) asBytes() *[maxRate]byte { - return (*[maxRate]byte)(unsafe.Pointer(b)) + return (*[maxRate]byte)(unsafe.Pointer(b)) //nolint:gosec } // xorInuses unaligned reads and writes to update d.a to contain d.a // XOR buf. func xorIn(d *State, buf []byte) { n := len(buf) - bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] + bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8] //nolint:gosec if n >= 72 { d.a[0] ^= bw[0] d.a[1] ^= bw[1] @@ -56,6 +56,6 @@ func xorIn(d *State, buf []byte) { } func copyOut(d *State, buf []byte) { - ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) + ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0])) //nolint:gosec copy(buf, ab[:]) } diff --git a/vendor/github.com/cloudflare/circl/sign/ed25519/point.go b/vendor/github.com/cloudflare/circl/sign/ed25519/point.go index 374a69503c..d1c3b146b7 100644 --- a/vendor/github.com/cloudflare/circl/sign/ed25519/point.go +++ b/vendor/github.com/cloudflare/circl/sign/ed25519/point.go @@ -164,7 +164,7 @@ func (P *pointR1) isEqual(Q *pointR1) bool { fp.Mul(r, r, &P.z) fp.Sub(l, l, r) b = b && fp.IsZero(l) - return b + return b && !fp.IsZero(&P.z) && !fp.IsZero(&Q.z) } func (P *pointR3) neg() { diff --git a/vendor/github.com/cloudflare/circl/sign/sign.go b/vendor/github.com/cloudflare/circl/sign/sign.go index 557d6f0960..1247f1b626 100644 --- a/vendor/github.com/cloudflare/circl/sign/sign.go +++ b/vendor/github.com/cloudflare/circl/sign/sign.go @@ -38,6 +38,12 @@ type PrivateKey interface { encoding.BinaryMarshaler } +// A private key that retains the seed with which it was generated. +type Seeded interface { + // returns the seed if retained, otherwise nil + Seed() []byte +} + // A Scheme represents a specific instance of a signature scheme. type Scheme interface { // Name of the scheme. diff --git a/vendor/github.com/containerd/containerd/api/types/mount.pb.go b/vendor/github.com/containerd/containerd/api/types/mount.pb.go index ff77a7d7bd..4b91bc9090 100644 --- a/vendor/github.com/containerd/containerd/api/types/mount.pb.go +++ b/vendor/github.com/containerd/containerd/api/types/mount.pb.go @@ -24,6 +24,7 @@ package types import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" ) @@ -118,6 +119,148 @@ func (x *Mount) GetOptions() []string { return nil } +type ActiveMount struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Mount *Mount `protobuf:"bytes,1,opt,name=mount,proto3" json:"mount,omitempty"` + MountedAt *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=mounted_at,json=mountedAt,proto3" json:"mounted_at,omitempty"` + MountPoint string `protobuf:"bytes,3,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"` + Data map[string]string `protobuf:"bytes,4,rep,name=data,proto3" json:"data,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ActiveMount) Reset() { + *x = ActiveMount{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_containerd_api_types_mount_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActiveMount) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActiveMount) ProtoMessage() {} + +func (x *ActiveMount) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_containerd_api_types_mount_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActiveMount.ProtoReflect.Descriptor instead. +func (*ActiveMount) Descriptor() ([]byte, []int) { + return file_github_com_containerd_containerd_api_types_mount_proto_rawDescGZIP(), []int{1} +} + +func (x *ActiveMount) GetMount() *Mount { + if x != nil { + return x.Mount + } + return nil +} + +func (x *ActiveMount) GetMountedAt() *timestamppb.Timestamp { + if x != nil { + return x.MountedAt + } + return nil +} + +func (x *ActiveMount) GetMountPoint() string { + if x != nil { + return x.MountPoint + } + return "" +} + +func (x *ActiveMount) GetData() map[string]string { + if x != nil { + return x.Data + } + return nil +} + +type ActivationInfo struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Active []*ActiveMount `protobuf:"bytes,2,rep,name=active,proto3" json:"active,omitempty"` + System []*Mount `protobuf:"bytes,3,rep,name=system,proto3" json:"system,omitempty"` + Labels map[string]string `protobuf:"bytes,4,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` +} + +func (x *ActivationInfo) Reset() { + *x = ActivationInfo{} + if protoimpl.UnsafeEnabled { + mi := &file_github_com_containerd_containerd_api_types_mount_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ActivationInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActivationInfo) ProtoMessage() {} + +func (x *ActivationInfo) ProtoReflect() protoreflect.Message { + mi := &file_github_com_containerd_containerd_api_types_mount_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActivationInfo.ProtoReflect.Descriptor instead. +func (*ActivationInfo) Descriptor() ([]byte, []int) { + return file_github_com_containerd_containerd_api_types_mount_proto_rawDescGZIP(), []int{2} +} + +func (x *ActivationInfo) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ActivationInfo) GetActive() []*ActiveMount { + if x != nil { + return x.Active + } + return nil +} + +func (x *ActivationInfo) GetSystem() []*Mount { + if x != nil { + return x.System + } + return nil +} + +func (x *ActivationInfo) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + var File_github_com_containerd_containerd_api_types_mount_proto protoreflect.FileDescriptor var file_github_com_containerd_containerd_api_types_mount_proto_rawDesc = []byte{ @@ -125,17 +268,53 @@ var file_github_com_containerd_containerd_api_types_mount_proto_rawDesc = []byte 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x10, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x22, 0x65, 0x0a, 0x05, 0x4d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x3b, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, + 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, + 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x65, 0x0a, 0x05, 0x4d, + 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x22, 0x8e, 0x02, 0x0a, 0x0b, 0x41, 0x63, 0x74, 0x69, 0x76, 0x65, 0x4d, 0x6f, 0x75, + 0x6e, 0x74, 0x12, 0x2d, 0x0a, 0x05, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x05, 0x6d, 0x6f, 0x75, 0x6e, + 0x74, 0x12, 0x39, 0x0a, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, + 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0a, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x50, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x3b, 0x0a, + 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x63, 0x6f, + 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, + 0x63, 0x74, 0x69, 0x76, 0x65, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x37, 0x0a, 0x09, 0x44, 0x61, + 0x74, 0x61, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x22, 0x8d, 0x02, 0x0a, 0x0e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x35, 0x0a, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x63, + 0x74, 0x69, 0x76, 0x65, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x61, 0x63, 0x74, 0x69, 0x76, + 0x65, 0x12, 0x2f, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x17, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4d, 0x6f, 0x75, 0x6e, 0x74, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, + 0x65, 0x6d, 0x12, 0x44, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, + 0x02, 0x38, 0x01, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, + 0x6d, 0x2f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x63, 0x6f, 0x6e, + 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x64, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x3b, 0x74, 0x79, 0x70, 0x65, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -150,16 +329,27 @@ func file_github_com_containerd_containerd_api_types_mount_proto_rawDescGZIP() [ return file_github_com_containerd_containerd_api_types_mount_proto_rawDescData } -var file_github_com_containerd_containerd_api_types_mount_proto_msgTypes = make([]protoimpl.MessageInfo, 1) +var file_github_com_containerd_containerd_api_types_mount_proto_msgTypes = make([]protoimpl.MessageInfo, 5) var file_github_com_containerd_containerd_api_types_mount_proto_goTypes = []interface{}{ - (*Mount)(nil), // 0: containerd.types.Mount + (*Mount)(nil), // 0: containerd.types.Mount + (*ActiveMount)(nil), // 1: containerd.types.ActiveMount + (*ActivationInfo)(nil), // 2: containerd.types.ActivationInfo + nil, // 3: containerd.types.ActiveMount.DataEntry + nil, // 4: containerd.types.ActivationInfo.LabelsEntry + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp } var file_github_com_containerd_containerd_api_types_mount_proto_depIdxs = []int32{ - 0, // [0:0] is the sub-list for method output_type - 0, // [0:0] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name + 0, // 0: containerd.types.ActiveMount.mount:type_name -> containerd.types.Mount + 5, // 1: containerd.types.ActiveMount.mounted_at:type_name -> google.protobuf.Timestamp + 3, // 2: containerd.types.ActiveMount.data:type_name -> containerd.types.ActiveMount.DataEntry + 1, // 3: containerd.types.ActivationInfo.active:type_name -> containerd.types.ActiveMount + 0, // 4: containerd.types.ActivationInfo.system:type_name -> containerd.types.Mount + 4, // 5: containerd.types.ActivationInfo.labels:type_name -> containerd.types.ActivationInfo.LabelsEntry + 6, // [6:6] is the sub-list for method output_type + 6, // [6:6] is the sub-list for method input_type + 6, // [6:6] is the sub-list for extension type_name + 6, // [6:6] is the sub-list for extension extendee + 0, // [0:6] is the sub-list for field type_name } func init() { file_github_com_containerd_containerd_api_types_mount_proto_init() } @@ -180,6 +370,30 @@ func file_github_com_containerd_containerd_api_types_mount_proto_init() { return nil } } + file_github_com_containerd_containerd_api_types_mount_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActiveMount); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_github_com_containerd_containerd_api_types_mount_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ActivationInfo); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } } type x struct{} out := protoimpl.TypeBuilder{ @@ -187,7 +401,7 @@ func file_github_com_containerd_containerd_api_types_mount_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_github_com_containerd_containerd_api_types_mount_proto_rawDesc, NumEnums: 0, - NumMessages: 1, + NumMessages: 5, NumExtensions: 0, NumServices: 0, }, diff --git a/vendor/github.com/containerd/containerd/api/types/mount.proto b/vendor/github.com/containerd/containerd/api/types/mount.proto index 54e0a0cddf..2f3b0b6229 100644 --- a/vendor/github.com/containerd/containerd/api/types/mount.proto +++ b/vendor/github.com/containerd/containerd/api/types/mount.proto @@ -18,6 +18,8 @@ syntax = "proto3"; package containerd.types; +import "google/protobuf/timestamp.proto"; + option go_package = "github.com/containerd/containerd/api/types;types"; // Mount describes mounts for a container. @@ -41,3 +43,23 @@ message Mount { // Options specifies zero or more fstab style mount options. repeated string options = 4; } + +message ActiveMount { + Mount mount = 1; + + google.protobuf.Timestamp mounted_at = 2; + + string mount_point = 3; + + map data = 4; +} + +message ActivationInfo { + string name = 1; + + repeated ActiveMount active = 2; + + repeated Mount system = 3; + + map labels = 4; +} diff --git a/vendor/github.com/containerd/ttrpc/.golangci.yml b/vendor/github.com/containerd/ttrpc/.golangci.yml index 6462e52f66..ef1a7d9635 100644 --- a/vendor/github.com/containerd/ttrpc/.golangci.yml +++ b/vendor/github.com/containerd/ttrpc/.golangci.yml @@ -1,52 +1,56 @@ +version: "2" linters: enable: - - staticcheck - - unconvert - - gofmt - - goimports - - revive - - ineffassign - - vet - - unused - misspell + - revive + - unconvert disable: - errcheck - -linters-settings: - revive: - ignore-generated-headers: true - rules: - - name: blank-imports - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: exported - - name: if-return - - name: increment-decrement - - name: var-naming - arguments: [["UID", "GID"], []] - - name: var-declaration - - name: package-comments - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - - name: indent-error-flow - - name: errorf - - name: empty-block - - name: superfluous-else - - name: unused-parameter - - name: unreachable-code - - name: redefines-builtin-id - -issues: - include: - - EXC0002 - -run: - timeout: 8m - skip-dirs: - - example + settings: + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: error-return + - name: error-strings + - name: error-naming + - name: exported + - name: if-return + - name: increment-decrement + - name: var-naming + arguments: + - - UID + - GID + - [] + - name: var-declaration + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: indent-error-flow + - name: errorf + - name: empty-block + - name: superfluous-else + - name: unused-parameter + - name: unreachable-code + - name: redefines-builtin-id + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - example +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - example diff --git a/vendor/github.com/containerd/ttrpc/client.go b/vendor/github.com/containerd/ttrpc/client.go index b1bc7a3fc4..be20ed4897 100644 --- a/vendor/github.com/containerd/ttrpc/client.go +++ b/vendor/github.com/containerd/ttrpc/client.go @@ -268,7 +268,8 @@ func (cs *clientStream) RecvMsg(m interface{}) error { case msg = <-cs.s.recv: } - if msg.header.Type == messageTypeResponse { + switch msg.header.Type { + case messageTypeResponse: resp := &Response{} err := proto.Unmarshal(msg.payload[:msg.header.Length], resp) // return the payload buffer for reuse @@ -289,7 +290,7 @@ func (cs *clientStream) RecvMsg(m interface{}) error { cs.remoteClosed = true return nil - } else if msg.header.Type == messageTypeData { + case messageTypeData: if !cs.desc.StreamingServer { cs.c.deleteStream(cs.s) cs.remoteClosed = true @@ -310,9 +311,9 @@ func (cs *clientStream) RecvMsg(m interface{}) error { return err } return nil + default: + return fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) } - - return fmt.Errorf("unexpected %q message received: %w", msg.header.Type, ErrProtocol) } // Close closes the ttrpc connection and underlying connection diff --git a/vendor/github.com/containerd/ttrpc/server.go b/vendor/github.com/containerd/ttrpc/server.go index bb71de677b..9606a975dd 100644 --- a/vendor/github.com/containerd/ttrpc/server.go +++ b/vendor/github.com/containerd/ttrpc/server.go @@ -113,9 +113,7 @@ func (s *Server) Serve(ctx context.Context, l net.Listener) error { backoff *= 2 } - if max := time.Second; backoff > max { - backoff = max - } + backoff = min(time.Second, backoff) sleep := time.Duration(rand.Int63n(int64(backoff))) log.G(ctx).WithError(err).Errorf("ttrpc: failed accept; backoff %v", sleep) @@ -415,6 +413,7 @@ func (c *serverConn) run(sctx context.Context) { if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "StreamID is no longer active")) { return } + continue } sh := i.(*streamHandler) if mh.Flags&flagNoData != flagNoData { @@ -428,6 +427,7 @@ func (c *serverConn) run(sctx context.Context) { if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data handling error: %v", err)) { return } + continue } } @@ -437,6 +437,7 @@ func (c *serverConn) run(sctx context.Context) { if !sendStatus(mh.StreamID, status.Newf(codes.InvalidArgument, "data close message cannot include data")) { return } + continue } } } else if mh.Type == messageTypeRequest { diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify_other.go b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify_other.go new file mode 100644 index 0000000000..d6309495d4 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify_other.go @@ -0,0 +1,22 @@ +// Copyright 2025 +// +// 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. + +//go:build !unix + +package daemon + +// SdNotifyMonotonicUsec returns the empty string on unsupported platforms. +func SdNotifyMonotonicUsec() string { + return "" +} diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify_unix.go b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify_unix.go new file mode 100644 index 0000000000..bdbe6b0c28 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/sdnotify_unix.go @@ -0,0 +1,36 @@ +// Copyright 2025 +// +// 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. + +//go:build unix + +package daemon + +import ( + "strconv" + + "golang.org/x/sys/unix" +) + +// SdNotifyMonotonicUsec returns a MONOTONIC_USEC=... assignment for the current time +// with a trailing newline included. This is typically used with [SdNotifyReloading]. +// +// If the monotonic clock is not available on the system, the empty string is returned. +func SdNotifyMonotonicUsec() string { + var ts unix.Timespec + if err := unix.ClockGettime(unix.CLOCK_MONOTONIC, &ts); err != nil { + // Monotonic clock is not available on this system. + return "" + } + return "MONOTONIC_USEC=" + strconv.FormatInt(ts.Nano()/1000, 10) + "\n" +} diff --git a/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go b/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go index 25d9c1aa93..a93314d0ab 100644 --- a/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go +++ b/vendor/github.com/coreos/go-systemd/v22/daemon/watchdog.go @@ -15,6 +15,7 @@ package daemon import ( + "errors" "fmt" "os" "strconv" @@ -51,10 +52,10 @@ func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) { } s, err := strconv.Atoi(wusec) if err != nil { - return 0, fmt.Errorf("error converting WATCHDOG_USEC: %s", err) + return 0, fmt.Errorf("error converting WATCHDOG_USEC: %w", err) } if s <= 0 { - return 0, fmt.Errorf("error WATCHDOG_USEC must be a positive number") + return 0, errors.New("error WATCHDOG_USEC must be a positive number") } interval := time.Duration(s) * time.Microsecond @@ -63,7 +64,7 @@ func SdWatchdogEnabled(unsetEnvironment bool) (time.Duration, error) { } p, err := strconv.Atoi(wpid) if err != nil { - return 0, fmt.Errorf("error converting WATCHDOG_PID: %s", err) + return 0, fmt.Errorf("error converting WATCHDOG_PID: %w", err) } if os.Getpid() != p { return 0, nil diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go index 147f756fe2..e966c156d4 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ +// Package dbus provides integration with the systemd D-Bus API. +// See http://www.freedesktop.org/wiki/Software/systemd/dbus/ package dbus import ( @@ -94,7 +95,7 @@ type Conn struct { sigobj dbus.BusObject jobListener struct { - jobs map[dbus.ObjectPath]chan<- string + jobs map[dbus.ObjectPath][]chan<- string sync.Mutex } subStateSubscriber struct { @@ -206,7 +207,7 @@ func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) { } c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64) - c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string) + c.jobListener.jobs = make(map[dbus.ObjectPath][]chan<- string) // Setup the listeners on jobs so that we can get completions c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go index 074148cb4d..490248b861 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go @@ -24,15 +24,15 @@ import ( "github.com/godbus/dbus/v5" ) -// Who can be used to specify which process to kill in the unit via the KillUnitWithTarget API +// Who specifies which process to send a signal to via the [Conn.KillUnitWithTarget]. type Who string const ( - // All sends the signal to all processes in the unit + // All sends the signal to all processes in the unit. All Who = "all" - // Main sends the signal to the main process of the unit + // Main sends the signal to the main process of the unit. Main Who = "main" - // Control sends the signal to the control process of the unit + // Control sends the signal to the control process of the unit. Control Who = "control" ) @@ -41,17 +41,17 @@ func (c *Conn) jobComplete(signal *dbus.Signal) { var job dbus.ObjectPath var unit string var result string - dbus.Store(signal.Body, &id, &job, &unit, &result) + + _ = dbus.Store(signal.Body, &id, &job, &unit, &result) c.jobListener.Lock() - out, ok := c.jobListener.jobs[job] - if ok { + for _, out := range c.jobListener.jobs[job] { out <- result - delete(c.jobListener.jobs, job) } + delete(c.jobListener.jobs, job) c.jobListener.Unlock() } -func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...interface{}) (int, error) { +func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...any) (int, error) { if ch != nil { c.jobListener.Lock() defer c.jobListener.Unlock() @@ -64,7 +64,7 @@ func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args } if ch != nil { - c.jobListener.jobs[p] = ch + c.jobListener.jobs[p] = append(c.jobListener.jobs[p], ch) } // ignore error since 0 is fine if conversion fails @@ -102,6 +102,10 @@ func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error // has been removed too. skipped indicates that a job was skipped because it // didn't apply to the units current state. // +// Important: It is the caller's responsibility to unblock the provided channel write, +// either by reading from the channel or by using a buffered channel. Until the write +// is unblocked, the Conn object cannot handle other jobs. +// // If no error occurs, the ID of the underlying systemd job will be returned. There // does exist the possibility for no error to be returned, but for the returned job // ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint @@ -189,22 +193,30 @@ func (c *Conn) StartTransientUnit(name string, mode string, properties []Propert // unique. mode is the same as in StartUnitContext, properties contains properties // of the unit. func (c *Conn) StartTransientUnitContext(ctx context.Context, name string, mode string, properties []Property, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) + return c.StartTransientUnitAux(ctx, name, mode, properties, make([]PropertyCollection, 0), ch) +} + +// StartTransientUnitAux is the same as StartTransientUnitContext but allows passing +// auxiliary units in the aux parameter. +func (c *Conn) StartTransientUnitAux(ctx context.Context, name string, mode string, properties []Property, aux []PropertyCollection, ch chan<- string) (int, error) { + return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, aux) } -// Deprecated: use KillUnitContext instead. +// Deprecated: use [Conn.KillUnitWithTarget] instead. func (c *Conn) KillUnit(name string, signal int32) { c.KillUnitContext(context.Background(), name, signal) } // KillUnitContext takes the unit name and a UNIX signal number to send. // All of the unit's processes are killed. +// +// Deprecated: use [Conn.KillUnitWithTarget] instead, with target argument set to [All]. func (c *Conn) KillUnitContext(ctx context.Context, name string, signal int32) { - c.KillUnitWithTarget(ctx, name, All, signal) + _ = c.KillUnitWithTarget(ctx, name, All, signal) } -// KillUnitWithTarget is like KillUnitContext, but allows you to specify which -// process in the unit to send the signal to. +// KillUnitWithTarget sends a signal to the specified unit. +// The target argument can be one of [All], [Main], or [Control]. func (c *Conn) KillUnitWithTarget(ctx context.Context, name string, target Who, signal int32) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.KillUnit", 0, name, string(target), signal).Store() } @@ -240,7 +252,7 @@ func (c *Conn) SystemStateContext(ctx context.Context) (*Property, error) { } // getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface. -func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) { +func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]any, error) { var err error var props map[string]dbus.Variant @@ -254,7 +266,7 @@ func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInte return nil, err } - out := make(map[string]interface{}, len(props)) + out := make(map[string]any, len(props)) for k, v := range props { out[k] = v.Value() } @@ -263,36 +275,36 @@ func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInte } // Deprecated: use GetUnitPropertiesContext instead. -func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { +func (c *Conn) GetUnitProperties(unit string) (map[string]any, error) { return c.GetUnitPropertiesContext(context.Background(), unit) } // GetUnitPropertiesContext takes the (unescaped) unit name and returns all of // its dbus object properties. -func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { +func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]any, error) { path := unitPath(unit) return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") } // Deprecated: use GetUnitPathPropertiesContext instead. -func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) { +func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]any, error) { return c.GetUnitPathPropertiesContext(context.Background(), path) } // GetUnitPathPropertiesContext takes the (escaped) unit path and returns all // of its dbus object properties. -func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]interface{}, error) { +func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]any, error) { return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") } // Deprecated: use GetAllPropertiesContext instead. -func (c *Conn) GetAllProperties(unit string) (map[string]interface{}, error) { +func (c *Conn) GetAllProperties(unit string) (map[string]any, error) { return c.GetAllPropertiesContext(context.Background(), unit) } // GetAllPropertiesContext takes the (unescaped) unit name and returns all of // its dbus object properties. -func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { +func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]any, error) { path := unitPath(unit) return c.getProperties(ctx, path, "") } @@ -331,20 +343,20 @@ func (c *Conn) GetServiceProperty(service string, propertyName string) (*Propert return c.GetServicePropertyContext(context.Background(), service, propertyName) } -// GetServiceProperty returns property for given service name and property name. +// GetServicePropertyContext returns property for given service name and property name. func (c *Conn) GetServicePropertyContext(ctx context.Context, service string, propertyName string) (*Property, error) { return c.getProperty(ctx, service, "org.freedesktop.systemd1.Service", propertyName) } // Deprecated: use GetUnitTypePropertiesContext instead. -func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { +func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]any, error) { return c.GetUnitTypePropertiesContext(context.Background(), unit, unitType) } // GetUnitTypePropertiesContext returns the extra properties for a unit, specific to the unit type. // Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope. // Returns "dbus.Error: Unknown interface" error if the unitType is not the correct type of the unit. -func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]interface{}, error) { +func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]any, error) { path := unitPath(unit) return c.getProperties(ctx, path, "org.freedesktop.systemd1."+unitType) } @@ -389,32 +401,35 @@ type UnitStatus struct { JobPath dbus.ObjectPath // The job object path } -type storeFunc func(retvalues ...interface{}) error +type storeFunc func(retvalues ...any) error -func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { - result := make([][]interface{}, 0) - err := f(&result) - if err != nil { - return nil, err +// convertSlice converts a []any result into a slice of the target type T +// using dbus.Store to handle the type conversion. +func convertSlice[T any](result []any) ([]T, error) { + converted := make([]T, len(result)) + convertedInterface := make([]any, len(converted)) + for i := range converted { + convertedInterface[i] = &converted[i] } - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] + err := dbus.Store(result, convertedInterface...) + if err != nil { + return nil, err } - status := make([]UnitStatus, len(result)) - statusInterface := make([]interface{}, len(status)) - for i := range status { - statusInterface[i] = &status[i] - } + return converted, nil +} - err = dbus.Store(resultInterface, statusInterface...) +// storeSlice fetches D-Bus array results via the provided storeFunc +// and converts them into a slice of the target type T. +func storeSlice[T any](f storeFunc) ([]T, error) { + var result []any + err := f(&result) if err != nil { return nil, err } - return status, nil + return convertSlice[T](result) } // GetUnitByPID returns the unit object path of the unit a process ID @@ -451,7 +466,7 @@ func (c *Conn) ListUnits() ([]UnitStatus, error) { // Also note that a unit is only loaded if it is active and/or enabled. // Units that are both disabled and inactive will thus not be returned. func (c *Conn) ListUnitsContext(ctx context.Context) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnits", 0).Store) + return storeSlice[UnitStatus](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnits", 0).Store) } // Deprecated: use ListUnitsFilteredContext instead. @@ -462,7 +477,7 @@ func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) { // ListUnitsFilteredContext returns an array with units filtered by state. // It takes a list of units' statuses to filter. func (c *Conn) ListUnitsFilteredContext(ctx context.Context, states []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store) + return storeSlice[UnitStatus](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store) } // Deprecated: use ListUnitsByPatternsContext instead. @@ -475,7 +490,7 @@ func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitSt // Note that units may be known by multiple names at the same time, // and hence there might be more unit names loaded than actual units behind them. func (c *Conn) ListUnitsByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store) + return storeSlice[UnitStatus](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store) } // Deprecated: use ListUnitsByNamesContext instead. @@ -490,7 +505,7 @@ func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) { // // Requires systemd v230 or higher. func (c *Conn) ListUnitsByNamesContext(ctx context.Context, units []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store) + return storeSlice[UnitStatus](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store) } type UnitFile struct { @@ -498,40 +513,14 @@ type UnitFile struct { Type string } -func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) { - result := make([][]interface{}, 0) - err := f(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - files := make([]UnitFile, len(result)) - fileInterface := make([]interface{}, len(files)) - for i := range files { - fileInterface[i] = &files[i] - } - - err = dbus.Store(resultInterface, fileInterface...) - if err != nil { - return nil, err - } - - return files, nil -} - // Deprecated: use ListUnitFilesContext instead. func (c *Conn) ListUnitFiles() ([]UnitFile, error) { return c.ListUnitFilesContext(context.Background()) } -// ListUnitFiles returns an array of all available units on disk. +// ListUnitFilesContext returns an array of all available units on disk. func (c *Conn) ListUnitFilesContext(ctx context.Context) ([]UnitFile, error) { - return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) + return storeSlice[UnitFile](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) } // Deprecated: use ListUnitFilesByPatternsContext instead. @@ -541,7 +530,7 @@ func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]Un // ListUnitFilesByPatternsContext returns an array of all available units on disk matched the patterns. func (c *Conn) ListUnitFilesByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitFile, error) { - return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store) + return storeSlice[UnitFile](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store) } type LinkUnitFileChange EnableUnitFileChange @@ -569,29 +558,7 @@ func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUn // or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) LinkUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]LinkUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil + return storeSlice[LinkUnitFileChange](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store) } // Deprecated: use EnableUnitFilesContext instead. @@ -617,25 +584,14 @@ func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, // symlink. func (c *Conn) EnableUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { var carries_install_info bool + var result []any - result := make([][]interface{}, 0) err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) if err != nil { return false, nil, err } - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]EnableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) + changes, err := convertSlice[EnableUnitFileChange](result) if err != nil { return false, nil, err } @@ -667,29 +623,7 @@ func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFile // symlink or unlink), the file name of the symlink and the destination of the // symlink. func (c *Conn) DisableUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]DisableUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]DisableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil + return storeSlice[DisableUnitFileChange](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store) } type DisableUnitFileChange struct { @@ -713,29 +647,7 @@ func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUn // runtime only (true, /run/systemd/..), or persistently (false, // /etc/systemd/..). func (c *Conn) MaskUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]MaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil + return storeSlice[MaskUnitFileChange](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store) } type MaskUnitFileChange struct { @@ -757,29 +669,7 @@ func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileCh // for runtime only (true, /run/systemd/..), or persistently (false, // /etc/systemd/..). func (c *Conn) UnmaskUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]UnmaskUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]UnmaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil + return storeSlice[UnmaskUnitFileChange](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store) } type UnmaskUnitFileChange struct { @@ -825,40 +715,21 @@ func (c *Conn) ListJobs() ([]JobStatus, error) { // ListJobsContext returns an array with all currently queued jobs. func (c *Conn) ListJobsContext(ctx context.Context) ([]JobStatus, error) { - return c.listJobsInternal(ctx) + return storeSlice[JobStatus](c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListJobs", 0).Store) } -func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { - result := make([][]interface{}, 0) - if err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListJobs", 0).Store(&result); err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - status := make([]JobStatus, len(result)) - statusInterface := make([]interface{}, len(status)) - for i := range status { - statusInterface[i] = &status[i] - } - - if err := dbus.Store(resultInterface, statusInterface...); err != nil { - return nil, err - } - - return status, nil -} - -// Freeze the cgroup associated with the unit. -// Note that FreezeUnit and ThawUnit are only supported on systems running with cgroup v2. +// FreezeUnit freezes the cgroup associated with the unit. +// Note that FreezeUnit and [Conn.ThawUnit] are only supported on systems running with cgroup v2. func (c *Conn) FreezeUnit(ctx context.Context, unit string) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store() } -// Unfreeze the cgroup associated with the unit. +// ThawUnit unfreezes the cgroup associated with the unit. func (c *Conn) ThawUnit(ctx context.Context, unit string) error { return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() } + +// AttachProcessesToUnit moves existing processes, identified by pids, into an existing systemd unit. +func (c *Conn) AttachProcessesToUnit(ctx context.Context, unit, subcgroup string, pids []uint32) error { + return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.AttachProcessesToUnit", 0, unit, subcgroup, pids).Store() +} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/set.go index 17c5d48565..c0b8fde1fc 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/set.go @@ -14,28 +14,43 @@ package dbus +import ( + "sync" +) + type set struct { data map[string]bool + mu sync.Mutex } func (s *set) Add(value string) { + s.mu.Lock() + defer s.mu.Unlock() s.data[value] = true } func (s *set) Remove(value string) { + s.mu.Lock() + defer s.mu.Unlock() delete(s.data, value) } func (s *set) Contains(value string) (exists bool) { + s.mu.Lock() + defer s.mu.Unlock() _, exists = s.data[value] return } func (s *set) Length() int { + s.mu.Lock() + defer s.mu.Unlock() return len(s.data) } func (s *set) Values() (values []string) { + s.mu.Lock() + defer s.mu.Unlock() for val := range s.data { values = append(values, val) } @@ -43,5 +58,5 @@ func (s *set) Values() (values []string) { } func newSet() *set { - return &set{make(map[string]bool)} + return &set{data: make(map[string]bool)} } diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go index 7e370fea21..fe06f2fceb 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go @@ -15,6 +15,7 @@ package dbus import ( + "context" "errors" "log" "time" @@ -70,7 +71,7 @@ func (c *Conn) dispatch() { switch signal.Name { case "org.freedesktop.systemd1.Manager.JobRemoved": unitName := signal.Body[2].(string) - c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) + _ = c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) case "org.freedesktop.systemd1.Manager.UnitNew": unitPath = signal.Body[1].(dbus.ObjectPath) case "org.freedesktop.DBus.Properties.PropertiesChanged": @@ -94,16 +95,26 @@ func (c *Conn) dispatch() { }() } -// SubscribeUnits returns two unbuffered channels which will receive all changed units every -// interval. Deleted units are sent as nil. +// Deprecated: use SubscribeUnitsContext instead. func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { - return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) + return c.SubscribeUnitsContext(context.Background(), interval) +} + +// SubscribeUnitsContext returns two unbuffered channels which will receive all changed units every +// interval. Deleted units are sent as nil. +func (c *Conn) SubscribeUnitsContext(ctx context.Context, interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { + return c.SubscribeUnitsCustomContext(ctx, interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) } -// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer +// Deprecated: use SubscribeUnitsCustomContext instead. +func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { + return c.SubscribeUnitsCustomContext(context.Background(), interval, buffer, isChanged, filterUnit) +} + +// SubscribeUnitsCustomContext is like [Conn.SubscribeUnitsContext] but lets you specify the buffer // size of the channels, the comparison function for detecting changes and a filter // function for cutting down on the noise that your channel receives. -func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { +func (c *Conn) SubscribeUnitsCustomContext(ctx context.Context, interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { old := make(map[string]*UnitStatus) statusChan := make(chan map[string]*UnitStatus, buffer) errChan := make(chan error, buffer) @@ -112,7 +123,7 @@ func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChange for { timerChan := time.After(interval) - units, err := c.ListUnits() + units, err := c.ListUnitsContext(ctx) if err == nil { cur := make(map[string]*UnitStatus) for i := range units { @@ -145,7 +156,14 @@ func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChange errChan <- err } - <-timerChan + select { + case <-timerChan: + continue + case <-ctx.Done(): + close(statusChan) + close(errChan) + return + } } }() @@ -262,7 +280,7 @@ func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { return ok && t >= time.Now().UnixNano() } -func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { +func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]any) { loadState, ok := info["LoadState"].(string) if !ok { return diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go index 5b408d5847..173ca37287 100644 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go +++ b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go @@ -15,6 +15,7 @@ package dbus import ( + "context" "time" ) @@ -29,19 +30,24 @@ func (s *SubscriptionSet) filter(unit string) bool { return !s.Contains(unit) } -// Subscribe starts listening for dbus events for all of the units in the set. +// SubscribeContext starts listening for dbus events for all of the units in the set. // Returns channels identical to conn.SubscribeUnits. -func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { +func (s *SubscriptionSet) SubscribeContext(ctx context.Context) (<-chan map[string]*UnitStatus, <-chan error) { // TODO: Make fully evented by using systemd 209 with properties changed values - return s.conn.SubscribeUnitsCustom(time.Second, 0, + return s.conn.SubscribeUnitsCustomContext(ctx, time.Second, 0, mismatchUnitStatus, func(unit string) bool { return s.filter(unit) }, ) } +// Deprecated: use SubscribeContext instead. +func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { + return s.SubscribeContext(context.Background()) +} + // NewSubscriptionSet returns a new subscription set. -func (conn *Conn) NewSubscriptionSet() *SubscriptionSet { - return &SubscriptionSet{newSet(), conn} +func (c *Conn) NewSubscriptionSet() *SubscriptionSet { + return &SubscriptionSet{newSet(), c} } // mismatchUnitStatus returns true if the provided UnitStatus objects diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal.go index ac24c7767d..16c4e47751 100644 --- a/vendor/github.com/coreos/go-systemd/v22/journal/journal.go +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal.go @@ -41,6 +41,6 @@ const ( ) // Print prints a message to the local systemd journal using Send(). -func Print(priority Priority, format string, a ...interface{}) error { +func Print(priority Priority, format string, a ...any) error { return Send(fmt.Sprintf(format, a...), priority, nil) } diff --git a/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go b/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go index c5b23a8196..55cb9eb26a 100644 --- a/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go +++ b/vendor/github.com/coreos/go-systemd/v22/journal/journal_unix.go @@ -13,7 +13,6 @@ // limitations under the License. //go:build !windows -// +build !windows // Package journal provides write bindings to the local systemd journal. // It is implemented in pure Go and connects to the journal directly over its @@ -31,7 +30,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net" "os" "strconv" @@ -108,7 +106,7 @@ func fdIsJournalStream(fd int) (bool, error) { var expectedStat syscall.Stat_t _, err := fmt.Sscanf(journalStream, "%d:%d", &expectedStat.Dev, &expectedStat.Ino) if err != nil { - return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %v", journalStream, err) + return false, fmt.Errorf("failed to parse JOURNAL_STREAM=%q: %w", journalStream, err) } var stat syscall.Stat_t @@ -194,7 +192,7 @@ func appendVariable(w io.Writer, name, value string) { * - the data, followed by a newline */ fmt.Fprintln(w, name) - binary.Write(w, binary.LittleEndian, uint64(len(value))) + _ = binary.Write(w, binary.LittleEndian, uint64(len(value))) fmt.Fprintln(w, value) } else { /* just write the variable and value all on one line */ @@ -214,7 +212,7 @@ func validVarName(name string) error { } for _, c := range name { - if !(('A' <= c && c <= 'Z') || ('0' <= c && c <= '9') || c == '_') { + if ('A' > c || c > 'Z') && ('0' > c || c > '9') && c != '_' { return errors.New("Variable name contains invalid characters") } } @@ -239,7 +237,7 @@ func isSocketSpaceError(err error) bool { // tempFd creates a temporary, unlinked file under `/dev/shm`. func tempFd() (*os.File, error) { - file, err := ioutil.TempFile("/dev/shm/", "journal.XXXXX") + file, err := os.CreateTemp("/dev/shm/", "journal.XXXXX") if err != nil { return nil, err } diff --git a/vendor/github.com/coreos/go-systemd/v22/unit/deserialize.go b/vendor/github.com/coreos/go-systemd/v22/unit/deserialize.go index 283c150771..7796e16956 100644 --- a/vendor/github.com/coreos/go-systemd/v22/unit/deserialize.go +++ b/vendor/github.com/coreos/go-systemd/v22/unit/deserialize.go @@ -38,10 +38,8 @@ const ( SYSTEMD_NEWLINE = "\r\n" ) -var ( - // ErrLineTooLong gets returned when a line is too long for systemd to handle. - ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX) -) +// ErrLineTooLong gets returned when a line is too long for systemd to handle. +var ErrLineTooLong = fmt.Errorf("line too long (max %d bytes)", SYSTEMD_LINE_MAX) // DeserializeOptions parses a systemd unit file into a list of UnitOptions func DeserializeOptions(f io.Reader) (opts []*UnitOption, err error) { @@ -56,8 +54,8 @@ func DeserializeSections(f io.Reader) ([]*UnitSection, error) { } // Deserialize parses a systemd unit file into a list of UnitOptions. -// Note: this function is deprecated in favor of DeserializeOptions -// and will be removed at a future date. +// +// Deprecated: use [DeserializeOptions] instead. func Deserialize(f io.Reader) (opts []*UnitOption, err error) { return DeserializeOptions(f) } @@ -79,7 +77,6 @@ type lexData struct { // deserializeAll deserializes into UnitSections and UnitOptions. func deserializeAll(f io.Reader) ([]*UnitSection, []*UnitOption, error) { - lexer, lexchan, errchan := newLexer(f) go lexer.lex() @@ -92,13 +89,12 @@ func deserializeAll(f io.Reader) ([]*UnitSection, []*UnitOption, error) { case optionKind: if ld.Option != nil { // add to options - opt := ld.Option - options = append(options, &(*opt)) + opt := *ld.Option + options = append(options, &opt) // sanity check. "should not happen" as sectionKind is first in code flow. if len(sections) == 0 { - return nil, nil, fmt.Errorf( - "Unit file misparse: option before section") + return nil, nil, errors.New("unit file misparse: option before section") } // add to newest section entries. @@ -255,7 +251,7 @@ func (l *lexer) lexNextSectionOrOptionFunc(section string) lexStep { return l.ignoreLineFunc(l.lexNextSectionOrOptionFunc(section)), nil } - l.buf.UnreadRune() + _ = l.buf.UnreadRune() // This can't fail as we just called ReadRune. return l.lexOptionNameFunc(section), nil } } @@ -287,29 +283,23 @@ func (l *lexer) lexOptionNameFunc(section string) lexStep { func (l *lexer) lexOptionValueFunc(section, name string, partial bytes.Buffer) lexStep { return func() (lexStep, error) { - for { - line, eof, err := l.toEOL() - if err != nil { - return nil, err - } - - if len(bytes.TrimSpace(line)) == 0 { - break - } + line, eof, err := l.toEOL() + if err != nil { + return nil, err + } + if len(bytes.TrimSpace(line)) != 0 { partial.Write(line) // lack of continuation means this value has been exhausted - idx := bytes.LastIndex(line, []byte{'\\'}) - if idx == -1 || idx != (len(line)-1) { - break - } + if bytes.HasSuffix(line, []byte{'\\'}) { + // line ends with backslash, continue parsing + if !eof { + partial.WriteRune('\n') + } - if !eof { - partial.WriteRune('\n') + return l.lexOptionValueFunc(section, name, partial), nil } - - return l.lexOptionValueFunc(section, name, partial), nil } val := partial.String() diff --git a/vendor/github.com/coreos/go-systemd/v22/unit/doc.go b/vendor/github.com/coreos/go-systemd/v22/unit/doc.go new file mode 100644 index 0000000000..1c265e4aa2 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/v22/unit/doc.go @@ -0,0 +1,12 @@ +// Package unit provides utilities for parsing, serializing, and manipulating +// systemd unit files. It supports both reading unit file content into Go data +// structures and writing Go data structures back to unit file format. +// +// The package provides functionality to: +// - Parse systemd unit files into [UnitOption] and [UnitSection] structures +// - Serialize Go structures back into unit file format +// - Escape and unescape unit names according to systemd conventions +// +// Unit files are configuration files that describe how systemd should manage +// services, sockets, devices, and other system resources. +package unit diff --git a/vendor/github.com/coreos/go-systemd/v22/unit/escape.go b/vendor/github.com/coreos/go-systemd/v22/unit/escape.go index 98e2044a71..df9c8cc13f 100644 --- a/vendor/github.com/coreos/go-systemd/v22/unit/escape.go +++ b/vendor/github.com/coreos/go-systemd/v22/unit/escape.go @@ -57,7 +57,7 @@ func escape(unescaped string, isPath bool) string { if c == '/' { e = append(e, '-') } else if start && c == '.' || strings.IndexByte(allowed, c) == -1 { - e = append(e, []byte(fmt.Sprintf(`\x%x`, c))...) + e = fmt.Appendf(e, `\x%x`, c) } else { e = append(e, c) } diff --git a/vendor/github.com/coreos/go-systemd/v22/unit/option.go b/vendor/github.com/coreos/go-systemd/v22/unit/option.go index 98e1af5c99..800ecda449 100644 --- a/vendor/github.com/coreos/go-systemd/v22/unit/option.go +++ b/vendor/github.com/coreos/go-systemd/v22/unit/option.go @@ -49,7 +49,7 @@ func AllMatch(u1 []*UnitOption, u2 []*UnitOption) bool { return false } - for i := 0; i < length; i++ { + for i := range length { if !u1[i].Match(u2[i]) { return false } diff --git a/vendor/github.com/coreos/go-systemd/v22/unit/serialize.go b/vendor/github.com/coreos/go-systemd/v22/unit/serialize.go index c1b79c02dc..2557706593 100644 --- a/vendor/github.com/coreos/go-systemd/v22/unit/serialize.go +++ b/vendor/github.com/coreos/go-systemd/v22/unit/serialize.go @@ -61,7 +61,6 @@ func Serialize(opts []*UnitOption) io.Reader { // SerializeSections will serializes the unit file from the given // UnitSections. func SerializeSections(sections []*UnitSection) io.Reader { - var buf bytes.Buffer for i, s := range sections { diff --git a/vendor/github.com/docker/docker/api/types/versions/compare.go b/vendor/github.com/docker/docker/api/types/versions/compare.go index 621725a36d..1a0325c7ed 100644 --- a/vendor/github.com/docker/docker/api/types/versions/compare.go +++ b/vendor/github.com/docker/docker/api/types/versions/compare.go @@ -1,4 +1,4 @@ -package versions // import "github.com/docker/docker/api/types/versions" +package versions import ( "strconv" diff --git a/vendor/github.com/go-jose/go-jose/v4/.gitignore b/vendor/github.com/go-jose/go-jose/v4/.gitignore deleted file mode 100644 index eb29ebaefd..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -jose-util/jose-util -jose-util.t.err \ No newline at end of file diff --git a/vendor/github.com/go-jose/go-jose/v4/.golangci.yml b/vendor/github.com/go-jose/go-jose/v4/.golangci.yml deleted file mode 100644 index 2a577a8f95..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/.golangci.yml +++ /dev/null @@ -1,53 +0,0 @@ -# https://github.com/golangci/golangci-lint - -run: - skip-files: - - doc_test.go - modules-download-mode: readonly - -linters: - enable-all: true - disable: - - gochecknoglobals - - goconst - - lll - - maligned - - nakedret - - scopelint - - unparam - - funlen # added in 1.18 (requires go-jose changes before it can be enabled) - -linters-settings: - gocyclo: - min-complexity: 35 - -issues: - exclude-rules: - - text: "don't use ALL_CAPS in Go names" - linters: - - golint - - text: "hardcoded credentials" - linters: - - gosec - - text: "weak cryptographic primitive" - linters: - - gosec - - path: json/ - linters: - - dupl - - errcheck - - gocritic - - gocyclo - - golint - - govet - - ineffassign - - staticcheck - - structcheck - - stylecheck - - unused - - path: _test\.go - linters: - - scopelint - - path: jwk.go - linters: - - gocyclo diff --git a/vendor/github.com/go-jose/go-jose/v4/.travis.yml b/vendor/github.com/go-jose/go-jose/v4/.travis.yml deleted file mode 100644 index 48de631b00..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/.travis.yml +++ /dev/null @@ -1,33 +0,0 @@ -language: go - -matrix: - fast_finish: true - allow_failures: - - go: tip - -go: - - "1.13.x" - - "1.14.x" - - tip - -before_script: - - export PATH=$HOME/.local/bin:$PATH - -before_install: - - go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge - - curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0 - - pip install cram --user - -script: - - go test -v -covermode=count -coverprofile=profile.cov . - - go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner - - go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher - - go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt - - go test -v ./json # no coverage for forked encoding/json package - - golangci-lint run - - cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util - - cd .. - -after_success: - - gocovmerge *.cov */*.cov > merged.coverprofile - - goveralls -coverprofile merged.coverprofile -service=travis-ci diff --git a/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md b/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md deleted file mode 100644 index 4b4805add6..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/CONTRIBUTING.md +++ /dev/null @@ -1,9 +0,0 @@ -# Contributing - -If you would like to contribute code to go-jose you can do so through GitHub by -forking the repository and sending a pull request. - -When submitting code, please make every effort to follow existing conventions -and style in order to keep the code as readable as possible. Please also make -sure all tests pass by running `go test`, and format your code with `go fmt`. -We also recommend using `golint` and `errcheck`. diff --git a/vendor/github.com/go-jose/go-jose/v4/README.md b/vendor/github.com/go-jose/go-jose/v4/README.md deleted file mode 100644 index 55c5509176..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Go JOSE - -[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4) -[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt) -[![license](https://img.shields.io/badge/license-apache_2.0-blue.svg?style=flat)](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE) - -Package jose aims to provide an implementation of the Javascript Object Signing -and Encryption set of standards. This includes support for JSON Web Encryption, -JSON Web Signature, and JSON Web Token standards. - -## Overview - -The implementation follows the -[JSON Web Encryption](https://dx.doi.org/10.17487/RFC7516) (RFC 7516), -[JSON Web Signature](https://dx.doi.org/10.17487/RFC7515) (RFC 7515), and -[JSON Web Token](https://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications. -Tables of supported algorithms are shown below. The library supports both -the compact and JWS/JWE JSON Serialization formats, and has optional support for -multiple recipients. It also comes with a small command-line utility -([`jose-util`](https://pkg.go.dev/github.com/go-jose/go-jose/jose-util)) -for dealing with JOSE messages in a shell. - -**Note**: We use a forked version of the `encoding/json` package from the Go -standard library which uses case-sensitive matching for member names (instead -of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)). -This is to avoid differences in interpretation of messages between go-jose and -libraries in other languages. - -### Versions - -The forthcoming Version 5 will be released with several breaking API changes, -and will require Golang's `encoding/json/v2`, which is currently requires -Go 1.25 built with GOEXPERIMENT=jsonv2. - -Version 4 is the current stable version: - - import "github.com/go-jose/go-jose/v4" - -It supports at least the current and previous Golang release. Currently it -requires Golang 1.24. - -Version 3 is only receiving critical security updates. Migration to Version 4 is recommended. - -Versions 1 and 2 are obsolete, but can be found in the old repository, [square/go-jose](https://github.com/square/go-jose). - -### Supported algorithms - -See below for a table of supported algorithms. Algorithm identifiers match -the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518) -standard where possible. The Godoc reference has a list of constants. - -| Key encryption | Algorithm identifier(s) | -|:-----------------------|:-----------------------------------------------| -| RSA-PKCS#1v1.5 | RSA1_5 | -| RSA-OAEP | RSA-OAEP, RSA-OAEP-256 | -| AES key wrap | A128KW, A192KW, A256KW | -| AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW | -| ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW | -| ECDH-ES (direct) | ECDH-ES1 | -| Direct encryption | dir1 | - -1. Not supported in multi-recipient mode - -| Signing / MAC | Algorithm identifier(s) | -|:------------------|:------------------------| -| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 | -| RSASSA-PSS | PS256, PS384, PS512 | -| HMAC | HS256, HS384, HS512 | -| ECDSA | ES256, ES384, ES512 | -| Ed25519 | EdDSA2 | - -2. Only available in version 2 of the package - -| Content encryption | Algorithm identifier(s) | -|:-------------------|:--------------------------------------------| -| AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 | -| AES-GCM | A128GCM, A192GCM, A256GCM | - -| Compression | Algorithm identifiers(s) | -|:-------------------|--------------------------| -| DEFLATE (RFC 1951) | DEF | - -### Supported key types - -See below for a table of supported key types. These are understood by the -library, and can be passed to corresponding functions such as `NewEncrypter` or -`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which -allows attaching a key id. - -| Algorithm(s) | Corresponding types | -|:------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) | -| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) | -| EdDSA1 | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) | -| AES, HMAC | []byte | - -1. Only available in version 2 or later of the package - -## Examples - -[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4) -[![godoc](https://pkg.go.dev/badge/github.com/go-jose/go-jose/v4/jwt.svg)](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt) - -Examples can be found in the Godoc -reference for this package. The -[`jose-util`](https://github.com/go-jose/go-jose/tree/main/jose-util) -subdirectory also contains a small command-line utility which might be useful -as an example as well. diff --git a/vendor/github.com/go-jose/go-jose/v4/SECURITY.md b/vendor/github.com/go-jose/go-jose/v4/SECURITY.md deleted file mode 100644 index 2f18a75a82..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/SECURITY.md +++ /dev/null @@ -1,13 +0,0 @@ -# Security Policy -This document explains how to contact the Let's Encrypt security team to report security vulnerabilities. - -## Supported Versions -| Version | Supported | -| ------- | ----------| -| >= v3 | ✓ | -| v2 | ✗ | -| v1 | ✗ | - -## Reporting a vulnerability - -Please see [https://letsencrypt.org/contact/#security](https://letsencrypt.org/contact/#security) for the email address to report a vulnerability. Ensure that the subject line for your report contains the word `vulnerability` and is descriptive. Your email should be acknowledged within 24 hours. If you do not receive a response within 24 hours, please follow-up again with another email. diff --git a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go b/vendor/github.com/go-jose/go-jose/v4/asymmetric.go deleted file mode 100644 index 7784cd4584..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/asymmetric.go +++ /dev/null @@ -1,603 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 jose - -import ( - "crypto" - "crypto/aes" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rand" - "crypto/rsa" - "crypto/sha1" - "crypto/sha256" - "errors" - "fmt" - "math/big" - - josecipher "github.com/go-jose/go-jose/v4/cipher" - "github.com/go-jose/go-jose/v4/json" -) - -// A generic RSA-based encrypter/verifier -type rsaEncrypterVerifier struct { - publicKey *rsa.PublicKey -} - -// A generic RSA-based decrypter/signer -type rsaDecrypterSigner struct { - privateKey *rsa.PrivateKey -} - -// A generic EC-based encrypter/verifier -type ecEncrypterVerifier struct { - publicKey *ecdsa.PublicKey -} - -type edEncrypterVerifier struct { - publicKey ed25519.PublicKey -} - -// A key generator for ECDH-ES -type ecKeyGenerator struct { - size int - algID string - publicKey *ecdsa.PublicKey -} - -// A generic EC-based decrypter/signer -type ecDecrypterSigner struct { - privateKey *ecdsa.PrivateKey -} - -type edDecrypterSigner struct { - privateKey ed25519.PrivateKey -} - -// newRSARecipient creates recipientKeyInfo based on the given key. -func newRSARecipient(keyAlg KeyAlgorithm, publicKey *rsa.PublicKey) (recipientKeyInfo, error) { - // Verify that key management algorithm is supported by this encrypter - switch keyAlg { - case RSA1_5, RSA_OAEP, RSA_OAEP_256: - default: - return recipientKeyInfo{}, ErrUnsupportedAlgorithm - } - - if publicKey == nil { - return recipientKeyInfo{}, errors.New("invalid public key") - } - - return recipientKeyInfo{ - keyAlg: keyAlg, - keyEncrypter: &rsaEncrypterVerifier{ - publicKey: publicKey, - }, - }, nil -} - -// newRSASigner creates a recipientSigInfo based on the given key. -func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipientSigInfo, error) { - // Verify that key management algorithm is supported by this encrypter - switch sigAlg { - case RS256, RS384, RS512, PS256, PS384, PS512: - default: - return recipientSigInfo{}, ErrUnsupportedAlgorithm - } - - if privateKey == nil { - return recipientSigInfo{}, errors.New("invalid private key") - } - - return recipientSigInfo{ - sigAlg: sigAlg, - publicKey: staticPublicKey(&JSONWebKey{ - Key: privateKey.Public(), - }), - signer: &rsaDecrypterSigner{ - privateKey: privateKey, - }, - }, nil -} - -func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) (recipientSigInfo, error) { - if sigAlg != EdDSA { - return recipientSigInfo{}, ErrUnsupportedAlgorithm - } - - if privateKey == nil { - return recipientSigInfo{}, errors.New("invalid private key") - } - return recipientSigInfo{ - sigAlg: sigAlg, - publicKey: staticPublicKey(&JSONWebKey{ - Key: privateKey.Public(), - }), - signer: &edDecrypterSigner{ - privateKey: privateKey, - }, - }, nil -} - -// newECDHRecipient creates recipientKeyInfo based on the given key. -func newECDHRecipient(keyAlg KeyAlgorithm, publicKey *ecdsa.PublicKey) (recipientKeyInfo, error) { - // Verify that key management algorithm is supported by this encrypter - switch keyAlg { - case ECDH_ES, ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: - default: - return recipientKeyInfo{}, ErrUnsupportedAlgorithm - } - - if publicKey == nil || !publicKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { - return recipientKeyInfo{}, errors.New("invalid public key") - } - - return recipientKeyInfo{ - keyAlg: keyAlg, - keyEncrypter: &ecEncrypterVerifier{ - publicKey: publicKey, - }, - }, nil -} - -// newECDSASigner creates a recipientSigInfo based on the given key. -func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (recipientSigInfo, error) { - // Verify that key management algorithm is supported by this encrypter - switch sigAlg { - case ES256, ES384, ES512: - default: - return recipientSigInfo{}, ErrUnsupportedAlgorithm - } - - if privateKey == nil { - return recipientSigInfo{}, errors.New("invalid private key") - } - - return recipientSigInfo{ - sigAlg: sigAlg, - publicKey: staticPublicKey(&JSONWebKey{ - Key: privateKey.Public(), - }), - signer: &ecDecrypterSigner{ - privateKey: privateKey, - }, - }, nil -} - -// Encrypt the given payload and update the object. -func (ctx rsaEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { - encryptedKey, err := ctx.encrypt(cek, alg) - if err != nil { - return recipientInfo{}, err - } - - return recipientInfo{ - encryptedKey: encryptedKey, - header: &rawHeader{}, - }, nil -} - -// Encrypt the given payload. Based on the key encryption algorithm, -// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256). -func (ctx rsaEncrypterVerifier) encrypt(cek []byte, alg KeyAlgorithm) ([]byte, error) { - switch alg { - case RSA1_5: - return rsa.EncryptPKCS1v15(RandReader, ctx.publicKey, cek) - case RSA_OAEP: - return rsa.EncryptOAEP(sha1.New(), RandReader, ctx.publicKey, cek, []byte{}) - case RSA_OAEP_256: - return rsa.EncryptOAEP(sha256.New(), RandReader, ctx.publicKey, cek, []byte{}) - } - - return nil, ErrUnsupportedAlgorithm -} - -// Decrypt the given payload and return the content encryption key. -func (ctx rsaDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { - return ctx.decrypt(recipient.encryptedKey, headers.getAlgorithm(), generator) -} - -// Decrypt the given payload. Based on the key encryption algorithm, -// this will either use RSA-PKCS1v1.5 or RSA-OAEP (with SHA-1 or SHA-256). -func (ctx rsaDecrypterSigner) decrypt(jek []byte, alg KeyAlgorithm, generator keyGenerator) ([]byte, error) { - // Note: The random reader on decrypt operations is only used for blinding, - // so stubbing is meanlingless (hence the direct use of rand.Reader). - switch alg { - case RSA1_5: - defer func() { - // DecryptPKCS1v15SessionKey sometimes panics on an invalid payload - // because of an index out of bounds error, which we want to ignore. - // This has been fixed in Go 1.3.1 (released 2014/08/13), the recover() - // only exists for preventing crashes with unpatched versions. - // See: https://groups.google.com/forum/#!topic/golang-dev/7ihX6Y6kx9k - // See: https://code.google.com/p/go/source/detail?r=58ee390ff31602edb66af41ed10901ec95904d33 - _ = recover() - }() - - // Perform some input validation. - keyBytes := ctx.privateKey.PublicKey.N.BitLen() / 8 - if keyBytes != len(jek) { - // Input size is incorrect, the encrypted payload should always match - // the size of the public modulus (e.g. using a 2048 bit key will - // produce 256 bytes of output). Reject this since it's invalid input. - return nil, ErrCryptoFailure - } - - cek, _, err := generator.genKey() - if err != nil { - return nil, ErrCryptoFailure - } - - // When decrypting an RSA-PKCS1v1.5 payload, we must take precautions to - // prevent chosen-ciphertext attacks as described in RFC 3218, "Preventing - // the Million Message Attack on Cryptographic Message Syntax". We are - // therefore deliberately ignoring errors here. - _ = rsa.DecryptPKCS1v15SessionKey(rand.Reader, ctx.privateKey, jek, cek) - - return cek, nil - case RSA_OAEP: - // Use rand.Reader for RSA blinding - return rsa.DecryptOAEP(sha1.New(), rand.Reader, ctx.privateKey, jek, []byte{}) - case RSA_OAEP_256: - // Use rand.Reader for RSA blinding - return rsa.DecryptOAEP(sha256.New(), rand.Reader, ctx.privateKey, jek, []byte{}) - } - - return nil, ErrUnsupportedAlgorithm -} - -// Sign the given payload -func (ctx rsaDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { - var hash crypto.Hash - - switch alg { - case RS256, PS256: - hash = crypto.SHA256 - case RS384, PS384: - hash = crypto.SHA384 - case RS512, PS512: - hash = crypto.SHA512 - default: - return Signature{}, ErrUnsupportedAlgorithm - } - - hasher := hash.New() - - // According to documentation, Write() on hash never fails - _, _ = hasher.Write(payload) - hashed := hasher.Sum(nil) - - var out []byte - var err error - - switch alg { - case RS256, RS384, RS512: - // TODO(https://github.com/go-jose/go-jose/issues/40): As of go1.20, the - // random parameter is legacy and ignored, and it can be nil. - // https://cs.opensource.google/go/go/+/refs/tags/go1.20:src/crypto/rsa/pkcs1v15.go;l=263;bpv=0;bpt=1 - out, err = rsa.SignPKCS1v15(RandReader, ctx.privateKey, hash, hashed) - case PS256, PS384, PS512: - out, err = rsa.SignPSS(RandReader, ctx.privateKey, hash, hashed, &rsa.PSSOptions{ - SaltLength: rsa.PSSSaltLengthEqualsHash, - }) - } - - if err != nil { - return Signature{}, err - } - - return Signature{ - Signature: out, - protected: &rawHeader{}, - }, nil -} - -// Verify the given payload -func (ctx rsaEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { - var hash crypto.Hash - - switch alg { - case RS256, PS256: - hash = crypto.SHA256 - case RS384, PS384: - hash = crypto.SHA384 - case RS512, PS512: - hash = crypto.SHA512 - default: - return ErrUnsupportedAlgorithm - } - - hasher := hash.New() - - // According to documentation, Write() on hash never fails - _, _ = hasher.Write(payload) - hashed := hasher.Sum(nil) - - switch alg { - case RS256, RS384, RS512: - return rsa.VerifyPKCS1v15(ctx.publicKey, hash, hashed, signature) - case PS256, PS384, PS512: - return rsa.VerifyPSS(ctx.publicKey, hash, hashed, signature, nil) - } - - return ErrUnsupportedAlgorithm -} - -// Encrypt the given payload and update the object. -func (ctx ecEncrypterVerifier) encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) { - switch alg { - case ECDH_ES: - // ECDH-ES mode doesn't wrap a key, the shared secret is used directly as the key. - return recipientInfo{ - header: &rawHeader{}, - }, nil - case ECDH_ES_A128KW, ECDH_ES_A192KW, ECDH_ES_A256KW: - default: - return recipientInfo{}, ErrUnsupportedAlgorithm - } - - generator := ecKeyGenerator{ - algID: string(alg), - publicKey: ctx.publicKey, - } - - switch alg { - case ECDH_ES_A128KW: - generator.size = 16 - case ECDH_ES_A192KW: - generator.size = 24 - case ECDH_ES_A256KW: - generator.size = 32 - } - - kek, header, err := generator.genKey() - if err != nil { - return recipientInfo{}, err - } - - block, err := aes.NewCipher(kek) - if err != nil { - return recipientInfo{}, err - } - - jek, err := josecipher.KeyWrap(block, cek) - if err != nil { - return recipientInfo{}, err - } - - return recipientInfo{ - encryptedKey: jek, - header: &header, - }, nil -} - -// Get key size for EC key generator -func (ctx ecKeyGenerator) keySize() int { - return ctx.size -} - -// Get a content encryption key for ECDH-ES -func (ctx ecKeyGenerator) genKey() ([]byte, rawHeader, error) { - priv, err := ecdsa.GenerateKey(ctx.publicKey.Curve, RandReader) - if err != nil { - return nil, rawHeader{}, err - } - - out := josecipher.DeriveECDHES(ctx.algID, []byte{}, []byte{}, priv, ctx.publicKey, ctx.size) - - b, err := json.Marshal(&JSONWebKey{ - Key: &priv.PublicKey, - }) - if err != nil { - return nil, nil, err - } - - headers := rawHeader{ - headerEPK: makeRawMessage(b), - } - - return out, headers, nil -} - -// Decrypt the given payload and return the content encryption key. -func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) { - if recipient == nil { - return nil, errors.New("go-jose/go-jose: missing recipient") - } - epk, err := headers.getEPK() - if err != nil { - return nil, errors.New("go-jose/go-jose: invalid epk header") - } - if epk == nil { - return nil, errors.New("go-jose/go-jose: missing epk header") - } - - publicKey, ok := epk.Key.(*ecdsa.PublicKey) - if publicKey == nil || !ok { - return nil, errors.New("go-jose/go-jose: invalid epk header") - } - - if !ctx.privateKey.Curve.IsOnCurve(publicKey.X, publicKey.Y) { - return nil, errors.New("go-jose/go-jose: invalid public key in epk header") - } - - apuData, err := headers.getAPU() - if err != nil { - return nil, errors.New("go-jose/go-jose: invalid apu header") - } - apvData, err := headers.getAPV() - if err != nil { - return nil, errors.New("go-jose/go-jose: invalid apv header") - } - - deriveKey := func(algID string, size int) []byte { - return josecipher.DeriveECDHES(algID, apuData.bytes(), apvData.bytes(), ctx.privateKey, publicKey, size) - } - - var keySize int - - algorithm := headers.getAlgorithm() - switch algorithm { - case ECDH_ES: - // ECDH-ES uses direct key agreement, no key unwrapping necessary. - return deriveKey(string(headers.getEncryption()), generator.keySize()), nil - case ECDH_ES_A128KW: - keySize = 16 - case ECDH_ES_A192KW: - keySize = 24 - case ECDH_ES_A256KW: - keySize = 32 - default: - return nil, ErrUnsupportedAlgorithm - } - - encryptedKey := recipient.encryptedKey - if len(encryptedKey) == 0 { - return nil, errors.New("go-jose/go-jose: missing JWE Encrypted Key") - } - - key := deriveKey(string(algorithm), keySize) - block, err := aes.NewCipher(key) - if err != nil { - return nil, err - } - - return josecipher.KeyUnwrap(block, encryptedKey) -} - -func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { - if alg != EdDSA { - return Signature{}, ErrUnsupportedAlgorithm - } - - sig, err := ctx.privateKey.Sign(RandReader, payload, crypto.Hash(0)) - if err != nil { - return Signature{}, err - } - - return Signature{ - Signature: sig, - protected: &rawHeader{}, - }, nil -} - -func (ctx edEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { - if alg != EdDSA { - return ErrUnsupportedAlgorithm - } - ok := ed25519.Verify(ctx.publicKey, payload, signature) - if !ok { - return errors.New("go-jose/go-jose: ed25519 signature failed to verify") - } - return nil -} - -// Sign the given payload -func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { - var expectedBitSize int - var hash crypto.Hash - - switch alg { - case ES256: - expectedBitSize = 256 - hash = crypto.SHA256 - case ES384: - expectedBitSize = 384 - hash = crypto.SHA384 - case ES512: - expectedBitSize = 521 - hash = crypto.SHA512 - } - - curveBits := ctx.privateKey.Curve.Params().BitSize - if expectedBitSize != curveBits { - return Signature{}, fmt.Errorf("go-jose/go-jose: expected %d bit key, got %d bits instead", expectedBitSize, curveBits) - } - - hasher := hash.New() - - // According to documentation, Write() on hash never fails - _, _ = hasher.Write(payload) - hashed := hasher.Sum(nil) - - r, s, err := ecdsa.Sign(RandReader, ctx.privateKey, hashed) - if err != nil { - return Signature{}, err - } - - keyBytes := curveBits / 8 - if curveBits%8 > 0 { - keyBytes++ - } - - // We serialize the outputs (r and s) into big-endian byte arrays and pad - // them with zeros on the left to make sure the sizes work out. Both arrays - // must be keyBytes long, and the output must be 2*keyBytes long. - rBytes := r.Bytes() - rBytesPadded := make([]byte, keyBytes) - copy(rBytesPadded[keyBytes-len(rBytes):], rBytes) - - sBytes := s.Bytes() - sBytesPadded := make([]byte, keyBytes) - copy(sBytesPadded[keyBytes-len(sBytes):], sBytes) - - out := append(rBytesPadded, sBytesPadded...) - - return Signature{ - Signature: out, - protected: &rawHeader{}, - }, nil -} - -// Verify the given payload -func (ctx ecEncrypterVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { - var keySize int - var hash crypto.Hash - - switch alg { - case ES256: - keySize = 32 - hash = crypto.SHA256 - case ES384: - keySize = 48 - hash = crypto.SHA384 - case ES512: - keySize = 66 - hash = crypto.SHA512 - default: - return ErrUnsupportedAlgorithm - } - - if len(signature) != 2*keySize { - return fmt.Errorf("go-jose/go-jose: invalid signature size, have %d bytes, wanted %d", len(signature), 2*keySize) - } - - hasher := hash.New() - - // According to documentation, Write() on hash never fails - _, _ = hasher.Write(payload) - hashed := hasher.Sum(nil) - - r := big.NewInt(0).SetBytes(signature[:keySize]) - s := big.NewInt(0).SetBytes(signature[keySize:]) - - match := ecdsa.Verify(ctx.publicKey, hashed, r, s) - if !match { - return errors.New("go-jose/go-jose: ecdsa signature failed to verify") - } - - return nil -} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go b/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go deleted file mode 100644 index af029cec0b..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/cipher/cbc_hmac.go +++ /dev/null @@ -1,196 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 josecipher - -import ( - "bytes" - "crypto/cipher" - "crypto/hmac" - "crypto/sha256" - "crypto/sha512" - "crypto/subtle" - "encoding/binary" - "errors" - "hash" -) - -const ( - nonceBytes = 16 -) - -// NewCBCHMAC instantiates a new AEAD based on CBC+HMAC. -func NewCBCHMAC(key []byte, newBlockCipher func([]byte) (cipher.Block, error)) (cipher.AEAD, error) { - keySize := len(key) / 2 - integrityKey := key[:keySize] - encryptionKey := key[keySize:] - - blockCipher, err := newBlockCipher(encryptionKey) - if err != nil { - return nil, err - } - - var hash func() hash.Hash - switch keySize { - case 16: - hash = sha256.New - case 24: - hash = sha512.New384 - case 32: - hash = sha512.New - } - - return &cbcAEAD{ - hash: hash, - blockCipher: blockCipher, - authtagBytes: keySize, - integrityKey: integrityKey, - }, nil -} - -// An AEAD based on CBC+HMAC -type cbcAEAD struct { - hash func() hash.Hash - authtagBytes int - integrityKey []byte - blockCipher cipher.Block -} - -func (ctx *cbcAEAD) NonceSize() int { - return nonceBytes -} - -func (ctx *cbcAEAD) Overhead() int { - // Maximum overhead is block size (for padding) plus auth tag length, where - // the length of the auth tag is equivalent to the key size. - return ctx.blockCipher.BlockSize() + ctx.authtagBytes -} - -// Seal encrypts and authenticates the plaintext. -func (ctx *cbcAEAD) Seal(dst, nonce, plaintext, data []byte) []byte { - // Output buffer -- must take care not to mangle plaintext input. - ciphertext := make([]byte, uint64(len(plaintext))+uint64(ctx.Overhead()))[:len(plaintext)] - copy(ciphertext, plaintext) - ciphertext = padBuffer(ciphertext, ctx.blockCipher.BlockSize()) - - cbc := cipher.NewCBCEncrypter(ctx.blockCipher, nonce) - - cbc.CryptBlocks(ciphertext, ciphertext) - authtag := ctx.computeAuthTag(data, nonce, ciphertext) - - ret, out := resize(dst, uint64(len(dst))+uint64(len(ciphertext))+uint64(len(authtag))) - copy(out, ciphertext) - copy(out[len(ciphertext):], authtag) - - return ret -} - -// Open decrypts and authenticates the ciphertext. -func (ctx *cbcAEAD) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) { - if len(ciphertext) < ctx.authtagBytes { - return nil, errors.New("go-jose/go-jose: invalid ciphertext (too short)") - } - - offset := len(ciphertext) - ctx.authtagBytes - expectedTag := ctx.computeAuthTag(data, nonce, ciphertext[:offset]) - match := subtle.ConstantTimeCompare(expectedTag, ciphertext[offset:]) - if match != 1 { - return nil, errors.New("go-jose/go-jose: invalid ciphertext (auth tag mismatch)") - } - - cbc := cipher.NewCBCDecrypter(ctx.blockCipher, nonce) - - // Make copy of ciphertext buffer, don't want to modify in place - buffer := append([]byte{}, ciphertext[:offset]...) - - if len(buffer)%ctx.blockCipher.BlockSize() > 0 { - return nil, errors.New("go-jose/go-jose: invalid ciphertext (invalid length)") - } - - cbc.CryptBlocks(buffer, buffer) - - // Remove padding - plaintext, err := unpadBuffer(buffer, ctx.blockCipher.BlockSize()) - if err != nil { - return nil, err - } - - ret, out := resize(dst, uint64(len(dst))+uint64(len(plaintext))) - copy(out, plaintext) - - return ret, nil -} - -// Compute an authentication tag -func (ctx *cbcAEAD) computeAuthTag(aad, nonce, ciphertext []byte) []byte { - buffer := make([]byte, uint64(len(aad))+uint64(len(nonce))+uint64(len(ciphertext))+8) - n := 0 - n += copy(buffer, aad) - n += copy(buffer[n:], nonce) - n += copy(buffer[n:], ciphertext) - binary.BigEndian.PutUint64(buffer[n:], uint64(len(aad))*8) - - // According to documentation, Write() on hash.Hash never fails. - hmac := hmac.New(ctx.hash, ctx.integrityKey) - _, _ = hmac.Write(buffer) - - return hmac.Sum(nil)[:ctx.authtagBytes] -} - -// resize ensures that the given slice has a capacity of at least n bytes. -// If the capacity of the slice is less than n, a new slice is allocated -// and the existing data will be copied. -func resize(in []byte, n uint64) (head, tail []byte) { - if uint64(cap(in)) >= n { - head = in[:n] - } else { - head = make([]byte, n) - copy(head, in) - } - - tail = head[len(in):] - return -} - -// Apply padding -func padBuffer(buffer []byte, blockSize int) []byte { - missing := blockSize - (len(buffer) % blockSize) - ret, out := resize(buffer, uint64(len(buffer))+uint64(missing)) - padding := bytes.Repeat([]byte{byte(missing)}, missing) - copy(out, padding) - return ret -} - -// Remove padding -func unpadBuffer(buffer []byte, blockSize int) ([]byte, error) { - if len(buffer)%blockSize != 0 { - return nil, errors.New("go-jose/go-jose: invalid padding") - } - - last := buffer[len(buffer)-1] - count := int(last) - - if count == 0 || count > blockSize || count > len(buffer) { - return nil, errors.New("go-jose/go-jose: invalid padding") - } - - padding := bytes.Repeat([]byte{last}, count) - if !bytes.HasSuffix(buffer, padding) { - return nil, errors.New("go-jose/go-jose: invalid padding") - } - - return buffer[:len(buffer)-count], nil -} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go b/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go deleted file mode 100644 index f62c3bdba5..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/cipher/concat_kdf.go +++ /dev/null @@ -1,75 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 josecipher - -import ( - "crypto" - "encoding/binary" - "hash" - "io" -) - -type concatKDF struct { - z, info []byte - i uint32 - cache []byte - hasher hash.Hash -} - -// NewConcatKDF builds a KDF reader based on the given inputs. -func NewConcatKDF(hash crypto.Hash, z, algID, ptyUInfo, ptyVInfo, supPubInfo, supPrivInfo []byte) io.Reader { - buffer := make([]byte, uint64(len(algID))+uint64(len(ptyUInfo))+uint64(len(ptyVInfo))+uint64(len(supPubInfo))+uint64(len(supPrivInfo))) - n := 0 - n += copy(buffer, algID) - n += copy(buffer[n:], ptyUInfo) - n += copy(buffer[n:], ptyVInfo) - n += copy(buffer[n:], supPubInfo) - copy(buffer[n:], supPrivInfo) - - hasher := hash.New() - - return &concatKDF{ - z: z, - info: buffer, - hasher: hasher, - cache: []byte{}, - i: 1, - } -} - -func (ctx *concatKDF) Read(out []byte) (int, error) { - copied := copy(out, ctx.cache) - ctx.cache = ctx.cache[copied:] - - for copied < len(out) { - ctx.hasher.Reset() - - // Write on a hash.Hash never fails - _ = binary.Write(ctx.hasher, binary.BigEndian, ctx.i) - _, _ = ctx.hasher.Write(ctx.z) - _, _ = ctx.hasher.Write(ctx.info) - - hash := ctx.hasher.Sum(nil) - chunkCopied := copy(out[copied:], hash) - copied += chunkCopied - ctx.cache = hash[chunkCopied:] - - ctx.i++ - } - - return copied, nil -} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go b/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go deleted file mode 100644 index 093c646740..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/cipher/ecdh_es.go +++ /dev/null @@ -1,86 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 josecipher - -import ( - "bytes" - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "encoding/binary" -) - -// DeriveECDHES derives a shared encryption key using ECDH/ConcatKDF as described in JWE/JWA. -// It is an error to call this function with a private/public key that are not on the same -// curve. Callers must ensure that the keys are valid before calling this function. Output -// size may be at most 1<<16 bytes (64 KiB). -func DeriveECDHES(alg string, apuData, apvData []byte, priv *ecdsa.PrivateKey, pub *ecdsa.PublicKey, size int) []byte { - if size > 1<<16 { - panic("ECDH-ES output size too large, must be less than or equal to 1<<16") - } - - // algId, partyUInfo, partyVInfo inputs must be prefixed with the length - algID := lengthPrefixed([]byte(alg)) - ptyUInfo := lengthPrefixed(apuData) - ptyVInfo := lengthPrefixed(apvData) - - // suppPubInfo is the encoded length of the output size in bits - supPubInfo := make([]byte, 4) - binary.BigEndian.PutUint32(supPubInfo, uint32(size)*8) - - if !priv.PublicKey.Curve.IsOnCurve(pub.X, pub.Y) { - panic("public key not on same curve as private key") - } - - z, _ := priv.Curve.ScalarMult(pub.X, pub.Y, priv.D.Bytes()) - zBytes := z.Bytes() - - // Note that calling z.Bytes() on a big.Int may strip leading zero bytes from - // the returned byte array. This can lead to a problem where zBytes will be - // shorter than expected which breaks the key derivation. Therefore we must pad - // to the full length of the expected coordinate here before calling the KDF. - octSize := dSize(priv.Curve) - if len(zBytes) != octSize { - zBytes = append(bytes.Repeat([]byte{0}, octSize-len(zBytes)), zBytes...) - } - - reader := NewConcatKDF(crypto.SHA256, zBytes, algID, ptyUInfo, ptyVInfo, supPubInfo, []byte{}) - key := make([]byte, size) - - // Read on the KDF will never fail - _, _ = reader.Read(key) - - return key -} - -// dSize returns the size in octets for a coordinate on a elliptic curve. -func dSize(curve elliptic.Curve) int { - order := curve.Params().P - bitLen := order.BitLen() - size := bitLen / 8 - if bitLen%8 != 0 { - size++ - } - return size -} - -func lengthPrefixed(data []byte) []byte { - out := make([]byte, len(data)+4) - binary.BigEndian.PutUint32(out, uint32(len(data))) - copy(out[4:], data) - return out -} diff --git a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go b/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go deleted file mode 100644 index a2f86e3db9..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/cipher/key_wrap.go +++ /dev/null @@ -1,117 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 josecipher - -import ( - "crypto/cipher" - "crypto/subtle" - "encoding/binary" - "errors" -) - -var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6} - -// KeyWrap implements NIST key wrapping; it wraps a content encryption key (cek) with the given block cipher. -func KeyWrap(block cipher.Block, cek []byte) ([]byte, error) { - if len(cek)%8 != 0 { - return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") - } - - n := len(cek) / 8 - r := make([][]byte, n) - - for i := range r { - r[i] = make([]byte, 8) - copy(r[i], cek[i*8:]) - } - - buffer := make([]byte, 16) - tBytes := make([]byte, 8) - copy(buffer, defaultIV) - - for t := 0; t < 6*n; t++ { - copy(buffer[8:], r[t%n]) - - block.Encrypt(buffer, buffer) - - binary.BigEndian.PutUint64(tBytes, uint64(t+1)) - - for i := 0; i < 8; i++ { - buffer[i] ^= tBytes[i] - } - copy(r[t%n], buffer[8:]) - } - - out := make([]byte, (n+1)*8) - copy(out, buffer[:8]) - for i := range r { - copy(out[(i+1)*8:], r[i]) - } - - return out, nil -} - -// KeyUnwrap implements NIST key unwrapping; it unwraps a content encryption key (cek) with the given block cipher. -// -// https://datatracker.ietf.org/doc/html/rfc7518#section-4.4 -// https://datatracker.ietf.org/doc/html/rfc7518#section-4.6 -// https://datatracker.ietf.org/doc/html/rfc7518#section-4.8 -func KeyUnwrap(block cipher.Block, ciphertext []byte) ([]byte, error) { - n := (len(ciphertext) / 8) - 1 - if n <= 0 { - return nil, errors.New("go-jose/go-jose: JWE Encrypted Key too short") - } - - if len(ciphertext)%8 != 0 { - return nil, errors.New("go-jose/go-jose: key wrap input must be 8 byte blocks") - } - - r := make([][]byte, n) - - for i := range r { - r[i] = make([]byte, 8) - copy(r[i], ciphertext[(i+1)*8:]) - } - - buffer := make([]byte, 16) - tBytes := make([]byte, 8) - copy(buffer[:8], ciphertext[:8]) - - for t := 6*n - 1; t >= 0; t-- { - binary.BigEndian.PutUint64(tBytes, uint64(t+1)) - - for i := 0; i < 8; i++ { - buffer[i] ^= tBytes[i] - } - copy(buffer[8:], r[t%n]) - - block.Decrypt(buffer, buffer) - - copy(r[t%n], buffer[8:]) - } - - if subtle.ConstantTimeCompare(buffer[:8], defaultIV) == 0 { - return nil, errors.New("go-jose/go-jose: failed to unwrap key") - } - - out := make([]byte, n*8) - for i := range r { - copy(out[i*8:], r[i]) - } - - return out, nil -} diff --git a/vendor/github.com/go-jose/go-jose/v4/crypter.go b/vendor/github.com/go-jose/go-jose/v4/crypter.go deleted file mode 100644 index 31290fc871..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/crypter.go +++ /dev/null @@ -1,595 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 jose - -import ( - "crypto/ecdsa" - "crypto/rsa" - "errors" - "fmt" - - "github.com/go-jose/go-jose/v4/json" -) - -// Encrypter represents an encrypter which produces an encrypted JWE object. -type Encrypter interface { - Encrypt(plaintext []byte) (*JSONWebEncryption, error) - EncryptWithAuthData(plaintext []byte, aad []byte) (*JSONWebEncryption, error) - Options() EncrypterOptions -} - -// A generic content cipher -type contentCipher interface { - keySize() int - encrypt(cek []byte, aad, plaintext []byte) (*aeadParts, error) - decrypt(cek []byte, aad []byte, parts *aeadParts) ([]byte, error) -} - -// A key generator (for generating/getting a CEK) -type keyGenerator interface { - keySize() int - genKey() ([]byte, rawHeader, error) -} - -// A generic key encrypter -type keyEncrypter interface { - encryptKey(cek []byte, alg KeyAlgorithm) (recipientInfo, error) // Encrypt a key -} - -// A generic key decrypter -type keyDecrypter interface { - decryptKey(headers rawHeader, recipient *recipientInfo, generator keyGenerator) ([]byte, error) // Decrypt a key -} - -// A generic encrypter based on the given key encrypter and content cipher. -type genericEncrypter struct { - contentAlg ContentEncryption - compressionAlg CompressionAlgorithm - cipher contentCipher - recipients []recipientKeyInfo - keyGenerator keyGenerator - extraHeaders map[HeaderKey]interface{} -} - -type recipientKeyInfo struct { - keyID string - keyAlg KeyAlgorithm - keyEncrypter keyEncrypter -} - -// EncrypterOptions represents options that can be set on new encrypters. -type EncrypterOptions struct { - Compression CompressionAlgorithm - - // Optional map of name/value pairs to be inserted into the protected - // header of a JWS object. Some specifications which make use of - // JWS require additional values here. - // - // Values will be serialized by [json.Marshal] and must be valid inputs to - // that function. - // - // [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal - ExtraHeaders map[HeaderKey]interface{} -} - -// WithHeader adds an arbitrary value to the ExtraHeaders map, initializing it -// if necessary, and returns the updated EncrypterOptions. -// -// The v parameter will be serialized by [json.Marshal] and must be a valid -// input to that function. -// -// [json.Marshal]: https://pkg.go.dev/encoding/json#Marshal -func (eo *EncrypterOptions) WithHeader(k HeaderKey, v interface{}) *EncrypterOptions { - if eo.ExtraHeaders == nil { - eo.ExtraHeaders = map[HeaderKey]interface{}{} - } - eo.ExtraHeaders[k] = v - return eo -} - -// WithContentType adds a content type ("cty") header and returns the updated -// EncrypterOptions. -func (eo *EncrypterOptions) WithContentType(contentType ContentType) *EncrypterOptions { - return eo.WithHeader(HeaderContentType, contentType) -} - -// WithType adds a type ("typ") header and returns the updated EncrypterOptions. -func (eo *EncrypterOptions) WithType(typ ContentType) *EncrypterOptions { - return eo.WithHeader(HeaderType, typ) -} - -// Recipient represents an algorithm/key to encrypt messages to. -// -// PBES2Count and PBES2Salt correspond with the "p2c" and "p2s" headers used -// on the password-based encryption algorithms PBES2-HS256+A128KW, -// PBES2-HS384+A192KW, and PBES2-HS512+A256KW. If they are not provided a safe -// default of 100000 will be used for the count and a 128-bit random salt will -// be generated. -type Recipient struct { - Algorithm KeyAlgorithm - // Key must have one of these types: - // - ed25519.PublicKey - // - *ecdsa.PublicKey - // - *rsa.PublicKey - // - *JSONWebKey - // - JSONWebKey - // - []byte (a symmetric key) - // - Any type that satisfies the OpaqueKeyEncrypter interface - // - // The type of Key must match the value of Algorithm. - Key interface{} - KeyID string - PBES2Count int - PBES2Salt []byte -} - -// NewEncrypter creates an appropriate encrypter based on the key type -func NewEncrypter(enc ContentEncryption, rcpt Recipient, opts *EncrypterOptions) (Encrypter, error) { - encrypter := &genericEncrypter{ - contentAlg: enc, - recipients: []recipientKeyInfo{}, - cipher: getContentCipher(enc), - } - if opts != nil { - encrypter.compressionAlg = opts.Compression - encrypter.extraHeaders = opts.ExtraHeaders - } - - if encrypter.cipher == nil { - return nil, ErrUnsupportedAlgorithm - } - - var keyID string - var rawKey interface{} - switch encryptionKey := rcpt.Key.(type) { - case JSONWebKey: - keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key - case *JSONWebKey: - keyID, rawKey = encryptionKey.KeyID, encryptionKey.Key - case OpaqueKeyEncrypter: - keyID, rawKey = encryptionKey.KeyID(), encryptionKey - default: - rawKey = encryptionKey - } - - switch rcpt.Algorithm { - case DIRECT: - // Direct encryption mode must be treated differently - keyBytes, ok := rawKey.([]byte) - if !ok { - return nil, ErrUnsupportedKeyType - } - if encrypter.cipher.keySize() != len(keyBytes) { - return nil, ErrInvalidKeySize - } - encrypter.keyGenerator = staticKeyGenerator{ - key: keyBytes, - } - recipientInfo, _ := newSymmetricRecipient(rcpt.Algorithm, keyBytes) - recipientInfo.keyID = keyID - if rcpt.KeyID != "" { - recipientInfo.keyID = rcpt.KeyID - } - encrypter.recipients = []recipientKeyInfo{recipientInfo} - return encrypter, nil - case ECDH_ES: - // ECDH-ES (w/o key wrapping) is similar to DIRECT mode - keyDSA, ok := rawKey.(*ecdsa.PublicKey) - if !ok { - return nil, ErrUnsupportedKeyType - } - encrypter.keyGenerator = ecKeyGenerator{ - size: encrypter.cipher.keySize(), - algID: string(enc), - publicKey: keyDSA, - } - recipientInfo, _ := newECDHRecipient(rcpt.Algorithm, keyDSA) - recipientInfo.keyID = keyID - if rcpt.KeyID != "" { - recipientInfo.keyID = rcpt.KeyID - } - encrypter.recipients = []recipientKeyInfo{recipientInfo} - return encrypter, nil - default: - // Can just add a standard recipient - encrypter.keyGenerator = randomKeyGenerator{ - size: encrypter.cipher.keySize(), - } - err := encrypter.addRecipient(rcpt) - return encrypter, err - } -} - -// NewMultiEncrypter creates a multi-encrypter based on the given parameters -func NewMultiEncrypter(enc ContentEncryption, rcpts []Recipient, opts *EncrypterOptions) (Encrypter, error) { - cipher := getContentCipher(enc) - - if cipher == nil { - return nil, ErrUnsupportedAlgorithm - } - if len(rcpts) == 0 { - return nil, fmt.Errorf("go-jose/go-jose: recipients is nil or empty") - } - - encrypter := &genericEncrypter{ - contentAlg: enc, - recipients: []recipientKeyInfo{}, - cipher: cipher, - keyGenerator: randomKeyGenerator{ - size: cipher.keySize(), - }, - } - - if opts != nil { - encrypter.compressionAlg = opts.Compression - encrypter.extraHeaders = opts.ExtraHeaders - } - - for _, recipient := range rcpts { - err := encrypter.addRecipient(recipient) - if err != nil { - return nil, err - } - } - - return encrypter, nil -} - -func (ctx *genericEncrypter) addRecipient(recipient Recipient) (err error) { - var recipientInfo recipientKeyInfo - - switch recipient.Algorithm { - case DIRECT, ECDH_ES: - return fmt.Errorf("go-jose/go-jose: key algorithm '%s' not supported in multi-recipient mode", recipient.Algorithm) - } - - recipientInfo, err = makeJWERecipient(recipient.Algorithm, recipient.Key) - if recipient.KeyID != "" { - recipientInfo.keyID = recipient.KeyID - } - - switch recipient.Algorithm { - case PBES2_HS256_A128KW, PBES2_HS384_A192KW, PBES2_HS512_A256KW: - if sr, ok := recipientInfo.keyEncrypter.(*symmetricKeyCipher); ok { - sr.p2c = recipient.PBES2Count - sr.p2s = recipient.PBES2Salt - } - } - - if err == nil { - ctx.recipients = append(ctx.recipients, recipientInfo) - } - return err -} - -func makeJWERecipient(alg KeyAlgorithm, encryptionKey interface{}) (recipientKeyInfo, error) { - switch encryptionKey := encryptionKey.(type) { - case *rsa.PublicKey: - return newRSARecipient(alg, encryptionKey) - case *ecdsa.PublicKey: - return newECDHRecipient(alg, encryptionKey) - case []byte: - return newSymmetricRecipient(alg, encryptionKey) - case string: - return newSymmetricRecipient(alg, []byte(encryptionKey)) - case JSONWebKey: - recipient, err := makeJWERecipient(alg, encryptionKey.Key) - recipient.keyID = encryptionKey.KeyID - return recipient, err - case *JSONWebKey: - recipient, err := makeJWERecipient(alg, encryptionKey.Key) - recipient.keyID = encryptionKey.KeyID - return recipient, err - case OpaqueKeyEncrypter: - return newOpaqueKeyEncrypter(alg, encryptionKey) - } - return recipientKeyInfo{}, ErrUnsupportedKeyType -} - -// newDecrypter creates an appropriate decrypter based on the key type -func newDecrypter(decryptionKey interface{}) (keyDecrypter, error) { - switch decryptionKey := decryptionKey.(type) { - case *rsa.PrivateKey: - return &rsaDecrypterSigner{ - privateKey: decryptionKey, - }, nil - case *ecdsa.PrivateKey: - return &ecDecrypterSigner{ - privateKey: decryptionKey, - }, nil - case []byte: - return &symmetricKeyCipher{ - key: decryptionKey, - }, nil - case string: - return &symmetricKeyCipher{ - key: []byte(decryptionKey), - }, nil - case JSONWebKey: - return newDecrypter(decryptionKey.Key) - case *JSONWebKey: - return newDecrypter(decryptionKey.Key) - case OpaqueKeyDecrypter: - return &opaqueKeyDecrypter{decrypter: decryptionKey}, nil - default: - return nil, ErrUnsupportedKeyType - } -} - -// Implementation of encrypt method producing a JWE object. -func (ctx *genericEncrypter) Encrypt(plaintext []byte) (*JSONWebEncryption, error) { - return ctx.EncryptWithAuthData(plaintext, nil) -} - -// Implementation of encrypt method producing a JWE object. -func (ctx *genericEncrypter) EncryptWithAuthData(plaintext, aad []byte) (*JSONWebEncryption, error) { - obj := &JSONWebEncryption{} - obj.aad = aad - - obj.protected = &rawHeader{} - err := obj.protected.set(headerEncryption, ctx.contentAlg) - if err != nil { - return nil, err - } - - obj.recipients = make([]recipientInfo, len(ctx.recipients)) - - if len(ctx.recipients) == 0 { - return nil, fmt.Errorf("go-jose/go-jose: no recipients to encrypt to") - } - - cek, headers, err := ctx.keyGenerator.genKey() - if err != nil { - return nil, err - } - - obj.protected.merge(&headers) - - for i, info := range ctx.recipients { - recipient, err := info.keyEncrypter.encryptKey(cek, info.keyAlg) - if err != nil { - return nil, err - } - - err = recipient.header.set(headerAlgorithm, info.keyAlg) - if err != nil { - return nil, err - } - - if info.keyID != "" { - err = recipient.header.set(headerKeyID, info.keyID) - if err != nil { - return nil, err - } - } - obj.recipients[i] = recipient - } - - if len(ctx.recipients) == 1 { - // Move per-recipient headers into main protected header if there's - // only a single recipient. - obj.protected.merge(obj.recipients[0].header) - obj.recipients[0].header = nil - } - - if ctx.compressionAlg != NONE { - plaintext, err = compress(ctx.compressionAlg, plaintext) - if err != nil { - return nil, err - } - - err = obj.protected.set(headerCompression, ctx.compressionAlg) - if err != nil { - return nil, err - } - } - - for k, v := range ctx.extraHeaders { - b, err := json.Marshal(v) - if err != nil { - return nil, err - } - (*obj.protected)[k] = makeRawMessage(b) - } - - authData := obj.computeAuthData() - parts, err := ctx.cipher.encrypt(cek, authData, plaintext) - if err != nil { - return nil, err - } - - obj.iv = parts.iv - obj.ciphertext = parts.ciphertext - obj.tag = parts.tag - - return obj, nil -} - -func (ctx *genericEncrypter) Options() EncrypterOptions { - return EncrypterOptions{ - Compression: ctx.compressionAlg, - ExtraHeaders: ctx.extraHeaders, - } -} - -// Decrypt and validate the object and return the plaintext. This -// function does not support multi-recipient. If you desire multi-recipient -// decryption use DecryptMulti instead. -// -// The decryptionKey argument must contain a private or symmetric key -// and must have one of these types: -// - *ecdsa.PrivateKey -// - *rsa.PrivateKey -// - *JSONWebKey -// - JSONWebKey -// - *JSONWebKeySet -// - JSONWebKeySet -// - []byte (a symmetric key) -// - string (a symmetric key) -// - Any type that satisfies the OpaqueKeyDecrypter interface. -// -// Note that ed25519 is only available for signatures, not encryption, so is -// not an option here. -// -// Automatically decompresses plaintext, but returns an error if the decompressed -// data would be >250kB or >10x the size of the compressed data, whichever is larger. -func (obj JSONWebEncryption) Decrypt(decryptionKey interface{}) ([]byte, error) { - headers := obj.mergedHeaders(nil) - - if len(obj.recipients) > 1 { - return nil, errors.New("go-jose/go-jose: too many recipients in payload; expecting only one") - } - - err := headers.checkNoCritical() - if err != nil { - return nil, err - } - - key, err := tryJWKS(decryptionKey, obj.Header) - if err != nil { - return nil, err - } - decrypter, err := newDecrypter(key) - if err != nil { - return nil, err - } - - cipher := getContentCipher(headers.getEncryption()) - if cipher == nil { - return nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(headers.getEncryption())) - } - - generator := randomKeyGenerator{ - size: cipher.keySize(), - } - - parts := &aeadParts{ - iv: obj.iv, - ciphertext: obj.ciphertext, - tag: obj.tag, - } - - authData := obj.computeAuthData() - - var plaintext []byte - recipient := obj.recipients[0] - recipientHeaders := obj.mergedHeaders(&recipient) - - cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) - if err == nil { - // Found a valid CEK -- let's try to decrypt. - plaintext, err = cipher.decrypt(cek, authData, parts) - } - - if plaintext == nil { - return nil, ErrCryptoFailure - } - - // The "zip" header parameter may only be present in the protected header. - if comp := obj.protected.getCompression(); comp != "" { - plaintext, err = decompress(comp, plaintext) - if err != nil { - return nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) - } - } - - return plaintext, nil -} - -// DecryptMulti decrypts and validates the object and returns the plaintexts, -// with support for multiple recipients. It returns the index of the recipient -// for which the decryption was successful, the merged headers for that recipient, -// and the plaintext. -// -// The decryptionKey argument must have one of the types allowed for the -// decryptionKey argument of Decrypt(). -// -// Automatically decompresses plaintext, but returns an error if the decompressed -// data would be >250kB or >3x the size of the compressed data, whichever is larger. -func (obj JSONWebEncryption) DecryptMulti(decryptionKey interface{}) (int, Header, []byte, error) { - globalHeaders := obj.mergedHeaders(nil) - - err := globalHeaders.checkNoCritical() - if err != nil { - return -1, Header{}, nil, err - } - - key, err := tryJWKS(decryptionKey, obj.Header) - if err != nil { - return -1, Header{}, nil, err - } - decrypter, err := newDecrypter(key) - if err != nil { - return -1, Header{}, nil, err - } - - encryption := globalHeaders.getEncryption() - cipher := getContentCipher(encryption) - if cipher == nil { - return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: unsupported enc value '%s'", string(encryption)) - } - - generator := randomKeyGenerator{ - size: cipher.keySize(), - } - - parts := &aeadParts{ - iv: obj.iv, - ciphertext: obj.ciphertext, - tag: obj.tag, - } - - authData := obj.computeAuthData() - - index := -1 - var plaintext []byte - var headers rawHeader - - for i, recipient := range obj.recipients { - recipientHeaders := obj.mergedHeaders(&recipient) - - cek, err := decrypter.decryptKey(recipientHeaders, &recipient, generator) - if err == nil { - // Found a valid CEK -- let's try to decrypt. - plaintext, err = cipher.decrypt(cek, authData, parts) - if err == nil { - index = i - headers = recipientHeaders - break - } - } - } - - if plaintext == nil { - return -1, Header{}, nil, ErrCryptoFailure - } - - // The "zip" header parameter may only be present in the protected header. - if comp := obj.protected.getCompression(); comp != "" { - plaintext, err = decompress(comp, plaintext) - if err != nil { - return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to decompress plaintext: %v", err) - } - } - - sanitized, err := headers.sanitized() - if err != nil { - return -1, Header{}, nil, fmt.Errorf("go-jose/go-jose: failed to sanitize header: %v", err) - } - - return index, sanitized, plaintext, err -} diff --git a/vendor/github.com/go-jose/go-jose/v4/doc.go b/vendor/github.com/go-jose/go-jose/v4/doc.go deleted file mode 100644 index 0ad40ca085..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/doc.go +++ /dev/null @@ -1,25 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 jose aims to provide an implementation of the Javascript Object Signing -and Encryption set of standards. It implements encryption and signing based on -the JSON Web Encryption and JSON Web Signature standards, with optional JSON Web -Token support available in a sub-package. The library supports both the compact -and JWS/JWE JSON Serialization formats, and has optional support for multiple -recipients. -*/ -package jose diff --git a/vendor/github.com/go-jose/go-jose/v4/encoding.go b/vendor/github.com/go-jose/go-jose/v4/encoding.go deleted file mode 100644 index 4f6e0d4a5c..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/encoding.go +++ /dev/null @@ -1,228 +0,0 @@ -/*- - * Copyright 2014 Square Inc. - * - * 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 jose - -import ( - "bytes" - "compress/flate" - "encoding/base64" - "encoding/binary" - "fmt" - "io" - "math/big" - "strings" - "unicode" - - "github.com/go-jose/go-jose/v4/json" -) - -// Helper function to serialize known-good objects. -// Precondition: value is not a nil pointer. -func mustSerializeJSON(value interface{}) []byte { - out, err := json.Marshal(value) - if err != nil { - panic(err) - } - // We never want to serialize the top-level value "null," since it's not a - // valid JOSE message. But if a caller passes in a nil pointer to this method, - // MarshalJSON will happily serialize it as the top-level value "null". If - // that value is then embedded in another operation, for instance by being - // base64-encoded and fed as input to a signing algorithm - // (https://github.com/go-jose/go-jose/issues/22), the result will be - // incorrect. Because this method is intended for known-good objects, and a nil - // pointer is not a known-good object, we are free to panic in this case. - // Note: It's not possible to directly check whether the data pointed at by an - // interface is a nil pointer, so we do this hacky workaround. - // https://groups.google.com/forum/#!topic/golang-nuts/wnH302gBa4I - if string(out) == "null" { - panic("Tried to serialize a nil pointer.") - } - return out -} - -// Strip all newlines and whitespace -func stripWhitespace(data string) string { - buf := strings.Builder{} - buf.Grow(len(data)) - for _, r := range data { - if !unicode.IsSpace(r) { - buf.WriteRune(r) - } - } - return buf.String() -} - -// Perform compression based on algorithm -func compress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) { - switch algorithm { - case DEFLATE: - return deflate(input) - default: - return nil, ErrUnsupportedAlgorithm - } -} - -// Perform decompression based on algorithm -func decompress(algorithm CompressionAlgorithm, input []byte) ([]byte, error) { - switch algorithm { - case DEFLATE: - return inflate(input) - default: - return nil, ErrUnsupportedAlgorithm - } -} - -// deflate compresses the input. -func deflate(input []byte) ([]byte, error) { - output := new(bytes.Buffer) - - // Writing to byte buffer, err is always nil - writer, _ := flate.NewWriter(output, 1) - _, _ = io.Copy(writer, bytes.NewBuffer(input)) - - err := writer.Close() - return output.Bytes(), err -} - -// inflate decompresses the input. -// -// Errors if the decompressed data would be >250kB or >10x the size of the -// compressed data, whichever is larger. -func inflate(input []byte) ([]byte, error) { - output := new(bytes.Buffer) - reader := flate.NewReader(bytes.NewBuffer(input)) - - maxCompressedSize := max(250_000, 10*int64(len(input))) - - limit := maxCompressedSize + 1 - n, err := io.CopyN(output, reader, limit) - if err != nil && err != io.EOF { - return nil, err - } - if n == limit { - return nil, fmt.Errorf("uncompressed data would be too large (>%d bytes)", maxCompressedSize) - } - - err = reader.Close() - return output.Bytes(), err -} - -// byteBuffer represents a slice of bytes that can be serialized to url-safe base64. -type byteBuffer struct { - data []byte -} - -func newBuffer(data []byte) *byteBuffer { - if data == nil { - return nil - } - return &byteBuffer{ - data: data, - } -} - -func newFixedSizeBuffer(data []byte, length int) *byteBuffer { - if len(data) > length { - panic("go-jose/go-jose: invalid call to newFixedSizeBuffer (len(data) > length)") - } - pad := make([]byte, length-len(data)) - return newBuffer(append(pad, data...)) -} - -func newBufferFromInt(num uint64) *byteBuffer { - data := make([]byte, 8) - binary.BigEndian.PutUint64(data, num) - return newBuffer(bytes.TrimLeft(data, "\x00")) -} - -func (b *byteBuffer) MarshalJSON() ([]byte, error) { - return json.Marshal(b.base64()) -} - -func (b *byteBuffer) UnmarshalJSON(data []byte) error { - var encoded string - err := json.Unmarshal(data, &encoded) - if err != nil { - return err - } - - if encoded == "" { - return nil - } - - decoded, err := base64.RawURLEncoding.DecodeString(encoded) - if err != nil { - return err - } - - *b = *newBuffer(decoded) - - return nil -} - -func (b *byteBuffer) base64() string { - return base64.RawURLEncoding.EncodeToString(b.data) -} - -func (b *byteBuffer) bytes() []byte { - // Handling nil here allows us to transparently handle nil slices when serializing. - if b == nil { - return nil - } - return b.data -} - -func (b byteBuffer) bigInt() *big.Int { - return new(big.Int).SetBytes(b.data) -} - -func (b byteBuffer) toInt() int { - return int(b.bigInt().Int64()) -} - -func base64EncodeLen(sl []byte) int { - return base64.RawURLEncoding.EncodedLen(len(sl)) -} - -func base64JoinWithDots(inputs ...[]byte) string { - if len(inputs) == 0 { - return "" - } - - // Count of dots. - totalCount := len(inputs) - 1 - - for _, input := range inputs { - totalCount += base64EncodeLen(input) - } - - out := make([]byte, totalCount) - startEncode := 0 - for i, input := range inputs { - base64.RawURLEncoding.Encode(out[startEncode:], input) - - if i == len(inputs)-1 { - continue - } - - startEncode += base64EncodeLen(input) - out[startEncode] = '.' - startEncode++ - } - - return string(out) -} diff --git a/vendor/github.com/go-jose/go-jose/v4/json/README.md b/vendor/github.com/go-jose/go-jose/v4/json/README.md deleted file mode 100644 index 86de5e5581..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/json/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Safe JSON - -This repository contains a fork of the `encoding/json` package from Go 1.6. - -The following changes were made: - -* Object deserialization uses case-sensitive member name matching instead of - [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html). - This is to avoid differences in the interpretation of JOSE messages between - go-jose and libraries written in other languages. -* When deserializing a JSON object, we check for duplicate keys and reject the - input whenever we detect a duplicate. Rather than trying to work with malformed - data, we prefer to reject it right away. diff --git a/vendor/github.com/go-jose/go-jose/v4/json/decode.go b/vendor/github.com/go-jose/go-jose/v4/json/decode.go deleted file mode 100644 index 50634dd847..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/json/decode.go +++ /dev/null @@ -1,1216 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Represents JSON data structure using native Go types: booleans, floats, -// strings, arrays, and maps. - -package json - -import ( - "bytes" - "encoding" - "encoding/base64" - "errors" - "fmt" - "math" - "reflect" - "runtime" - "strconv" - "unicode" - "unicode/utf16" - "unicode/utf8" -) - -// Unmarshal parses the JSON-encoded data and stores the result -// in the value pointed to by v. -// -// Unmarshal uses the inverse of the encodings that -// Marshal uses, allocating maps, slices, and pointers as necessary, -// with the following additional rules: -// -// To unmarshal JSON into a pointer, Unmarshal first handles the case of -// the JSON being the JSON literal null. In that case, Unmarshal sets -// the pointer to nil. Otherwise, Unmarshal unmarshals the JSON into -// the value pointed at by the pointer. If the pointer is nil, Unmarshal -// allocates a new value for it to point to. -// -// To unmarshal JSON into a struct, Unmarshal matches incoming object -// keys to the keys used by Marshal (either the struct field name or its tag), -// preferring an exact match but also accepting a case-insensitive match. -// Unmarshal will only set exported fields of the struct. -// -// To unmarshal JSON into an interface value, -// Unmarshal stores one of these in the interface value: -// -// bool, for JSON booleans -// float64, for JSON numbers -// string, for JSON strings -// []interface{}, for JSON arrays -// map[string]interface{}, for JSON objects -// nil for JSON null -// -// To unmarshal a JSON array into a slice, Unmarshal resets the slice length -// to zero and then appends each element to the slice. -// As a special case, to unmarshal an empty JSON array into a slice, -// Unmarshal replaces the slice with a new empty slice. -// -// To unmarshal a JSON array into a Go array, Unmarshal decodes -// JSON array elements into corresponding Go array elements. -// If the Go array is smaller than the JSON array, -// the additional JSON array elements are discarded. -// If the JSON array is smaller than the Go array, -// the additional Go array elements are set to zero values. -// -// To unmarshal a JSON object into a string-keyed map, Unmarshal first -// establishes a map to use, If the map is nil, Unmarshal allocates a new map. -// Otherwise Unmarshal reuses the existing map, keeping existing entries. -// Unmarshal then stores key-value pairs from the JSON object into the map. -// -// If a JSON value is not appropriate for a given target type, -// or if a JSON number overflows the target type, Unmarshal -// skips that field and completes the unmarshaling as best it can. -// If no more serious errors are encountered, Unmarshal returns -// an UnmarshalTypeError describing the earliest such error. -// -// The JSON null value unmarshals into an interface, map, pointer, or slice -// by setting that Go value to nil. Because null is often used in JSON to mean -// “not present,” unmarshaling a JSON null into any other Go type has no effect -// on the value and produces no error. -// -// When unmarshaling quoted strings, invalid UTF-8 or -// invalid UTF-16 surrogate pairs are not treated as an error. -// Instead, they are replaced by the Unicode replacement -// character U+FFFD. -func Unmarshal(data []byte, v interface{}) error { - // Check for well-formedness. - // Avoids filling out half a data structure - // before discovering a JSON syntax error. - var d decodeState - err := checkValid(data, &d.scan) - if err != nil { - return err - } - - d.init(data) - return d.unmarshal(v) -} - -// Unmarshaler is the interface implemented by objects -// that can unmarshal a JSON description of themselves. -// The input can be assumed to be a valid encoding of -// a JSON value. UnmarshalJSON must copy the JSON data -// if it wishes to retain the data after returning. -type Unmarshaler interface { - UnmarshalJSON([]byte) error -} - -// An UnmarshalTypeError describes a JSON value that was -// not appropriate for a value of a specific Go type. -type UnmarshalTypeError struct { - Value string // description of JSON value - "bool", "array", "number -5" - Type reflect.Type // type of Go value it could not be assigned to - Offset int64 // error occurred after reading Offset bytes -} - -func (e *UnmarshalTypeError) Error() string { - return "json: cannot unmarshal " + e.Value + " into Go value of type " + e.Type.String() -} - -// An UnmarshalFieldError describes a JSON object key that -// led to an unexported (and therefore unwritable) struct field. -// (No longer used; kept for compatibility.) -type UnmarshalFieldError struct { - Key string - Type reflect.Type - Field reflect.StructField -} - -func (e *UnmarshalFieldError) Error() string { - return "json: cannot unmarshal object key " + strconv.Quote(e.Key) + " into unexported field " + e.Field.Name + " of type " + e.Type.String() -} - -// An InvalidUnmarshalError describes an invalid argument passed to Unmarshal. -// (The argument to Unmarshal must be a non-nil pointer.) -type InvalidUnmarshalError struct { - Type reflect.Type -} - -func (e *InvalidUnmarshalError) Error() string { - if e.Type == nil { - return "json: Unmarshal(nil)" - } - - if e.Type.Kind() != reflect.Ptr { - return "json: Unmarshal(non-pointer " + e.Type.String() + ")" - } - return "json: Unmarshal(nil " + e.Type.String() + ")" -} - -func (d *decodeState) unmarshal(v interface{}) (err error) { - defer func() { - if r := recover(); r != nil { - if _, ok := r.(runtime.Error); ok { - panic(r) - } - err = r.(error) - } - }() - - rv := reflect.ValueOf(v) - if rv.Kind() != reflect.Ptr || rv.IsNil() { - return &InvalidUnmarshalError{reflect.TypeOf(v)} - } - - d.scan.reset() - // We decode rv not rv.Elem because the Unmarshaler interface - // test must be applied at the top level of the value. - d.value(rv) - return d.savedError -} - -// A Number represents a JSON number literal. -type Number string - -// String returns the literal text of the number. -func (n Number) String() string { return string(n) } - -// Float64 returns the number as a float64. -func (n Number) Float64() (float64, error) { - return strconv.ParseFloat(string(n), 64) -} - -// Int64 returns the number as an int64. -func (n Number) Int64() (int64, error) { - return strconv.ParseInt(string(n), 10, 64) -} - -// isValidNumber reports whether s is a valid JSON number literal. -func isValidNumber(s string) bool { - // This function implements the JSON numbers grammar. - // See https://tools.ietf.org/html/rfc7159#section-6 - // and http://json.org/number.gif - - if s == "" { - return false - } - - // Optional - - if s[0] == '-' { - s = s[1:] - if s == "" { - return false - } - } - - // Digits - switch { - default: - return false - - case s[0] == '0': - s = s[1:] - - case '1' <= s[0] && s[0] <= '9': - s = s[1:] - for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { - s = s[1:] - } - } - - // . followed by 1 or more digits. - if len(s) >= 2 && s[0] == '.' && '0' <= s[1] && s[1] <= '9' { - s = s[2:] - for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { - s = s[1:] - } - } - - // e or E followed by an optional - or + and - // 1 or more digits. - if len(s) >= 2 && (s[0] == 'e' || s[0] == 'E') { - s = s[1:] - if s[0] == '+' || s[0] == '-' { - s = s[1:] - if s == "" { - return false - } - } - for len(s) > 0 && '0' <= s[0] && s[0] <= '9' { - s = s[1:] - } - } - - // Make sure we are at the end. - return s == "" -} - -type NumberUnmarshalType int - -const ( - // unmarshal a JSON number into an interface{} as a float64 - UnmarshalFloat NumberUnmarshalType = iota - // unmarshal a JSON number into an interface{} as a `json.Number` - UnmarshalJSONNumber - // unmarshal a JSON number into an interface{} as a int64 - // if value is an integer otherwise float64 - UnmarshalIntOrFloat -) - -// decodeState represents the state while decoding a JSON value. -type decodeState struct { - data []byte - off int // read offset in data - scan scanner - nextscan scanner // for calls to nextValue - savedError error - numberType NumberUnmarshalType -} - -// errPhase is used for errors that should not happen unless -// there is a bug in the JSON decoder or something is editing -// the data slice while the decoder executes. -var errPhase = errors.New("JSON decoder out of sync - data changing underfoot?") - -func (d *decodeState) init(data []byte) *decodeState { - d.data = data - d.off = 0 - d.savedError = nil - return d -} - -// error aborts the decoding by panicking with err. -func (d *decodeState) error(err error) { - panic(err) -} - -// saveError saves the first err it is called with, -// for reporting at the end of the unmarshal. -func (d *decodeState) saveError(err error) { - if d.savedError == nil { - d.savedError = err - } -} - -// next cuts off and returns the next full JSON value in d.data[d.off:]. -// The next value is known to be an object or array, not a literal. -func (d *decodeState) next() []byte { - c := d.data[d.off] - item, rest, err := nextValue(d.data[d.off:], &d.nextscan) - if err != nil { - d.error(err) - } - d.off = len(d.data) - len(rest) - - // Our scanner has seen the opening brace/bracket - // and thinks we're still in the middle of the object. - // invent a closing brace/bracket to get it out. - if c == '{' { - d.scan.step(&d.scan, '}') - } else { - d.scan.step(&d.scan, ']') - } - - return item -} - -// scanWhile processes bytes in d.data[d.off:] until it -// receives a scan code not equal to op. -// It updates d.off and returns the new scan code. -func (d *decodeState) scanWhile(op int) int { - var newOp int - for { - if d.off >= len(d.data) { - newOp = d.scan.eof() - d.off = len(d.data) + 1 // mark processed EOF with len+1 - } else { - c := d.data[d.off] - d.off++ - newOp = d.scan.step(&d.scan, c) - } - if newOp != op { - break - } - } - return newOp -} - -// value decodes a JSON value from d.data[d.off:] into the value. -// it updates d.off to point past the decoded value. -func (d *decodeState) value(v reflect.Value) { - if !v.IsValid() { - _, rest, err := nextValue(d.data[d.off:], &d.nextscan) - if err != nil { - d.error(err) - } - d.off = len(d.data) - len(rest) - - // d.scan thinks we're still at the beginning of the item. - // Feed in an empty string - the shortest, simplest value - - // so that it knows we got to the end of the value. - if d.scan.redo { - // rewind. - d.scan.redo = false - d.scan.step = stateBeginValue - } - d.scan.step(&d.scan, '"') - d.scan.step(&d.scan, '"') - - n := len(d.scan.parseState) - if n > 0 && d.scan.parseState[n-1] == parseObjectKey { - // d.scan thinks we just read an object key; finish the object - d.scan.step(&d.scan, ':') - d.scan.step(&d.scan, '"') - d.scan.step(&d.scan, '"') - d.scan.step(&d.scan, '}') - } - - return - } - - switch op := d.scanWhile(scanSkipSpace); op { - default: - d.error(errPhase) - - case scanBeginArray: - d.array(v) - - case scanBeginObject: - d.object(v) - - case scanBeginLiteral: - d.literal(v) - } -} - -type unquotedValue struct{} - -// valueQuoted is like value but decodes a -// quoted string literal or literal null into an interface value. -// If it finds anything other than a quoted string literal or null, -// valueQuoted returns unquotedValue{}. -func (d *decodeState) valueQuoted() interface{} { - switch op := d.scanWhile(scanSkipSpace); op { - default: - d.error(errPhase) - - case scanBeginArray: - d.array(reflect.Value{}) - - case scanBeginObject: - d.object(reflect.Value{}) - - case scanBeginLiteral: - switch v := d.literalInterface().(type) { - case nil, string: - return v - } - } - return unquotedValue{} -} - -// indirect walks down v allocating pointers as needed, -// until it gets to a non-pointer. -// if it encounters an Unmarshaler, indirect stops and returns that. -// if decodingNull is true, indirect stops at the last pointer so it can be set to nil. -func (d *decodeState) indirect(v reflect.Value, decodingNull bool) (Unmarshaler, encoding.TextUnmarshaler, reflect.Value) { - // If v is a named type and is addressable, - // start with its address, so that if the type has pointer methods, - // we find them. - if v.Kind() != reflect.Ptr && v.Type().Name() != "" && v.CanAddr() { - v = v.Addr() - } - for { - // Load value from interface, but only if the result will be - // usefully addressable. - if v.Kind() == reflect.Interface && !v.IsNil() { - e := v.Elem() - if e.Kind() == reflect.Ptr && !e.IsNil() && (!decodingNull || e.Elem().Kind() == reflect.Ptr) { - v = e - continue - } - } - - if v.Kind() != reflect.Ptr { - break - } - - if v.Elem().Kind() != reflect.Ptr && decodingNull && v.CanSet() { - break - } - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - if v.Type().NumMethod() > 0 { - if u, ok := v.Interface().(Unmarshaler); ok { - return u, nil, reflect.Value{} - } - if u, ok := v.Interface().(encoding.TextUnmarshaler); ok { - return nil, u, reflect.Value{} - } - } - v = v.Elem() - } - return nil, nil, v -} - -// array consumes an array from d.data[d.off-1:], decoding into the value v. -// the first byte of the array ('[') has been read already. -func (d *decodeState) array(v reflect.Value) { - // Check for unmarshaler. - u, ut, pv := d.indirect(v, false) - if u != nil { - d.off-- - err := u.UnmarshalJSON(d.next()) - if err != nil { - d.error(err) - } - return - } - if ut != nil { - d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)}) - d.off-- - d.next() - return - } - - v = pv - - // Check type of target. - switch v.Kind() { - case reflect.Interface: - if v.NumMethod() == 0 { - // Decoding into nil interface? Switch to non-reflect code. - v.Set(reflect.ValueOf(d.arrayInterface())) - return - } - // Otherwise it's invalid. - fallthrough - default: - d.saveError(&UnmarshalTypeError{"array", v.Type(), int64(d.off)}) - d.off-- - d.next() - return - case reflect.Array: - case reflect.Slice: - break - } - - i := 0 - for { - // Look ahead for ] - can only happen on first iteration. - op := d.scanWhile(scanSkipSpace) - if op == scanEndArray { - break - } - - // Back up so d.value can have the byte we just read. - d.off-- - d.scan.undo(op) - - // Get element of array, growing if necessary. - if v.Kind() == reflect.Slice { - // Grow slice if necessary - if i >= v.Cap() { - newcap := v.Cap() + v.Cap()/2 - if newcap < 4 { - newcap = 4 - } - newv := reflect.MakeSlice(v.Type(), v.Len(), newcap) - reflect.Copy(newv, v) - v.Set(newv) - } - if i >= v.Len() { - v.SetLen(i + 1) - } - } - - if i < v.Len() { - // Decode into element. - d.value(v.Index(i)) - } else { - // Ran out of fixed array: skip. - d.value(reflect.Value{}) - } - i++ - - // Next token must be , or ]. - op = d.scanWhile(scanSkipSpace) - if op == scanEndArray { - break - } - if op != scanArrayValue { - d.error(errPhase) - } - } - - if i < v.Len() { - if v.Kind() == reflect.Array { - // Array. Zero the rest. - z := reflect.Zero(v.Type().Elem()) - for ; i < v.Len(); i++ { - v.Index(i).Set(z) - } - } else { - v.SetLen(i) - } - } - if i == 0 && v.Kind() == reflect.Slice { - v.Set(reflect.MakeSlice(v.Type(), 0, 0)) - } -} - -var nullLiteral = []byte("null") - -// object consumes an object from d.data[d.off-1:], decoding into the value v. -// the first byte ('{') of the object has been read already. -func (d *decodeState) object(v reflect.Value) { - // Check for unmarshaler. - u, ut, pv := d.indirect(v, false) - if u != nil { - d.off-- - err := u.UnmarshalJSON(d.next()) - if err != nil { - d.error(err) - } - return - } - if ut != nil { - d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) - d.off-- - d.next() // skip over { } in input - return - } - v = pv - - // Decoding into nil interface? Switch to non-reflect code. - if v.Kind() == reflect.Interface && v.NumMethod() == 0 { - v.Set(reflect.ValueOf(d.objectInterface())) - return - } - - // Check type of target: struct or map[string]T - switch v.Kind() { - case reflect.Map: - // map must have string kind - t := v.Type() - if t.Key().Kind() != reflect.String { - d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) - d.off-- - d.next() // skip over { } in input - return - } - if v.IsNil() { - v.Set(reflect.MakeMap(t)) - } - case reflect.Struct: - - default: - d.saveError(&UnmarshalTypeError{"object", v.Type(), int64(d.off)}) - d.off-- - d.next() // skip over { } in input - return - } - - var mapElem reflect.Value - keys := map[string]bool{} - - for { - // Read opening " of string key or closing }. - op := d.scanWhile(scanSkipSpace) - if op == scanEndObject { - // closing } - can only happen on first iteration. - break - } - if op != scanBeginLiteral { - d.error(errPhase) - } - - // Read key. - start := d.off - 1 - op = d.scanWhile(scanContinue) - item := d.data[start : d.off-1] - key, ok := unquote(item) - if !ok { - d.error(errPhase) - } - - // Check for duplicate keys. - _, ok = keys[key] - if !ok { - keys[key] = true - } else { - d.error(fmt.Errorf("json: duplicate key '%s' in object", key)) - } - - // Figure out field corresponding to key. - var subv reflect.Value - destring := false // whether the value is wrapped in a string to be decoded first - - if v.Kind() == reflect.Map { - elemType := v.Type().Elem() - if !mapElem.IsValid() { - mapElem = reflect.New(elemType).Elem() - } else { - mapElem.Set(reflect.Zero(elemType)) - } - subv = mapElem - } else { - var f *field - fields := cachedTypeFields(v.Type()) - for i := range fields { - ff := &fields[i] - if bytes.Equal(ff.nameBytes, []byte(key)) { - f = ff - break - } - } - if f != nil { - subv = v - destring = f.quoted - for _, i := range f.index { - if subv.Kind() == reflect.Ptr { - if subv.IsNil() { - subv.Set(reflect.New(subv.Type().Elem())) - } - subv = subv.Elem() - } - subv = subv.Field(i) - } - } - } - - // Read : before value. - if op == scanSkipSpace { - op = d.scanWhile(scanSkipSpace) - } - if op != scanObjectKey { - d.error(errPhase) - } - - // Read value. - if destring { - switch qv := d.valueQuoted().(type) { - case nil: - d.literalStore(nullLiteral, subv, false) - case string: - d.literalStore([]byte(qv), subv, true) - default: - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal unquoted value into %v", subv.Type())) - } - } else { - d.value(subv) - } - - // Write value back to map; - // if using struct, subv points into struct already. - if v.Kind() == reflect.Map { - kv := reflect.ValueOf(key).Convert(v.Type().Key()) - v.SetMapIndex(kv, subv) - } - - // Next token must be , or }. - op = d.scanWhile(scanSkipSpace) - if op == scanEndObject { - break - } - if op != scanObjectValue { - d.error(errPhase) - } - } -} - -// literal consumes a literal from d.data[d.off-1:], decoding into the value v. -// The first byte of the literal has been read already -// (that's how the caller knows it's a literal). -func (d *decodeState) literal(v reflect.Value) { - // All bytes inside literal return scanContinue op code. - start := d.off - 1 - op := d.scanWhile(scanContinue) - - // Scan read one byte too far; back up. - d.off-- - d.scan.undo(op) - - d.literalStore(d.data[start:d.off], v, false) -} - -// convertNumber converts the number literal s to a float64, int64 or a Number -// depending on d.numberDecodeType. -func (d *decodeState) convertNumber(s string) (interface{}, error) { - switch d.numberType { - - case UnmarshalJSONNumber: - return Number(s), nil - case UnmarshalIntOrFloat: - v, err := strconv.ParseInt(s, 10, 64) - if err == nil { - return v, nil - } - - // tries to parse integer number in scientific notation - f, err := strconv.ParseFloat(s, 64) - if err != nil { - return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)} - } - - // if it has no decimal value use int64 - if fi, fd := math.Modf(f); fd == 0.0 { - return int64(fi), nil - } - return f, nil - default: - f, err := strconv.ParseFloat(s, 64) - if err != nil { - return nil, &UnmarshalTypeError{"number " + s, reflect.TypeOf(0.0), int64(d.off)} - } - return f, nil - } - -} - -var numberType = reflect.TypeOf(Number("")) - -// literalStore decodes a literal stored in item into v. -// -// fromQuoted indicates whether this literal came from unwrapping a -// string from the ",string" struct tag option. this is used only to -// produce more helpful error messages. -func (d *decodeState) literalStore(item []byte, v reflect.Value, fromQuoted bool) { - // Check for unmarshaler. - if len(item) == 0 { - //Empty string given - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - return - } - wantptr := item[0] == 'n' // null - u, ut, pv := d.indirect(v, wantptr) - if u != nil { - err := u.UnmarshalJSON(item) - if err != nil { - d.error(err) - } - return - } - if ut != nil { - if item[0] != '"' { - if fromQuoted { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) - } - return - } - s, ok := unquoteBytes(item) - if !ok { - if fromQuoted { - d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.error(errPhase) - } - } - err := ut.UnmarshalText(s) - if err != nil { - d.error(err) - } - return - } - - v = pv - - switch c := item[0]; c { - case 'n': // null - switch v.Kind() { - case reflect.Interface, reflect.Ptr, reflect.Map, reflect.Slice: - v.Set(reflect.Zero(v.Type())) - // otherwise, ignore null for primitives/string - } - case 't', 'f': // true, false - value := c == 't' - switch v.Kind() { - default: - if fromQuoted { - d.saveError(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)}) - } - case reflect.Bool: - v.SetBool(value) - case reflect.Interface: - if v.NumMethod() == 0 { - v.Set(reflect.ValueOf(value)) - } else { - d.saveError(&UnmarshalTypeError{"bool", v.Type(), int64(d.off)}) - } - } - - case '"': // string - s, ok := unquoteBytes(item) - if !ok { - if fromQuoted { - d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.error(errPhase) - } - } - switch v.Kind() { - default: - d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) - case reflect.Slice: - if v.Type().Elem().Kind() != reflect.Uint8 { - d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) - break - } - b := make([]byte, base64.StdEncoding.DecodedLen(len(s))) - n, err := base64.StdEncoding.Decode(b, s) - if err != nil { - d.saveError(err) - break - } - v.SetBytes(b[:n]) - case reflect.String: - v.SetString(string(s)) - case reflect.Interface: - if v.NumMethod() == 0 { - v.Set(reflect.ValueOf(string(s))) - } else { - d.saveError(&UnmarshalTypeError{"string", v.Type(), int64(d.off)}) - } - } - - default: // number - if c != '-' && (c < '0' || c > '9') { - if fromQuoted { - d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.error(errPhase) - } - } - s := string(item) - switch v.Kind() { - default: - if v.Kind() == reflect.String && v.Type() == numberType { - v.SetString(s) - if !isValidNumber(s) { - d.error(fmt.Errorf("json: invalid number literal, trying to unmarshal %q into Number", item)) - } - break - } - if fromQuoted { - d.error(fmt.Errorf("json: invalid use of ,string struct tag, trying to unmarshal %q into %v", item, v.Type())) - } else { - d.error(&UnmarshalTypeError{"number", v.Type(), int64(d.off)}) - } - case reflect.Interface: - n, err := d.convertNumber(s) - if err != nil { - d.saveError(err) - break - } - if v.NumMethod() != 0 { - d.saveError(&UnmarshalTypeError{"number", v.Type(), int64(d.off)}) - break - } - v.Set(reflect.ValueOf(n)) - - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - n, err := strconv.ParseInt(s, 10, 64) - if err != nil || v.OverflowInt(n) { - d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) - break - } - v.SetInt(n) - - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - n, err := strconv.ParseUint(s, 10, 64) - if err != nil || v.OverflowUint(n) { - d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) - break - } - v.SetUint(n) - - case reflect.Float32, reflect.Float64: - n, err := strconv.ParseFloat(s, v.Type().Bits()) - if err != nil || v.OverflowFloat(n) { - d.saveError(&UnmarshalTypeError{"number " + s, v.Type(), int64(d.off)}) - break - } - v.SetFloat(n) - } - } -} - -// The xxxInterface routines build up a value to be stored -// in an empty interface. They are not strictly necessary, -// but they avoid the weight of reflection in this common case. - -// valueInterface is like value but returns interface{} -func (d *decodeState) valueInterface() interface{} { - switch d.scanWhile(scanSkipSpace) { - default: - d.error(errPhase) - panic("unreachable") - case scanBeginArray: - return d.arrayInterface() - case scanBeginObject: - return d.objectInterface() - case scanBeginLiteral: - return d.literalInterface() - } -} - -// arrayInterface is like array but returns []interface{}. -func (d *decodeState) arrayInterface() []interface{} { - var v = make([]interface{}, 0) - for { - // Look ahead for ] - can only happen on first iteration. - op := d.scanWhile(scanSkipSpace) - if op == scanEndArray { - break - } - - // Back up so d.value can have the byte we just read. - d.off-- - d.scan.undo(op) - - v = append(v, d.valueInterface()) - - // Next token must be , or ]. - op = d.scanWhile(scanSkipSpace) - if op == scanEndArray { - break - } - if op != scanArrayValue { - d.error(errPhase) - } - } - return v -} - -// objectInterface is like object but returns map[string]interface{}. -func (d *decodeState) objectInterface() map[string]interface{} { - m := make(map[string]interface{}) - keys := map[string]bool{} - - for { - // Read opening " of string key or closing }. - op := d.scanWhile(scanSkipSpace) - if op == scanEndObject { - // closing } - can only happen on first iteration. - break - } - if op != scanBeginLiteral { - d.error(errPhase) - } - - // Read string key. - start := d.off - 1 - op = d.scanWhile(scanContinue) - item := d.data[start : d.off-1] - key, ok := unquote(item) - if !ok { - d.error(errPhase) - } - - // Check for duplicate keys. - _, ok = keys[key] - if !ok { - keys[key] = true - } else { - d.error(fmt.Errorf("json: duplicate key '%s' in object", key)) - } - - // Read : before value. - if op == scanSkipSpace { - op = d.scanWhile(scanSkipSpace) - } - if op != scanObjectKey { - d.error(errPhase) - } - - // Read value. - m[key] = d.valueInterface() - - // Next token must be , or }. - op = d.scanWhile(scanSkipSpace) - if op == scanEndObject { - break - } - if op != scanObjectValue { - d.error(errPhase) - } - } - return m -} - -// literalInterface is like literal but returns an interface value. -func (d *decodeState) literalInterface() interface{} { - // All bytes inside literal return scanContinue op code. - start := d.off - 1 - op := d.scanWhile(scanContinue) - - // Scan read one byte too far; back up. - d.off-- - d.scan.undo(op) - item := d.data[start:d.off] - - switch c := item[0]; c { - case 'n': // null - return nil - - case 't', 'f': // true, false - return c == 't' - - case '"': // string - s, ok := unquote(item) - if !ok { - d.error(errPhase) - } - return s - - default: // number - if c != '-' && (c < '0' || c > '9') { - d.error(errPhase) - } - n, err := d.convertNumber(string(item)) - if err != nil { - d.saveError(err) - } - return n - } -} - -// getu4 decodes \uXXXX from the beginning of s, returning the hex value, -// or it returns -1. -func getu4(s []byte) rune { - if len(s) < 6 || s[0] != '\\' || s[1] != 'u' { - return -1 - } - r, err := strconv.ParseUint(string(s[2:6]), 16, 64) - if err != nil { - return -1 - } - return rune(r) -} - -// unquote converts a quoted JSON string literal s into an actual string t. -// The rules are different than for Go, so cannot use strconv.Unquote. -func unquote(s []byte) (t string, ok bool) { - s, ok = unquoteBytes(s) - t = string(s) - return -} - -func unquoteBytes(s []byte) (t []byte, ok bool) { - if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' { - return - } - s = s[1 : len(s)-1] - - // Check for unusual characters. If there are none, - // then no unquoting is needed, so return a slice of the - // original bytes. - r := 0 - for r < len(s) { - c := s[r] - if c == '\\' || c == '"' || c < ' ' { - break - } - if c < utf8.RuneSelf { - r++ - continue - } - rr, size := utf8.DecodeRune(s[r:]) - if rr == utf8.RuneError && size == 1 { - break - } - r += size - } - if r == len(s) { - return s, true - } - - b := make([]byte, len(s)+2*utf8.UTFMax) - w := copy(b, s[0:r]) - for r < len(s) { - // Out of room? Can only happen if s is full of - // malformed UTF-8 and we're replacing each - // byte with RuneError. - if w >= len(b)-2*utf8.UTFMax { - nb := make([]byte, (len(b)+utf8.UTFMax)*2) - copy(nb, b[0:w]) - b = nb - } - switch c := s[r]; { - case c == '\\': - r++ - if r >= len(s) { - return - } - switch s[r] { - default: - return - case '"', '\\', '/', '\'': - b[w] = s[r] - r++ - w++ - case 'b': - b[w] = '\b' - r++ - w++ - case 'f': - b[w] = '\f' - r++ - w++ - case 'n': - b[w] = '\n' - r++ - w++ - case 'r': - b[w] = '\r' - r++ - w++ - case 't': - b[w] = '\t' - r++ - w++ - case 'u': - r-- - rr := getu4(s[r:]) - if rr < 0 { - return - } - r += 6 - if utf16.IsSurrogate(rr) { - rr1 := getu4(s[r:]) - if dec := utf16.DecodeRune(rr, rr1); dec != unicode.ReplacementChar { - // A valid pair; consume. - r += 6 - w += utf8.EncodeRune(b[w:], dec) - break - } - // Invalid surrogate; fall back to replacement rune. - rr = unicode.ReplacementChar - } - w += utf8.EncodeRune(b[w:], rr) - } - - // Quote, control characters are invalid. - case c == '"', c < ' ': - return - - // ASCII - case c < utf8.RuneSelf: - b[w] = c - r++ - w++ - - // Coerce to well-formed UTF-8. - default: - rr, size := utf8.DecodeRune(s[r:]) - r += size - w += utf8.EncodeRune(b[w:], rr) - } - } - return b[0:w], true -} diff --git a/vendor/github.com/go-jose/go-jose/v4/json/encode.go b/vendor/github.com/go-jose/go-jose/v4/json/encode.go deleted file mode 100644 index 98de68ce1e..0000000000 --- a/vendor/github.com/go-jose/go-jose/v4/json/encode.go +++ /dev/null @@ -1,1197 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package json implements encoding and decoding of JSON objects as defined in -// RFC 4627. The mapping between JSON objects and Go values is described -// in the documentation for the Marshal and Unmarshal functions. -// -// See "JSON and Go" for an introduction to this package: -// https://golang.org/doc/articles/json_and_go.html -package json - -import ( - "bytes" - "encoding" - "encoding/base64" - "fmt" - "math" - "reflect" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "unicode" - "unicode/utf8" -) - -// Marshal returns the JSON encoding of v. -// -// Marshal traverses the value v recursively. -// If an encountered value implements the Marshaler interface -// and is not a nil pointer, Marshal calls its MarshalJSON method -// to produce JSON. If no MarshalJSON method is present but the -// value implements encoding.TextMarshaler instead, Marshal calls -// its MarshalText method. -// The nil pointer exception is not strictly necessary -// but mimics a similar, necessary exception in the behavior of -// UnmarshalJSON. -// -// Otherwise, Marshal uses the following type-dependent default encodings: -// -// Boolean values encode as JSON booleans. -// -// Floating point, integer, and Number values encode as JSON numbers. -// -// String values encode as JSON strings coerced to valid UTF-8, -// replacing invalid bytes with the Unicode replacement rune. -// The angle brackets "<" and ">" are escaped to "\u003c" and "\u003e" -// to keep some browsers from misinterpreting JSON output as HTML. -// Ampersand "&" is also escaped to "\u0026" for the same reason. -// -// Array and slice values encode as JSON arrays, except that -// []byte encodes as a base64-encoded string, and a nil slice -// encodes as the null JSON object. -// -// Struct values encode as JSON objects. Each exported struct field -// becomes a member of the object unless -// - the field's tag is "-", or -// - the field is empty and its tag specifies the "omitempty" option. -// -// The empty values are false, 0, any -// nil pointer or interface value, and any array, slice, map, or string of -// length zero. The object's default key string is the struct field name -// but can be specified in the struct field's tag value. The "json" key in -// the struct field's tag value is the key name, followed by an optional comma -// and options. Examples: -// -// // Field is ignored by this package. -// Field int `json:"-"` -// -// // Field appears in JSON as key "myName". -// Field int `json:"myName"` -// -// // Field appears in JSON as key "myName" and -// // the field is omitted from the object if its value is empty, -// // as defined above. -// Field int `json:"myName,omitempty"` -// -// // Field appears in JSON as key "Field" (the default), but -// // the field is skipped if empty. -// // Note the leading comma. -// Field int `json:",omitempty"` -// -// The "string" option signals that a field is stored as JSON inside a -// JSON-encoded string. It applies only to fields of string, floating point, -// integer, or boolean types. This extra level of encoding is sometimes used -// when communicating with JavaScript programs: -// -// Int64String int64 `json:",string"` -// -// The key name will be used if it's a non-empty string consisting of -// only Unicode letters, digits, dollar signs, percent signs, hyphens, -// underscores and slashes. -// -// Anonymous struct fields are usually marshaled as if their inner exported fields -// were fields in the outer struct, subject to the usual Go visibility rules amended -// as described in the next paragraph. -// An anonymous struct field with a name given in its JSON tag is treated as -// having that name, rather than being anonymous. -// An anonymous struct field of interface type is treated the same as having -// that type as its name, rather than being anonymous. -// -// The Go visibility rules for struct fields are amended for JSON when -// deciding which field to marshal or unmarshal. If there are -// multiple fields at the same level, and that level is the least -// nested (and would therefore be the nesting level selected by the -// usual Go rules), the following extra rules apply: -// -// 1) Of those fields, if any are JSON-tagged, only tagged fields are considered, -// even if there are multiple untagged fields that would otherwise conflict. -// 2) If there is exactly one field (tagged or not according to the first rule), that is selected. -// 3) Otherwise there are multiple fields, and all are ignored; no error occurs. -// -// Handling of anonymous struct fields is new in Go 1.1. -// Prior to Go 1.1, anonymous struct fields were ignored. To force ignoring of -// an anonymous struct field in both current and earlier versions, give the field -// a JSON tag of "-". -// -// Map values encode as JSON objects. -// The map's key type must be string; the map keys are used as JSON object -// keys, subject to the UTF-8 coercion described for string values above. -// -// Pointer values encode as the value pointed to. -// A nil pointer encodes as the null JSON object. -// -// Interface values encode as the value contained in the interface. -// A nil interface value encodes as the null JSON object. -// -// Channel, complex, and function values cannot be encoded in JSON. -// Attempting to encode such a value causes Marshal to return -// an UnsupportedTypeError. -// -// JSON cannot represent cyclic data structures and Marshal does not -// handle them. Passing cyclic structures to Marshal will result in -// an infinite recursion. -func Marshal(v interface{}) ([]byte, error) { - e := &encodeState{} - err := e.marshal(v) - if err != nil { - return nil, err - } - return e.Bytes(), nil -} - -// MarshalIndent is like Marshal but applies Indent to format the output. -func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { - b, err := Marshal(v) - if err != nil { - return nil, err - } - var buf bytes.Buffer - err = Indent(&buf, b, prefix, indent) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// HTMLEscape appends to dst the JSON-encoded src with <, >, &, U+2028 and U+2029 -// characters inside string literals changed to \u003c, \u003e, \u0026, \u2028, \u2029 -// so that the JSON will be safe to embed inside HTML