feat: kubedog integration with unified resource handling (#2383)
* feat: add kubedog-based resource tracking integration Add kubedog tracking as an alternative to Helm's --wait flag with: - Real-time deployment progress tracking - Container log streaming - Fine-grained resource filtering (trackKinds/skipKinds/trackResources) Features: - New pkg/resource package for unified manifest parsing and filtering - New pkg/kubedog package wrapping kubedog library - CLI flags: --track-mode, --track-timeout, --track-logs - Helmfile YAML support for trackMode, trackTimeout, trackLogs, trackKinds, skipKinds, trackResources - Case-insensitive kind matching for filtering - Multi-context support with proper kubeconfig/kubeContext handling Tracking supports: Deployment, StatefulSet, DaemonSet, Job Resource filtering priority (highest to lowest): 1. trackResources - explicit resource whitelist 2. skipKinds - blacklist specific kinds 3. trackKinds - whitelist specific kinds Integration: - Disable Helm --wait when using kubedog tracking - Track after successful Helm sync/apply - Respect release.Namespace as fallback for resources without namespace - Use getKubeContext() for correct cluster targeting Tests: - Unit tests for resource filtering and kubedog options - Integration test with httpbin chart - E2E snapshot tests for YAML serialization - Documentation in docs/advanced-features.md Signed-off-by: yxxhero <aiopsclub@163.com> * fix: address PR #2383 review comments (round 4) 1. resource/filter.go: Skip empty whitelist entries in matchWhitelist - At least one field (kind/name/namespace) must be specified - Prevents matching all resources with empty TrackResources entries 2. config/apply.go: Add ValidateConfig for track-mode validation - Validate --track-mode must be 'helm' or 'kubedog' - Reject invalid values like --track-mode foo 3. config/sync.go: Add ValidateConfig for track-mode validation - Same validation as apply command - Ensures consistent behavior across commands Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
parent
c70bd04f3a
commit
6e21671228
|
|
@ -67,7 +67,10 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.StringArrayVar(&applyOptions.PostRendererArgs, "post-renderer-args", nil, `pass --post-renderer-args to "helm template" or "helm upgrade --install"`)
|
||||
f.BoolVar(&applyOptions.SkipSchemaValidation, "skip-schema-validation", false, `pass --skip-schema-validation to "helm template" or "helm upgrade --install"`)
|
||||
f.StringVar(&applyOptions.Cascade, "cascade", "", "pass cascade to helm exec, default: background")
|
||||
f.StringArrayVar(&applyOptions.SuppressOutputLineRegex, "suppress-output-line-regex", nil, "a list of regex patterns to suppress output lines from the diff output")
|
||||
f.StringArrayVar(&applyOptions.SuppressOutputLineRegex, "suppress-output-line-regex", nil, "a list of regex patterns to suppress output lines from diff output")
|
||||
f.StringVar(&applyOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default) or 'kubedog'")
|
||||
f.IntVar(&applyOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`)
|
||||
f.BoolVar(&applyOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.StringArrayVar(&syncOptions.PostRendererArgs, "post-renderer-args", nil, `pass --post-renderer-args to "helm template" or "helm upgrade --install"`)
|
||||
f.BoolVar(&syncOptions.SkipSchemaValidation, "skip-schema-validation", false, `pass --skip-schema-validation to "helm template" or "helm upgrade --install"`)
|
||||
f.StringVar(&syncOptions.Cascade, "cascade", "", "pass cascade to helm exec, default: background")
|
||||
f.StringVar(&syncOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default) or 'kubedog'")
|
||||
f.IntVar(&syncOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`)
|
||||
f.BoolVar(&syncOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,97 @@
|
|||
## Advanced Features
|
||||
|
||||
- [Resource Tracking with Kubedog](#resource-tracking-with-kubedog)
|
||||
- [Import Configuration Parameters into Helmfile](#import-configuration-parameters-into-helmfile)
|
||||
- [Deploy Kustomization with Helmfile](#deploy-kustomizations-with-helmfile)
|
||||
- [Adhoc Kustomization of Helm Charts](#adhoc-kustomization-of-helm-charts)
|
||||
- [Adding dependencies without forking the chart](#adding-dependencies-without-forking-the-chart)
|
||||
|
||||
### Resource Tracking with Kubedog
|
||||
|
||||
Helmfile can use [kubedog](https://github.com/werf/kubedog) for advanced resource tracking instead of Helm's built-in `--wait` flag. This provides more detailed feedback and control over deployment progress.
|
||||
|
||||
#### Basic Usage
|
||||
|
||||
Enable kubedog tracking in your `helmfile.yaml`:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: ./charts/myapp
|
||||
trackMode: kubedog
|
||||
trackTimeout: 300 # seconds
|
||||
trackLogs: true
|
||||
```
|
||||
|
||||
Or use command-line flags:
|
||||
|
||||
```bash
|
||||
helmfile apply --track-mode kubedog --track-timeout 300 --track-logs
|
||||
```
|
||||
|
||||
#### Configuration Options
|
||||
|
||||
- **`trackMode`**: Set to `kubedog` to enable kubedog tracking (default: `helm`)
|
||||
- **`trackTimeout`**: Timeout in seconds for tracking resources (default: 300)
|
||||
- **`trackLogs`**: Enable real-time log streaming from tracked resources
|
||||
|
||||
#### Resource Filtering
|
||||
|
||||
Control which resources to track using whitelist/blacklist:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: ./charts/myapp
|
||||
trackMode: kubedog
|
||||
# Track only specific resource kinds
|
||||
trackKinds:
|
||||
- Deployment
|
||||
- StatefulSet
|
||||
# Skip certain resource kinds
|
||||
skipKinds:
|
||||
- ConfigMap
|
||||
- Secret
|
||||
```
|
||||
|
||||
#### Specific Resource Tracking
|
||||
|
||||
Track only specific resources by name and namespace:
|
||||
|
||||
```yaml
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: ./charts/myapp
|
||||
trackMode: kubedog
|
||||
trackResources:
|
||||
- kind: Deployment
|
||||
name: myapp-deployment
|
||||
namespace: default
|
||||
- kind: Job
|
||||
name: myapp-job
|
||||
```
|
||||
|
||||
#### Priority Rules
|
||||
|
||||
Resource filtering follows this priority (highest to lowest):
|
||||
|
||||
1. **`trackResources`**: Whitelist specific resources (takes highest priority)
|
||||
2. **`skipKinds`**: Blacklist resource kinds
|
||||
3. **`trackKinds`**: Whitelist resource kinds
|
||||
|
||||
#### Benefits
|
||||
|
||||
- **Real-time feedback**: See deployment progress with detailed status updates
|
||||
- **Log streaming**: View container logs during deployment
|
||||
- **Fine-grained control**: Track only the resources you care about
|
||||
- **Better debugging**: Immediate visibility into deployment issues
|
||||
|
||||
#### Compatibility
|
||||
|
||||
- Kubedog tracking is compatible with Helm 3.x
|
||||
- Kubedog is a compiled dependency and is only used when `trackMode: kubedog` is set
|
||||
- Works with charts that deploy supported workload kinds (currently `Deployment`, `StatefulSet`, `DaemonSet`, and `Job`); other resource kinds are created by Helm/Helmfile as usual but are ignored by the kubedog tracker
|
||||
|
||||
### Import Configuration Parameters into Helmfile
|
||||
|
||||
Helmfile integrates [vals]() to import configuration parameters from following backends:
|
||||
|
|
|
|||
14
go.mod
14
go.mod
|
|
@ -25,6 +25,7 @@ require (
|
|||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||
github.com/tj/assert v0.0.3
|
||||
github.com/variantdev/dag v1.1.0
|
||||
github.com/werf/kubedog v0.13.0
|
||||
github.com/zclconf/go-cty v1.18.0
|
||||
github.com/zclconf/go-cty-yaml v1.2.0
|
||||
go.szostok.io/version v1.2.0
|
||||
|
|
@ -107,7 +108,7 @@ require (
|
|||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
)
|
||||
|
||||
require (
|
||||
|
|
@ -144,6 +145,7 @@ require (
|
|||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/antchfx/jsonquery v1.3.6 // indirect
|
||||
github.com/antchfx/xpath v1.3.5 // indirect
|
||||
|
|
@ -152,6 +154,7 @@ require (
|
|||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.5 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.10 // indirect
|
||||
|
|
@ -178,6 +181,7 @@ require (
|
|||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/chanced/caps v1.0.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
|
||||
github.com/containerd/containerd v1.7.30 // indirect
|
||||
|
|
@ -199,6 +203,7 @@ require (
|
|||
github.com/extism/go-sdk v1.7.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fluxcd/cli-utils v0.37.0-flux.1 // indirect
|
||||
github.com/fluxcd/flagger v1.36.1 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
|
||||
github.com/getsops/sops/v3 v3.12.1 // indirect
|
||||
|
|
@ -241,6 +246,7 @@ require (
|
|||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.12 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
|
|
@ -286,6 +292,7 @@ require (
|
|||
github.com/rs/zerolog v1.26.1 // indirect
|
||||
github.com/rubenv/sql-migrate v1.8.1 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/samber/lo v1.39.0 // indirect
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
|
|
@ -300,9 +307,13 @@ require (
|
|||
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
github.com/werf/logboek v0.6.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.56.0 // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.31.0 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
|
|
@ -320,6 +331,7 @@ require (
|
|||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20260203192932-546029d2fa20 // indirect
|
||||
|
|
|
|||
25
go.sum
25
go.sum
|
|
@ -130,6 +130,8 @@ github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBi
|
|||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
||||
github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
|
||||
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8=
|
||||
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo=
|
||||
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
|
||||
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
|
||||
github.com/antchfx/jsonquery v1.3.6 h1:TaSfeAh7n6T11I74bsZ1FswreIfrbJ0X+OyLflx6mx4=
|
||||
|
|
@ -147,6 +149,8 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
|||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 h1:HrMVYtly2IVqg9EBooHsakQ256ueojP7QuG32K71X/U=
|
||||
github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE=
|
||||
github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ=
|
||||
github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk=
|
||||
github.com/aws/aws-sdk-go-v2 v1.41.2 h1:LuT2rzqNQsauaGkPK/7813XxcZ3o3yePY0Iy891T2ls=
|
||||
|
|
@ -214,6 +218,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
|
|||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
|
||||
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
|
||||
github.com/chanced/caps v1.0.2 h1:RELvNN4lZajqSXJGzPaU7z8B4LK2+o2Oc/upeWdgMOA=
|
||||
github.com/chanced/caps v1.0.2/go.mod h1:SJhRzeYLKJ3OmzyQXhdZ7Etj7lqqWoPtQ1zcSJRtQjs=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8=
|
||||
github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4=
|
||||
|
|
@ -237,8 +243,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N
|
|||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
|
||||
github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyberark/conjur-api-go v0.13.16 h1:4PT/cja78hIIyUy1EOQPhppedVMY76BfzmWR20c8kog=
|
||||
github.com/cyberark/conjur-api-go v0.13.16/go.mod h1:BQmiYeA8hJmGSduF+wgfXY4Ktdky30+cevXm+tzr63k=
|
||||
github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE=
|
||||
|
|
@ -305,6 +311,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2
|
|||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fluxcd/cli-utils v0.37.0-flux.1 h1:k/VvPNT3tGa/l2N+qzHduaQr3GVbgoWS6nw7tGZz16w=
|
||||
github.com/fluxcd/cli-utils v0.37.0-flux.1/go.mod h1:aND5wX3LuTFtB7eUT7vsWr8mmxRVSPR2Wkvbn0SqPfw=
|
||||
github.com/fluxcd/flagger v1.36.1 h1:X2PumtNwZz9YSGaOtZLFm2zAKLgHhFkbNv8beg7ifyc=
|
||||
github.com/fluxcd/flagger v1.36.1/go.mod h1:qmtLsxheVDTI8XeCaXUxW5UCmfcSKnY9fizG9NmW/Fk=
|
||||
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
|
||||
github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
|
|
@ -462,6 +470,8 @@ github.com/googleapis/enterprise-certificate-proxy v0.3.12 h1:Fg+zsqzYEs1Znvmczt
|
|||
github.com/googleapis/enterprise-certificate-proxy v0.3.12/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc=
|
||||
github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY=
|
||||
github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0=
|
||||
github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
|
|
@ -696,6 +706,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
|
||||
github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 h1:ObX9hZmK+VmijreZO/8x9pQ8/P/ToHD/bdSb4Eg4tUo=
|
||||
|
|
@ -763,11 +775,16 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
|||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
||||
github.com/variantdev/dag v1.1.0 h1:xodYlSng33KWGvIGMpKUyLcIZRXKiNUx612mZJqYrDg=
|
||||
github.com/variantdev/dag v1.1.0/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE=
|
||||
github.com/werf/kubedog v0.13.0 h1:ys+GyZbIMqm0r2po0HClbONcEnS5cWSFR2BayIfBqsY=
|
||||
github.com/werf/kubedog v0.13.0/go.mod h1:Y6pesrIN5uhFKqmHnHSoeW4jmVyZlWPFWv5SjB0rUPg=
|
||||
github.com/werf/logboek v0.6.1 h1:oEe6FkmlKg0z0n80oZjLplj6sXcBeLleCkjfOOZEL2g=
|
||||
github.com/werf/logboek v0.6.1/go.mod h1:Gez5J4bxekyr6MxTmIJyId1F61rpO+0/V4vjCIEIZmk=
|
||||
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
|
||||
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
|
||||
|
|
@ -776,6 +793,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
|
|||
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
|
||||
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
||||
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
github.com/yandex-cloud/go-genproto v0.56.0 h1:dMBLqeWc4X0gkdevJEnBXkYV9JGci7EHb9NdCbU3N0c=
|
||||
github.com/yandex-cloud/go-genproto v0.56.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo=
|
||||
github.com/yandex-cloud/go-sdk v0.31.0 h1:iPixKMu7t64xziWRIEW3pKkq3kGuvgNmiwH/Vl1FcqY=
|
||||
|
|
@ -876,6 +895,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v
|
|||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
|
|
|
|||
|
|
@ -808,7 +808,14 @@ func (a *App) loadDesiredStateFromYamlWithBaseDir(file string, baseDir string, o
|
|||
valsRuntime: a.valsRuntime,
|
||||
}
|
||||
|
||||
return ld.Load(file, op)
|
||||
st, err := ld.Load(file, op)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
st.SetKubeconfig(a.Kubeconfig)
|
||||
|
||||
return st, nil
|
||||
}
|
||||
|
||||
type helmKey struct {
|
||||
|
|
@ -1762,6 +1769,9 @@ Do you really want to apply?
|
|||
HideNotes: c.HideNotes(),
|
||||
TakeOwnership: c.TakeOwnership(),
|
||||
SyncReleaseLabels: c.SyncReleaseLabels(),
|
||||
TrackMode: c.TrackMode(),
|
||||
TrackTimeout: c.TrackTimeout(),
|
||||
TrackLogs: c.TrackLogs(),
|
||||
}
|
||||
return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts)
|
||||
}))
|
||||
|
|
@ -2227,6 +2237,9 @@ Do you really want to sync?
|
|||
TakeOwnership: c.TakeOwnership(),
|
||||
SkipSchemaValidation: c.SkipSchemaValidation(),
|
||||
SyncReleaseLabels: c.SyncReleaseLabels(),
|
||||
TrackMode: c.TrackMode(),
|
||||
TrackTimeout: c.TrackTimeout(),
|
||||
TrackLogs: c.TrackLogs(),
|
||||
}
|
||||
return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), opts)
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -80,7 +80,6 @@ func TestSync(t *testing.T) {
|
|||
}
|
||||
|
||||
syncErr := app.Sync(applyConfig{
|
||||
// if we check log output, concurrency must be 1. otherwise the test becomes non-deterministic.
|
||||
concurrency: tc.concurrency,
|
||||
logger: logger,
|
||||
skipDiffOnInstall: tc.skipDiffOnInstall,
|
||||
|
|
|
|||
|
|
@ -2380,6 +2380,9 @@ type applyConfig struct {
|
|||
takeOwnership bool
|
||||
syncReleaseLabels bool
|
||||
enforceNeedsAreInstalled bool
|
||||
trackMode string
|
||||
trackTimeout int
|
||||
trackLogs bool
|
||||
|
||||
// template-only options
|
||||
includeCRDs, skipTests bool
|
||||
|
|
@ -2590,6 +2593,18 @@ func (a applyConfig) SyncReleaseLabels() bool {
|
|||
return a.syncReleaseLabels
|
||||
}
|
||||
|
||||
func (a applyConfig) TrackMode() string {
|
||||
return a.trackMode
|
||||
}
|
||||
|
||||
func (a applyConfig) TrackTimeout() int {
|
||||
return a.trackTimeout
|
||||
}
|
||||
|
||||
func (a applyConfig) TrackLogs() bool {
|
||||
return a.trackLogs
|
||||
}
|
||||
|
||||
type depsConfig struct {
|
||||
skipRepos bool
|
||||
includeTransitiveNeeds bool
|
||||
|
|
|
|||
|
|
@ -87,6 +87,10 @@ type ApplyConfigProvider interface {
|
|||
|
||||
DAGConfig
|
||||
|
||||
TrackMode() string
|
||||
TrackTimeout() int
|
||||
TrackLogs() bool
|
||||
|
||||
concurrencyConfig
|
||||
interactive
|
||||
loggingConfig
|
||||
|
|
@ -119,6 +123,9 @@ type SyncConfigProvider interface {
|
|||
IncludeTransitiveNeeds() bool
|
||||
|
||||
SyncReleaseLabels() bool
|
||||
TrackMode() string
|
||||
TrackTimeout() int
|
||||
TrackLogs() bool
|
||||
|
||||
DAGConfig
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package cluster
|
||||
|
||||
import (
|
||||
"github.com/helmfile/helmfile/pkg/resource"
|
||||
)
|
||||
|
||||
type (
|
||||
Resource = resource.Resource
|
||||
FilterConfig = resource.FilterConfig
|
||||
ResourceFilter = resource.ResourceFilter
|
||||
)
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ApplyOptoons is the options for the apply command
|
||||
type ApplyOptions struct {
|
||||
// Set is a list of key value pairs to be merged into the command
|
||||
|
|
@ -75,6 +77,12 @@ type ApplyOptions struct {
|
|||
TakeOwnership bool
|
||||
|
||||
SyncReleaseLabels bool
|
||||
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
|
||||
TrackMode string
|
||||
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
|
||||
TrackTimeout int
|
||||
// TrackLogs enables log streaming with kubedog
|
||||
TrackLogs bool
|
||||
}
|
||||
|
||||
// NewApply creates a new Apply
|
||||
|
|
@ -280,3 +288,25 @@ func (a *ApplyImpl) TakeOwnership() bool {
|
|||
func (a *ApplyImpl) SyncReleaseLabels() bool {
|
||||
return a.ApplyOptions.SyncReleaseLabels
|
||||
}
|
||||
|
||||
// TrackMode returns the track mode.
|
||||
func (a *ApplyImpl) TrackMode() string {
|
||||
return a.ApplyOptions.TrackMode
|
||||
}
|
||||
|
||||
// TrackTimeout returns the track timeout.
|
||||
func (a *ApplyImpl) TrackTimeout() int {
|
||||
return a.ApplyOptions.TrackTimeout
|
||||
}
|
||||
|
||||
// TrackLogs returns the track logs flag.
|
||||
func (a *ApplyImpl) TrackLogs() bool {
|
||||
return a.ApplyOptions.TrackLogs
|
||||
}
|
||||
|
||||
func (a *ApplyImpl) ValidateConfig() error {
|
||||
if a.ApplyOptions.TrackMode != "" && a.ApplyOptions.TrackMode != "helm" && a.ApplyOptions.TrackMode != "kubedog" {
|
||||
return fmt.Errorf("--track-mode must be 'helm' or 'kubedog', got: %s", a.ApplyOptions.TrackMode)
|
||||
}
|
||||
return a.GlobalImpl.ValidateConfig()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package config
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SyncOptions is the options for the build command
|
||||
type SyncOptions struct {
|
||||
// Set is the set flag
|
||||
|
|
@ -48,6 +50,12 @@ type SyncOptions struct {
|
|||
TakeOwnership bool
|
||||
// SyncReleaseLabels is the sync release labels flag
|
||||
SyncReleaseLabels bool
|
||||
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
|
||||
TrackMode string
|
||||
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
|
||||
TrackTimeout int
|
||||
// TrackLogs enables log streaming with kubedog
|
||||
TrackLogs bool
|
||||
}
|
||||
|
||||
// NewSyncOptions creates a new Apply
|
||||
|
|
@ -187,3 +195,25 @@ func (t *SyncImpl) TakeOwnership() bool {
|
|||
func (t *SyncImpl) SyncReleaseLabels() bool {
|
||||
return t.SyncOptions.SyncReleaseLabels
|
||||
}
|
||||
|
||||
// TrackMode returns the track mode.
|
||||
func (t *SyncImpl) TrackMode() string {
|
||||
return t.SyncOptions.TrackMode
|
||||
}
|
||||
|
||||
// TrackTimeout returns the track timeout.
|
||||
func (t *SyncImpl) TrackTimeout() int {
|
||||
return t.SyncOptions.TrackTimeout
|
||||
}
|
||||
|
||||
// TrackLogs returns the track logs flag.
|
||||
func (t *SyncImpl) TrackLogs() bool {
|
||||
return t.SyncOptions.TrackLogs
|
||||
}
|
||||
|
||||
func (t *SyncImpl) ValidateConfig() error {
|
||||
if t.SyncOptions.TrackMode != "" && t.SyncOptions.TrackMode != "helm" && t.SyncOptions.TrackMode != "kubedog" {
|
||||
return fmt.Errorf("--track-mode must be 'helm' or 'kubedog', got: %s", t.SyncOptions.TrackMode)
|
||||
}
|
||||
return t.GlobalImpl.ValidateConfig()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
package kubedog
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/resource"
|
||||
)
|
||||
|
||||
type TrackMode string
|
||||
|
||||
const (
|
||||
TrackModeHelm TrackMode = "helm"
|
||||
TrackModeKubedog TrackMode = "kubedog"
|
||||
)
|
||||
|
||||
type TrackOptions struct {
|
||||
Timeout time.Duration
|
||||
Logs bool
|
||||
LogsSince time.Duration
|
||||
Filter *resource.FilterConfig
|
||||
}
|
||||
|
||||
func NewTrackOptions() *TrackOptions {
|
||||
return &TrackOptions{
|
||||
Timeout: 5 * time.Minute,
|
||||
LogsSince: 10 * time.Minute,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *TrackOptions) WithTimeout(timeout time.Duration) *TrackOptions {
|
||||
o.Timeout = timeout
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *TrackOptions) WithLogs(logs bool) *TrackOptions {
|
||||
o.Logs = logs
|
||||
return o
|
||||
}
|
||||
|
||||
func (o *TrackOptions) WithFilterConfig(config *resource.FilterConfig) *TrackOptions {
|
||||
o.Filter = config
|
||||
return o
|
||||
}
|
||||
|
|
@ -0,0 +1,200 @@
|
|||
package kubedog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/werf/kubedog/pkg/kube"
|
||||
"github.com/werf/kubedog/pkg/tracker"
|
||||
"github.com/werf/kubedog/pkg/trackers/rollout/multitrack"
|
||||
"go.uber.org/zap"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/resource"
|
||||
)
|
||||
|
||||
type cacheKey struct {
|
||||
kubeContext string
|
||||
kubeconfig string
|
||||
}
|
||||
|
||||
var (
|
||||
kubeInitMu sync.Mutex
|
||||
clientCache = make(map[cacheKey]kubernetes.Interface)
|
||||
)
|
||||
|
||||
type Tracker struct {
|
||||
logger *zap.SugaredLogger
|
||||
clientSet kubernetes.Interface
|
||||
trackOptions *TrackOptions
|
||||
filter *resource.ResourceFilter
|
||||
namespace string
|
||||
}
|
||||
|
||||
type TrackerConfig struct {
|
||||
Logger *zap.SugaredLogger
|
||||
Namespace string
|
||||
KubeContext string
|
||||
Kubeconfig string
|
||||
TrackOptions *TrackOptions
|
||||
}
|
||||
|
||||
func NewTracker(config *TrackerConfig) (*Tracker, error) {
|
||||
logger := config.Logger
|
||||
if logger == nil {
|
||||
logger = zap.NewNop().Sugar()
|
||||
}
|
||||
|
||||
kubeconfig := config.Kubeconfig
|
||||
if kubeconfig == "" {
|
||||
kubeconfig = os.Getenv("KUBECONFIG")
|
||||
}
|
||||
|
||||
clientSet, err := getOrCreateClient(config.KubeContext, kubeconfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize kubernetes client: %w", err)
|
||||
}
|
||||
|
||||
options := config.TrackOptions
|
||||
if options == nil {
|
||||
options = NewTrackOptions()
|
||||
}
|
||||
|
||||
var filter *resource.ResourceFilter
|
||||
if options.Filter != nil {
|
||||
filter = resource.NewResourceFilter(options.Filter, logger)
|
||||
}
|
||||
|
||||
return &Tracker{
|
||||
logger: logger,
|
||||
clientSet: clientSet,
|
||||
trackOptions: options,
|
||||
filter: filter,
|
||||
namespace: config.Namespace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getOrCreateClient(kubeContext, kubeconfig string) (kubernetes.Interface, error) {
|
||||
key := cacheKey{
|
||||
kubeContext: kubeContext,
|
||||
kubeconfig: kubeconfig,
|
||||
}
|
||||
|
||||
kubeInitMu.Lock()
|
||||
defer kubeInitMu.Unlock()
|
||||
|
||||
if client, ok := clientCache[key]; ok {
|
||||
return client, nil
|
||||
}
|
||||
|
||||
initOpts := kube.InitOptions{
|
||||
KubeConfigOptions: kube.KubeConfigOptions{
|
||||
Context: kubeContext,
|
||||
ConfigPath: kubeconfig,
|
||||
},
|
||||
}
|
||||
|
||||
if err := kube.Init(initOpts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := kube.Kubernetes
|
||||
clientCache[key] = client
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func (t *Tracker) TrackResources(ctx context.Context, resources []*resource.Resource) error {
|
||||
if len(resources) == 0 {
|
||||
t.logger.Info("No resources to track")
|
||||
return nil
|
||||
}
|
||||
|
||||
filtered := t.filterResources(resources)
|
||||
if len(filtered) == 0 {
|
||||
t.logger.Info("No resources to track after filtering")
|
||||
return nil
|
||||
}
|
||||
|
||||
t.logger.Infof("Tracking %d resources with kubedog (filtered from %d total)", len(filtered), len(resources))
|
||||
|
||||
specs := multitrack.MultitrackSpecs{}
|
||||
|
||||
for _, res := range filtered {
|
||||
namespace := res.Namespace
|
||||
if namespace == "" {
|
||||
namespace = t.namespace
|
||||
}
|
||||
|
||||
switch strings.ToLower(res.Kind) {
|
||||
case "deployment", "deploy":
|
||||
specs.Deployments = append(specs.Deployments, multitrack.MultitrackSpec{
|
||||
ResourceName: res.Name,
|
||||
Namespace: namespace,
|
||||
SkipLogs: !t.trackOptions.Logs,
|
||||
})
|
||||
case "statefulset", "sts":
|
||||
specs.StatefulSets = append(specs.StatefulSets, multitrack.MultitrackSpec{
|
||||
ResourceName: res.Name,
|
||||
Namespace: namespace,
|
||||
SkipLogs: !t.trackOptions.Logs,
|
||||
})
|
||||
case "daemonset", "ds":
|
||||
specs.DaemonSets = append(specs.DaemonSets, multitrack.MultitrackSpec{
|
||||
ResourceName: res.Name,
|
||||
Namespace: namespace,
|
||||
SkipLogs: !t.trackOptions.Logs,
|
||||
})
|
||||
case "job":
|
||||
specs.Jobs = append(specs.Jobs, multitrack.MultitrackSpec{
|
||||
ResourceName: res.Name,
|
||||
Namespace: namespace,
|
||||
SkipLogs: !t.trackOptions.Logs,
|
||||
})
|
||||
default:
|
||||
t.logger.Debugf("Skipping unsupported kind %s for resource %s/%s", res.Kind, namespace, res.Name)
|
||||
}
|
||||
}
|
||||
|
||||
if len(specs.Deployments)+len(specs.StatefulSets)+len(specs.DaemonSets)+len(specs.Jobs) == 0 {
|
||||
t.logger.Info("No trackable resources found (only Deployment, StatefulSet, DaemonSet, and Job are supported)")
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := multitrack.MultitrackOptions{
|
||||
Options: tracker.Options{
|
||||
ParentContext: ctx,
|
||||
Timeout: t.trackOptions.Timeout,
|
||||
LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince),
|
||||
},
|
||||
StatusProgressPeriod: 5 * time.Second,
|
||||
}
|
||||
|
||||
err := multitrack.Multitrack(t.clientSet, specs, opts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("tracking failed: %w", err)
|
||||
}
|
||||
|
||||
t.logger.Info("All resources tracked successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Tracker) filterResources(resources []*resource.Resource) []*resource.Resource {
|
||||
if t.filter == nil {
|
||||
return resources
|
||||
}
|
||||
|
||||
var result []*resource.Resource
|
||||
for _, res := range resources {
|
||||
if t.filter.ShouldTrack(res) {
|
||||
result = append(result, res)
|
||||
} else {
|
||||
t.logger.Debugf("Skipping resource %s/%s (kind: %s) based on configuration", res.Namespace, res.Name, res.Kind)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package kubedog
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/resource"
|
||||
)
|
||||
|
||||
func TestTrackMode(t *testing.T) {
|
||||
assert.Equal(t, "helm", string(TrackModeHelm))
|
||||
assert.Equal(t, "kubedog", string(TrackModeKubedog))
|
||||
}
|
||||
|
||||
func TestNewTrackOptions(t *testing.T) {
|
||||
opts := NewTrackOptions()
|
||||
assert.NotNil(t, opts)
|
||||
assert.Equal(t, 5*time.Minute, opts.Timeout)
|
||||
assert.Equal(t, false, opts.Logs)
|
||||
assert.Equal(t, 10*time.Minute, opts.LogsSince)
|
||||
}
|
||||
|
||||
func TestTrackOptions_WithTimeout(t *testing.T) {
|
||||
opts := NewTrackOptions()
|
||||
opts = opts.WithTimeout(10 * time.Second)
|
||||
|
||||
assert.Equal(t, 10*time.Second, opts.Timeout)
|
||||
}
|
||||
|
||||
func TestTrackOptions_WithLogs(t *testing.T) {
|
||||
opts := NewTrackOptions()
|
||||
opts = opts.WithLogs(true)
|
||||
|
||||
assert.True(t, opts.Logs)
|
||||
}
|
||||
|
||||
func TestTrackOptions_Chaining(t *testing.T) {
|
||||
opts := NewTrackOptions()
|
||||
opts = opts.
|
||||
WithTimeout(20 * time.Second).
|
||||
WithLogs(true)
|
||||
|
||||
assert.Equal(t, 20*time.Second, opts.Timeout)
|
||||
assert.True(t, opts.Logs)
|
||||
}
|
||||
|
||||
func TestResource(t *testing.T) {
|
||||
res := &resource.Resource{
|
||||
Name: "test-resource",
|
||||
Namespace: "test-ns",
|
||||
Kind: "deployment",
|
||||
}
|
||||
|
||||
assert.Equal(t, "test-resource", res.Name)
|
||||
assert.Equal(t, "test-ns", res.Namespace)
|
||||
assert.Equal(t, "deployment", res.Kind)
|
||||
}
|
||||
|
||||
func TestTrackerConfig(t *testing.T) {
|
||||
config := &TrackerConfig{
|
||||
Logger: nil,
|
||||
Namespace: "test-ns",
|
||||
KubeContext: "test-ctx",
|
||||
Kubeconfig: "/test/kubeconfig",
|
||||
TrackOptions: NewTrackOptions(),
|
||||
}
|
||||
|
||||
assert.NotNil(t, config)
|
||||
assert.Equal(t, "test-ns", config.Namespace)
|
||||
assert.Equal(t, "test-ctx", config.KubeContext)
|
||||
assert.Equal(t, "/test/kubeconfig", config.Kubeconfig)
|
||||
assert.NotNil(t, config.TrackOptions)
|
||||
}
|
||||
|
||||
func TestTrackOptions_WithFilterConfig(t *testing.T) {
|
||||
opts := NewTrackOptions()
|
||||
filter := &resource.FilterConfig{
|
||||
TrackKinds: []string{"Deployment", "StatefulSet"},
|
||||
SkipKinds: []string{"ConfigMap"},
|
||||
}
|
||||
opts = opts.WithFilterConfig(filter)
|
||||
|
||||
assert.NotNil(t, opts.Filter)
|
||||
assert.Equal(t, []string{"Deployment", "StatefulSet"}, opts.Filter.TrackKinds)
|
||||
assert.Equal(t, []string{"ConfigMap"}, opts.Filter.SkipKinds)
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type ResourceFilter struct {
|
||||
logger *zap.SugaredLogger
|
||||
config *FilterConfig
|
||||
skipKinds map[string]bool
|
||||
trackKinds map[string]bool
|
||||
}
|
||||
|
||||
func NewResourceFilter(config *FilterConfig, logger *zap.SugaredLogger) *ResourceFilter {
|
||||
f := &ResourceFilter{
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
f.skipKinds = make(map[string]bool)
|
||||
for _, kind := range config.SkipKinds {
|
||||
f.skipKinds[strings.ToLower(kind)] = true
|
||||
}
|
||||
|
||||
f.trackKinds = make(map[string]bool)
|
||||
for _, kind := range config.TrackKinds {
|
||||
f.trackKinds[strings.ToLower(kind)] = true
|
||||
}
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *ResourceFilter) Filter(resources []Resource) []Resource {
|
||||
if f.config == nil {
|
||||
return resources
|
||||
}
|
||||
|
||||
var filtered []Resource
|
||||
for i := range resources {
|
||||
if f.ShouldTrack(&resources[i]) {
|
||||
filtered = append(filtered, resources[i])
|
||||
} else if f.logger != nil {
|
||||
res := resources[i]
|
||||
f.logger.Debugf("Skipping resource %s/%s (kind: %s) based on configuration", res.Namespace, res.Name, res.Kind)
|
||||
}
|
||||
}
|
||||
return filtered
|
||||
}
|
||||
|
||||
func (f *ResourceFilter) ShouldTrack(r *Resource) bool {
|
||||
if f.config == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(f.config.TrackResources) > 0 {
|
||||
return f.matchWhitelist(r)
|
||||
}
|
||||
|
||||
kindLower := strings.ToLower(r.Kind)
|
||||
if f.skipKinds[kindLower] {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(f.trackKinds) > 0 {
|
||||
return f.trackKinds[kindLower]
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *ResourceFilter) matchWhitelist(r *Resource) bool {
|
||||
for _, tr := range f.config.TrackResources {
|
||||
// At least one field must be specified for a match
|
||||
if tr.Kind == "" && tr.Name == "" && tr.Namespace == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if tr.Kind != "" && !strings.EqualFold(tr.Kind, r.Kind) {
|
||||
continue
|
||||
}
|
||||
if tr.Name != "" && tr.Name != r.Name {
|
||||
continue
|
||||
}
|
||||
if tr.Namespace != "" && tr.Namespace != r.Namespace {
|
||||
continue
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestResourceFilter_Filter(t *testing.T) {
|
||||
resources := []Resource{
|
||||
{Kind: "Deployment", Name: "app1", Namespace: "default"},
|
||||
{Kind: "StatefulSet", Name: "db1", Namespace: "default"},
|
||||
{Kind: "ConfigMap", Name: "cm1", Namespace: "default"},
|
||||
{Kind: "Secret", Name: "sec1", Namespace: "kube-system"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *FilterConfig
|
||||
expected int
|
||||
}{
|
||||
{
|
||||
name: "nil filter returns all",
|
||||
config: nil,
|
||||
expected: 4,
|
||||
},
|
||||
{
|
||||
name: "TrackKinds whitelist",
|
||||
config: &FilterConfig{
|
||||
TrackKinds: []string{"Deployment", "StatefulSet"},
|
||||
},
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
name: "SkipKinds blacklist",
|
||||
config: &FilterConfig{
|
||||
SkipKinds: []string{"ConfigMap", "Secret"},
|
||||
},
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
name: "TrackResources whitelist by kind and namespace",
|
||||
config: &FilterConfig{
|
||||
TrackResources: []Resource{
|
||||
{Kind: "Deployment", Namespace: "default"},
|
||||
},
|
||||
},
|
||||
expected: 1,
|
||||
},
|
||||
{
|
||||
name: "TrackResources whitelist by name",
|
||||
config: &FilterConfig{
|
||||
TrackResources: []Resource{
|
||||
{Name: "app1"},
|
||||
{Name: "db1"},
|
||||
},
|
||||
},
|
||||
expected: 2,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filter := NewResourceFilter(tt.config, zap.NewNop().Sugar())
|
||||
filtered := filter.Filter(resources)
|
||||
assert.Equal(t, tt.expected, len(filtered))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceFilter_ShouldTrack(t *testing.T) {
|
||||
logger := zap.NewNop().Sugar()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config *FilterConfig
|
||||
resource *Resource
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "nil config tracks all",
|
||||
config: nil,
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "TrackKinds matches",
|
||||
config: &FilterConfig{
|
||||
TrackKinds: []string{"Deployment"},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "TrackKinds no match",
|
||||
config: &FilterConfig{
|
||||
TrackKinds: []string{"StatefulSet"},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "SkipKinds matches",
|
||||
config: &FilterConfig{
|
||||
SkipKinds: []string{"ConfigMap"},
|
||||
},
|
||||
resource: &Resource{Kind: "ConfigMap", Name: "cm1"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "SkipKinds no match",
|
||||
config: &FilterConfig{
|
||||
SkipKinds: []string{"ConfigMap"},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "TrackResources whitelist matches all criteria",
|
||||
config: &FilterConfig{
|
||||
TrackResources: []Resource{
|
||||
{Kind: "Deployment", Name: "app1", Namespace: "default"},
|
||||
},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1", Namespace: "default"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "TrackResources whitelist partial match",
|
||||
config: &FilterConfig{
|
||||
TrackResources: []Resource{
|
||||
{Kind: "Deployment", Name: "app1"},
|
||||
},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1", Namespace: "other"},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "TrackResources whitelist no match",
|
||||
config: &FilterConfig{
|
||||
TrackResources: []Resource{
|
||||
{Kind: "Deployment", Name: "app2"},
|
||||
},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1"},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "TrackResources takes precedence over TrackKinds",
|
||||
config: &FilterConfig{
|
||||
TrackResources: []Resource{
|
||||
{Kind: "Deployment", Name: "app1"},
|
||||
},
|
||||
TrackKinds: []string{"StatefulSet"},
|
||||
},
|
||||
resource: &Resource{Kind: "Deployment", Name: "app1"},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
filter := NewResourceFilter(tt.config, logger)
|
||||
result := filter.ShouldTrack(tt.resource)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package resource
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
)
|
||||
|
||||
func ParseManifest(manifest []byte, defaultNamespace string, logger *zap.SugaredLogger) ([]Resource, error) {
|
||||
var resources []Resource
|
||||
|
||||
decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(manifest), 4096)
|
||||
|
||||
for {
|
||||
var obj unstructured.Unstructured
|
||||
err := decoder.Decode(&obj)
|
||||
if err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
break
|
||||
}
|
||||
return nil, fmt.Errorf("failed to decode manifest: %w", err)
|
||||
}
|
||||
|
||||
if len(obj.Object) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
kind := obj.GetKind()
|
||||
if kind == "" {
|
||||
if logger != nil {
|
||||
logger.Debugf("Skipping resource without kind")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
name := obj.GetName()
|
||||
if name == "" {
|
||||
if logger != nil {
|
||||
logger.Debugf("Skipping %s resource without name", kind)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
namespace := obj.GetNamespace()
|
||||
if namespace == "" {
|
||||
namespace = defaultNamespace
|
||||
}
|
||||
|
||||
resources = append(resources, Resource{
|
||||
Kind: kind,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
})
|
||||
}
|
||||
|
||||
return resources, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package resource
|
||||
|
||||
type Resource struct {
|
||||
Kind string
|
||||
Name string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
type FilterConfig struct {
|
||||
TrackKinds []string
|
||||
SkipKinds []string
|
||||
TrackResources []Resource
|
||||
}
|
||||
|
|
@ -1,18 +1,22 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/helmfile/chartify"
|
||||
"helm.sh/helm/v4/pkg/storage/driver"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/kubedog"
|
||||
"github.com/helmfile/helmfile/pkg/remote"
|
||||
"github.com/helmfile/helmfile/pkg/resource"
|
||||
)
|
||||
|
||||
type Dependency struct {
|
||||
|
|
@ -165,6 +169,10 @@ func (st *HelmState) appendSuppressOutputLineRegexFlags(flags []string, release
|
|||
}
|
||||
|
||||
func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
|
||||
if st.shouldUseKubedog(release, ops) {
|
||||
return flags
|
||||
}
|
||||
|
||||
switch {
|
||||
case release.WaitForJobs != nil && *release.WaitForJobs:
|
||||
flags = append(flags, "--wait-for-jobs")
|
||||
|
|
@ -173,10 +181,26 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec
|
|||
case release.WaitForJobs == nil && st.HelmDefaults.WaitForJobs:
|
||||
flags = append(flags, "--wait-for-jobs")
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func (st *HelmState) shouldUseKubedog(release *ReleaseSpec, ops *SyncOpts) bool {
|
||||
trackMode := release.TrackMode
|
||||
if trackMode == "" && ops != nil && ops.TrackMode != "" {
|
||||
trackMode = ops.TrackMode
|
||||
}
|
||||
if trackMode == "" {
|
||||
trackMode = st.HelmDefaults.TrackMode
|
||||
}
|
||||
return trackMode == "kubedog"
|
||||
}
|
||||
|
||||
func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
|
||||
if st.shouldUseKubedog(release, ops) {
|
||||
return flags
|
||||
}
|
||||
|
||||
switch {
|
||||
case release.Wait != nil && *release.Wait:
|
||||
flags = append(flags, "--wait")
|
||||
|
|
@ -423,3 +447,181 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
return nil, clean, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) trackWithKubedog(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface, ops *SyncOpts) error {
|
||||
timeout := 5 * time.Minute
|
||||
if release.TrackTimeout != nil && *release.TrackTimeout > 0 {
|
||||
timeout = time.Duration(*release.TrackTimeout) * time.Second
|
||||
} else if ops != nil && ops.TrackTimeout > 0 {
|
||||
timeout = time.Duration(ops.TrackTimeout) * time.Second
|
||||
}
|
||||
|
||||
trackLogs := release.TrackLogs != nil && *release.TrackLogs
|
||||
if release.TrackLogs == nil && ops != nil {
|
||||
trackLogs = ops.TrackLogs
|
||||
}
|
||||
|
||||
filterConfig := &resource.FilterConfig{
|
||||
TrackKinds: release.TrackKinds,
|
||||
SkipKinds: release.SkipKinds,
|
||||
TrackResources: convertTrackResources(release.TrackResources),
|
||||
}
|
||||
|
||||
kubeContext := st.getKubeContext(release)
|
||||
|
||||
trackOpts := kubedog.NewTrackOptions().
|
||||
WithTimeout(timeout).
|
||||
WithLogs(trackLogs).
|
||||
WithFilterConfig(filterConfig)
|
||||
|
||||
tracker, err := kubedog.NewTracker(&kubedog.TrackerConfig{
|
||||
Logger: st.logger,
|
||||
Namespace: release.Namespace,
|
||||
KubeContext: kubeContext,
|
||||
Kubeconfig: st.kubeconfig,
|
||||
TrackOptions: trackOpts,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create kubedog tracker: %w", err)
|
||||
}
|
||||
|
||||
resources, err := st.getReleaseResources(ctx, release, helm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get release resources: %w", err)
|
||||
}
|
||||
|
||||
if len(resources) == 0 {
|
||||
st.logger.Infof("No trackable resources found for release %s", release.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
st.logger.Infof("Tracking %d resources from release %s with kubedog", len(resources), release.Name)
|
||||
|
||||
if err := tracker.TrackResources(ctx, resources); err != nil {
|
||||
return fmt.Errorf("kubedog tracking failed for release %s: %w", release.Name, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *HelmState) getReleaseResources(_ context.Context, release *ReleaseSpec, helm helmexec.Interface) ([]*resource.Resource, error) {
|
||||
st.logger.Debugf("Getting resources for release %s", release.Name)
|
||||
|
||||
manifest, namespace, err := st.getReleaseManifest(release, helm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get release manifest: %w", err)
|
||||
}
|
||||
|
||||
if len(manifest) == 0 {
|
||||
st.logger.Infof("No manifest found for release %s", release.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
defaultNs := namespace
|
||||
if defaultNs == "" {
|
||||
defaultNs = "default"
|
||||
}
|
||||
|
||||
resources, err := resource.ParseManifest(manifest, defaultNs, st.logger)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse release resources from manifest: %w", err)
|
||||
}
|
||||
|
||||
if len(resources) == 0 {
|
||||
st.logger.Infof("No resources found in manifest for release %s", release.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
st.logger.Infof("Found %d resources in manifest for release %s", len(resources), release.Name)
|
||||
|
||||
result := make([]*resource.Resource, len(resources))
|
||||
for i := range resources {
|
||||
result[i] = &resources[i]
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) getReleaseManifest(release *ReleaseSpec, helm helmexec.Interface) ([]byte, string, error) {
|
||||
var tempDir string
|
||||
var err error
|
||||
|
||||
if st.tempDir != nil {
|
||||
tempDir, err = st.tempDir("", "helmfile-template-")
|
||||
} else {
|
||||
tempDir, err = os.MkdirTemp("", "helmfile-template-")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to create temp directory: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
if err := os.RemoveAll(tempDir); err != nil {
|
||||
st.logger.Warnf("Failed to remove temp directory %s: %v", tempDir, err)
|
||||
}
|
||||
}()
|
||||
|
||||
releaseCopy := *release
|
||||
st.ApplyOverrides(&releaseCopy)
|
||||
|
||||
flags, files, err := st.flagsForTemplate(helm, &releaseCopy, 0, &TemplateOpts{})
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to generate template flags: %w", err)
|
||||
}
|
||||
defer st.removeFiles(files)
|
||||
|
||||
flags = append(flags, "--output-dir", tempDir)
|
||||
|
||||
if err := helm.TemplateRelease(releaseCopy.Name, releaseCopy.ChartPathOrName(), flags...); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to run helm template: %w", err)
|
||||
}
|
||||
|
||||
var manifest []byte
|
||||
|
||||
err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(info.Name(), ".yaml") && !strings.HasSuffix(info.Name(), ".yml") {
|
||||
return nil
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read file %s: %w", path, err)
|
||||
}
|
||||
|
||||
if len(manifest) > 0 {
|
||||
manifest = append(manifest, []byte("\n---\n")...)
|
||||
}
|
||||
manifest = append(manifest, content...)
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("failed to walk template output directory: %w", err)
|
||||
}
|
||||
|
||||
return manifest, releaseCopy.Namespace, nil
|
||||
}
|
||||
|
||||
func convertTrackResources(resources []TrackResourceSpec) []resource.Resource {
|
||||
if len(resources) == 0 {
|
||||
return nil
|
||||
}
|
||||
result := make([]resource.Resource, len(resources))
|
||||
for i, r := range resources {
|
||||
result[i] = resource.Resource{
|
||||
Kind: r.Kind,
|
||||
Name: r.Name,
|
||||
Namespace: r.Namespace,
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package state
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
gocontext "context"
|
||||
"crypto/sha1"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
|
|
@ -129,12 +129,18 @@ type HelmState struct {
|
|||
|
||||
valsRuntime vals.Evaluator
|
||||
|
||||
kubeconfig string
|
||||
|
||||
// RenderedValues is the helmfile-wide values that is `.Values`
|
||||
// which is accessible from within the whole helmfile go template.
|
||||
// Note that this is usually computed by DesiredStateLoader from ReleaseSetSpec.Env
|
||||
RenderedValues map[string]any
|
||||
}
|
||||
|
||||
func (st *HelmState) SetKubeconfig(kubeconfig string) {
|
||||
st.kubeconfig = kubeconfig
|
||||
}
|
||||
|
||||
// SubHelmfileSpec defines the subhelmfile path and options
|
||||
type SubHelmfileSpec struct {
|
||||
//path or glob pattern for the sub helmfiles
|
||||
|
|
@ -226,6 +232,8 @@ type HelmSpec struct {
|
|||
SyncReleaseLabels *bool `yaml:"syncReleaseLabels,omitempty"`
|
||||
// TakeOwnership is true if the helmfile should take ownership of the release
|
||||
TakeOwnership *bool `yaml:"takeOwnership,omitempty"`
|
||||
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
|
||||
TrackMode string `yaml:"trackMode,omitempty"`
|
||||
}
|
||||
|
||||
// RepositorySpec that defines values for a helm repo
|
||||
|
|
@ -254,7 +262,7 @@ type Inherit struct {
|
|||
|
||||
type Inherits []Inherit
|
||||
|
||||
// ReleaseSpec defines the structure of a helm release
|
||||
// ReleaseSpec defines the configuration for a Helm release managed by helmfile.
|
||||
type ReleaseSpec struct {
|
||||
// Chart is the name of the chart being installed to create this release
|
||||
Chart string `yaml:"chart,omitempty"`
|
||||
|
|
@ -444,8 +452,27 @@ type ReleaseSpec struct {
|
|||
DeleteTimeout *int `yaml:"deleteTimeout,omitempty"`
|
||||
// SyncReleaseLabels is true if the release labels should be synced with the helmfile labels
|
||||
SyncReleaseLabels *bool `yaml:"syncReleaseLabels,omitempty"`
|
||||
// TakeOwnership is true if the release should take ownership of the resources
|
||||
// TakeOwnership is true if release should take ownership of resources
|
||||
TakeOwnership *bool `yaml:"takeOwnership,omitempty"`
|
||||
// TrackMode specifies whether to use 'helm' or 'kubedog' for tracking resources
|
||||
TrackMode string `yaml:"trackMode,omitempty"`
|
||||
// TrackTimeout specifies timeout for kubedog tracking (in seconds)
|
||||
TrackTimeout *int `yaml:"trackTimeout,omitempty"`
|
||||
// TrackLogs enables log streaming with kubedog
|
||||
TrackLogs *bool `yaml:"trackLogs,omitempty"`
|
||||
// TrackKinds is a whitelist of resource kinds to track
|
||||
TrackKinds []string `yaml:"trackKinds,omitempty"`
|
||||
// SkipKinds is a blacklist of resource kinds to skip tracking
|
||||
SkipKinds []string `yaml:"skipKinds,omitempty"`
|
||||
// TrackResources is a whitelist of specific resources to track
|
||||
TrackResources []TrackResourceSpec `yaml:"trackResources,omitempty"`
|
||||
}
|
||||
|
||||
// TrackResourceSpec specifies a resource to track
|
||||
type TrackResourceSpec struct {
|
||||
Kind string `yaml:"kind,omitempty"`
|
||||
Name string `yaml:"name,omitempty"`
|
||||
Namespace string `yaml:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
func (r *Inherits) UnmarshalYAML(unmarshal func(any) error) error {
|
||||
|
|
@ -870,6 +897,9 @@ type SyncOpts struct {
|
|||
SyncArgs string
|
||||
HideNotes bool
|
||||
TakeOwnership bool
|
||||
TrackMode string
|
||||
TrackTimeout int
|
||||
TrackLogs bool
|
||||
}
|
||||
|
||||
type SyncOpt interface{ Apply(*SyncOpts) }
|
||||
|
|
@ -1095,6 +1125,11 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
|||
}
|
||||
} else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden {
|
||||
relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...)
|
||||
if relErr == nil && st.shouldUseKubedog(release, opts) {
|
||||
if trackErr := st.trackWithKubedog(gocontext.Background(), release, helm, opts); trackErr != nil {
|
||||
st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
|
||||
m.Lock()
|
||||
|
|
@ -1111,6 +1146,12 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
|||
} else {
|
||||
release.installedVersion = installedVersion
|
||||
}
|
||||
|
||||
if st.shouldUseKubedog(release, opts) {
|
||||
if trackErr := st.trackWithKubedog(gocontext.Background(), release, helm, opts); trackErr != nil {
|
||||
st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4910,7 +4951,7 @@ func (st *HelmState) acquireSharedLock(result *chartLockResult, chartPath string
|
|||
result.inProcessMutex = st.getNamedRWMutex(chartPath)
|
||||
result.inProcessMutex.RLock()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), lockTimeout)
|
||||
ctx, cancel := gocontext.WithTimeout(gocontext.Background(), lockTimeout)
|
||||
defer cancel()
|
||||
|
||||
locked, err := result.fileLock.TryRLockContext(ctx, 500*time.Millisecond)
|
||||
|
|
@ -4945,7 +4986,7 @@ func (st *HelmState) acquireExclusiveLock(result *chartLockResult, chartPath str
|
|||
var lockErr error
|
||||
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), lockTimeout)
|
||||
ctx, cancel := gocontext.WithTimeout(gocontext.Background(), lockTimeout)
|
||||
locked, lockErr = result.fileLock.TryLockContext(ctx, 500*time.Millisecond)
|
||||
cancel()
|
||||
|
||||
|
|
|
|||
|
|
@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
|
|||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-6884949b8b",
|
||||
want: "foo-values-dd88b94b8",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-58f57b794f",
|
||||
want: "foo-values-6fb7bbb95f",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]any{"k": "v"},
|
||||
want: "foo-values-6b6b884cc9",
|
||||
want: "foo-values-56d84c9897",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-85494c4677",
|
||||
want: "foo-values-6644fc9d47",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-9d65c65f",
|
||||
want: "bar-values-859cd849bf",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-84b69bb989",
|
||||
want: "myns-foo-values-86d544f7f9",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
|
|
|
|||
90
test/e2e/template/helmfile/testdata/snapshot/kubedog_tracking/gopkg.in-yaml.v2-output.yaml
vendored
Normal file
90
test/e2e/template/helmfile/testdata/snapshot/kubedog_tracking/gopkg.in-yaml.v2-output.yaml
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
# Source: __workingdir__/testdata/snapshot/kubedog_tracking/input.yaml.gotmpl
|
||||
|
||||
filepath: input.yaml.gotmpl
|
||||
helmBinary: helm
|
||||
kustomizeBinary: kustomize
|
||||
releases:
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-baseline
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-baseline
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-baseline
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
baseline: value
|
||||
trackMode: kubedog
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-with-timeout
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-with-timeout
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-timeout
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
timeout: value
|
||||
trackMode: kubedog
|
||||
trackTimeout: 300
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-with-logs
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-with-logs
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-logs
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
logs: value
|
||||
trackMode: kubedog
|
||||
trackLogs: true
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-with-whitelist
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-with-whitelist
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-tracked
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
tracked: value
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-secret
|
||||
namespace: {{ .Release.Namespace }}
|
||||
type: Opaque
|
||||
data:
|
||||
secret: dmFsdWU=
|
||||
trackMode: kubedog
|
||||
trackKinds:
|
||||
- ConfigMap
|
||||
templates: {}
|
||||
renderedvalues: {}
|
||||
90
test/e2e/template/helmfile/testdata/snapshot/kubedog_tracking/gopkg.in-yaml.v3-output.yaml
vendored
Normal file
90
test/e2e/template/helmfile/testdata/snapshot/kubedog_tracking/gopkg.in-yaml.v3-output.yaml
vendored
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
# Source: __workingdir__/testdata/snapshot/kubedog_tracking/input.yaml.gotmpl
|
||||
|
||||
filepath: input.yaml.gotmpl
|
||||
helmBinary: helm
|
||||
kustomizeBinary: kustomize
|
||||
releases:
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-baseline
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-baseline
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-baseline
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
baseline: value
|
||||
trackMode: kubedog
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-with-timeout
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-with-timeout
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-timeout
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
timeout: value
|
||||
trackMode: kubedog
|
||||
trackTimeout: 300
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-with-logs
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-with-logs
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-logs
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
logs: value
|
||||
trackMode: kubedog
|
||||
trackLogs: true
|
||||
- chart: ../../charts/raw-0.1.0
|
||||
name: kubedog-with-whitelist
|
||||
labels:
|
||||
chart: raw-0.1.0
|
||||
name: kubedog-with-whitelist
|
||||
namespace: ""
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-tracked
|
||||
namespace: {{ .Release.Namespace }}
|
||||
data:
|
||||
tracked: value
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{ .Release.Name }}-secret
|
||||
namespace: {{ .Release.Namespace }}
|
||||
type: Opaque
|
||||
data:
|
||||
secret: dmFsdWU=
|
||||
trackMode: kubedog
|
||||
trackKinds:
|
||||
- ConfigMap
|
||||
templates: {}
|
||||
renderedvalues: {}
|
||||
69
test/e2e/template/helmfile/testdata/snapshot/kubedog_tracking/input.yaml.gotmpl
vendored
Normal file
69
test/e2e/template/helmfile/testdata/snapshot/kubedog_tracking/input.yaml.gotmpl
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
releases:
|
||||
- name: kubedog-baseline
|
||||
chart: ../../charts/raw-0.1.0
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-baseline
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
data:
|
||||
baseline: value
|
||||
trackMode: kubedog
|
||||
|
||||
- name: kubedog-with-timeout
|
||||
chart: ../../charts/raw-0.1.0
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-timeout
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
data:
|
||||
timeout: value
|
||||
trackMode: kubedog
|
||||
trackTimeout: 300
|
||||
|
||||
- name: kubedog-with-logs
|
||||
chart: ../../charts/raw-0.1.0
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-logs
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
data:
|
||||
logs: value
|
||||
trackMode: kubedog
|
||||
trackLogs: true
|
||||
|
||||
- name: kubedog-with-whitelist
|
||||
chart: ../../charts/raw-0.1.0
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-tracked
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
data:
|
||||
tracked: value
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}-secret
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
type: Opaque
|
||||
data:
|
||||
secret: dmFsdWU=
|
||||
trackMode: kubedog
|
||||
trackKinds:
|
||||
- ConfigMap
|
||||
|
|
@ -135,6 +135,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes
|
|||
. ${dir}/test-cases/issue-2418.sh
|
||||
. ${dir}/test-cases/issue-2424-sequential-values-paths.sh
|
||||
. ${dir}/test-cases/issue-2431.sh
|
||||
. ${dir}/test-cases/kubedog-tracking.sh
|
||||
|
||||
# ALL DONE -----------------------------------------------------------------------------------------------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,54 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
test_start "kubedog-tracking - resource tracking with kubedog integration"
|
||||
|
||||
kubedog_case_dir="${cases_dir}/kubedog-tracking"
|
||||
config_file="helmfile.yaml.gotmpl"
|
||||
|
||||
info "Testing kubedog integration with httpbin chart"
|
||||
|
||||
# Test 1: Basic sync with kubedog tracking
|
||||
info "Syncing release with basic kubedog tracking"
|
||||
${helmfile} -f "${kubedog_case_dir}/${config_file}" -l name=httpbin-basic sync
|
||||
code=$?
|
||||
[ "${code}" -eq 0 ] || fail "unexpected exit code returned by helmfile sync: ${code}"
|
||||
|
||||
wait_deploy_ready httpbin-basic-httpbin
|
||||
info "Verifying httpbin-basic deployment is running"
|
||||
${kubectl} get deployment httpbin-basic-httpbin -n "${test_ns}" || fail "httpbin-basic deployment not found"
|
||||
|
||||
# Test 2: Sync with whitelist filtering
|
||||
info "Syncing release with whitelist filtering"
|
||||
${helmfile} -f "${kubedog_case_dir}/${config_file}" -l name=httpbin-with-whitelist sync
|
||||
code=$?
|
||||
[ "${code}" -eq 0 ] || fail "unexpected exit code returned by helmfile sync with whitelist: ${code}"
|
||||
|
||||
wait_deploy_ready httpbin-with-whitelist-httpbin
|
||||
info "Verifying httpbin-with-whitelist deployment is running"
|
||||
${kubectl} get deployment httpbin-with-whitelist-httpbin -n "${test_ns}" || fail "httpbin-with-whitelist deployment not found"
|
||||
|
||||
# Test 3: Sync with specific resource tracking
|
||||
info "Syncing release with specific resource tracking"
|
||||
${helmfile} -f "${kubedog_case_dir}/${config_file}" -l name=httpbin-with-resources sync
|
||||
code=$?
|
||||
[ "${code}" -eq 0 ] || fail "unexpected exit code returned by helmfile sync with resource tracking: ${code}"
|
||||
|
||||
wait_deploy_ready httpbin-with-resources-httpbin
|
||||
info "Verifying httpbin-with-resources deployment is running"
|
||||
${kubectl} get deployment httpbin-with-resources-httpbin -n "${test_ns}" || fail "httpbin-with-resources deployment not found"
|
||||
|
||||
# Test 4: Apply all releases with kubedog via CLI flag
|
||||
info "Testing apply with kubedog CLI flag"
|
||||
${helmfile} -f "${kubedog_case_dir}/${config_file}" apply --track-mode kubedog --track-timeout 60
|
||||
code=$?
|
||||
[ "${code}" -eq 0 ] || fail "unexpected exit code returned by helmfile apply: ${code}"
|
||||
|
||||
# Test 5: Cleanup
|
||||
info "Destroying all releases"
|
||||
${helmfile} -f "${kubedog_case_dir}/${config_file}" destroy
|
||||
code=$?
|
||||
[ "${code}" -eq 0 ] || fail "unexpected exit code returned by helmfile destroy: ${code}"
|
||||
|
||||
info "kubedog integration test completed successfully"
|
||||
|
||||
test_pass "kubedog-tracking"
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# Kubedog Integration Test
|
||||
|
||||
This test validates the kubedog resource tracking integration with Helmfile.
|
||||
|
||||
## What it tests
|
||||
|
||||
1. **Basic kubedog tracking**: Deploys httpbin with `trackMode: kubedog` enabled
|
||||
2. **Whitelist filtering**: Uses `trackKinds` to only track Deployment resources
|
||||
3. **Specific resource tracking**: Uses `trackResources` to track specific resources by name
|
||||
4. **CLI flags usage**: Tests `--track-mode` and `--track-timeout` flags
|
||||
5. **Cleanup**: Ensures all releases are properly deleted
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Kubernetes cluster (minikube for local testing)
|
||||
- Helm 3.x installed
|
||||
- kubedog library integrated (built into Helmfile)
|
||||
- kubectl configured to access the cluster
|
||||
|
||||
## Test Cases
|
||||
|
||||
### httpbin-basic
|
||||
- Simple deployment with kubedog tracking enabled
|
||||
- `trackMode: kubedog`
|
||||
- `trackTimeout: 60` seconds
|
||||
- `trackLogs: false`
|
||||
|
||||
### httpbin-with-whitelist
|
||||
- Deployment with resource kind whitelist
|
||||
- Only tracks `Deployment` resources
|
||||
- Skips `ConfigMap` and `Secret` resources
|
||||
|
||||
### httpbin-with-resources
|
||||
- Deployment with specific resource tracking
|
||||
- Tracks only the deployment by name and kind
|
||||
|
||||
## Running the test
|
||||
|
||||
```bash
|
||||
# Run all integration tests including kubedog
|
||||
./test/integration/run.sh
|
||||
|
||||
# Run only kubedog test (if supported by your test framework)
|
||||
# Note: Currently all tests run together via run.sh
|
||||
```
|
||||
|
||||
## Expected behavior
|
||||
|
||||
1. All three httpbin deployments should be created successfully
|
||||
2. Kubedog should track the resources during deployment
|
||||
3. Deployments should reach ready state
|
||||
4. All releases should be cleaned up after tests
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
releases:
|
||||
- name: httpbin-basic
|
||||
namespace: {{ .Namespace }}
|
||||
chart: ../../charts/httpbin
|
||||
set:
|
||||
- name: ingress.enabled
|
||||
value: false
|
||||
trackMode: kubedog
|
||||
trackTimeout: 60
|
||||
trackLogs: false
|
||||
|
||||
- name: httpbin-with-whitelist
|
||||
namespace: {{ .Namespace }}
|
||||
chart: ../../charts/httpbin
|
||||
set:
|
||||
- name: ingress.enabled
|
||||
value: false
|
||||
trackMode: kubedog
|
||||
trackKinds:
|
||||
- Deployment
|
||||
skipKinds:
|
||||
- ConfigMap
|
||||
- Secret
|
||||
|
||||
- name: httpbin-with-resources
|
||||
namespace: {{ .Namespace }}
|
||||
chart: ../../charts/httpbin
|
||||
set:
|
||||
- name: ingress.enabled
|
||||
value: false
|
||||
trackMode: kubedog
|
||||
trackResources:
|
||||
- kind: Deployment
|
||||
name: httpbin-with-resources-httpbin
|
||||
Loading…
Reference in New Issue