Compare commits

...

159 Commits

Author SHA1 Message Date
Namkyu Park 173b5ff688
feat: implement otel telemetry sdk for distributed tracing (#221)
* feat: implement otel sdk

Signed-off-by: namkyu1999 <lak9348@konkuk.ac.kr>

* fix: update endpoint

Signed-off-by: namkyu1999 <lak9348@konkuk.ac.kr>

* fix: fix context logic

Signed-off-by: namkyu1999 <lak9348@konkuk.ac.kr>

* fix: make otel optional

Signed-off-by: namkyu1999 <lak9348@konkuk.ac.kr>

* (chore): Fix the release pipeline (#224)

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

* fix: add logs

Signed-off-by: namkyu1999 <lak9348@gmail.com>

* chore

Signed-off-by: namkyu1999 <lak9348@gmail.com>

* feat: go version from 1.20 to 1.22

Signed-off-by: namkyu1999 <lak9348@gmail.com>

---------

Signed-off-by: namkyu1999 <lak9348@konkuk.ac.kr>
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
Signed-off-by: namkyu1999 <lak9348@gmail.com>
Co-authored-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2025-04-02 11:50:02 +05:30
Shubham Chaudhary b0bca2fa6b
(chore): Fix the release pipeline (#224)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2024-10-15 23:44:14 +05:30
Vedant Shrotria 7c8ef36850
Merge pull request #222 from dusdjhyeon/ubi-migration
UBI migration of Images - chaos-runner
2024-08-06 13:00:13 +05:30
dusdjhyeon 7272457ded
feat: migration base image
Signed-off-by: dusdjhyeon <dusdj0813@gmail.com>
2024-07-16 10:48:40 +09:00
Shubham Chaudhary 6437228ec2
fix(logs): fix the rank warning in logs (#220)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2024-06-01 12:20:37 +05:30
Vedant Shrotria a53618dad5
Merge pull request #219 from Jonsy13/add-gitleaks
Added `gitleaks` as PR Check
2024-05-20 10:27:41 +05:30
Vedant Shrotria 3c7fe9516a
Merge branch 'master' into add-gitleaks 2024-05-17 17:37:19 +05:30
Jonsy13 f72590cbef
Added gitleaks
Signed-off-by: Jonsy13 <vedant.shrotria@harness.io>
2024-05-17 17:34:01 +05:30
Udit Gaurav 13f8c59591
Merge pull request #218 from uditgaurav/remove_bch
Remove BCH banner from readme
2024-04-26 14:31:41 +05:30
Udit Gaurav 1a58f1b8a5 Remove BCH banner from readme
Signed-off-by: Udit Gaurav <udit.gaurav@harness.io>
2024-04-26 14:25:22 +05:30
Udit Gaurav e2dfb54db0
Merge pull request #217 from uditgaurav/fix_pipeline_issues
Fixes pipeline issues and vulnerabilities in the image
2024-04-26 14:23:01 +05:30
Udit Gaurav 516de72ae7 Update security-scan.yml 2024-04-25 21:04:42 +05:30
Udit Gaurav 4004c0bce0 Fixes pipeline issues and vulnerabilities in the image 2024-04-25 21:04:05 +05:30
Arkajyoti Mukherjee 2f3f2c41b5
chore: [CHAOS-4699]: added fuzz test for FuzzBuildContainerSpec (#215)
Signed-off-by: Arkajyoti Mukherjee <arkajyoti.mukherjee@harness.io>
2024-04-01 20:16:18 +05:30
Sayan Mondal a8eaaa6912
test: Adding fuzz test for getSetSidecarSecrets in builders.go (#214) 2024-03-14 12:05:40 +05:30
Sayan Mondal 3d2913efd0
test: Adding fuzz test for getEnvFromMap in builders.go (#213) 2024-03-14 11:24:57 +05:30
Saranya Jena f7b569a5f4
Merge pull request #211 from litmuschaos/CHAOS-4611
chore: [CHAOS-4611]: writing initial fuzz testing for chaos runner
2024-03-11 11:50:27 +05:30
Arkajyoti Mukherjee fa7f5ed86e
chore: [CHAOS-4611]: writing initial fuzz testing for chaos runner
Signed-off-by: Arkajyoti Mukherjee <arkajyoti.mukherjee@harness.io>
2024-03-08 11:15:36 +05:30
Nageshbansal c5e46ee2c7
Adds support tolerations in source cmd Probe (#210)
Signed-off-by: nagesh bansal <nageshbansal59@gmail.com>
2024-03-01 14:51:20 +05:30
Nageshbansal 6750e9f25e
Adds verbosity property (#209)
Signed-off-by: nagesh bansal <nageshbansal59@gmail.com>
2024-01-11 17:32:50 +05:30
Shubham Chaudhary adee28bdd6
fix the go deps (#208)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-12-15 14:15:07 +05:30
Shubham Chaudhary 23f11723a8
fix the chaoshub fault url (#205)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-09-29 16:26:26 +05:30
Udit Gaurav a06a78a02f
Merge pull request #204 from Adarshkumar14/logrus-fix
Upgrading logrus version to v1.9.3 to resolve the vulnerability
2023-07-18 17:30:08 +05:30
Adarsh kumar 35bff87d87 upgrading logrus version
Signed-off-by: Adarsh kumar <adarsh.kumar@harness.io>
2023-07-18 17:18:56 +05:30
Udit Gaurav 6a91bd6f22
Merge pull request #203 from Adarshkumar14/master
Upgrading go version to 1.20
2023-07-18 17:05:38 +05:30
Adarsh kumar f3e98d147f updating go version
Signed-off-by: Adarsh kumar <adarsh.kumar@harness.io>
2023-07-13 14:14:00 +05:30
Adarsh kumar 755acc1c9a Merge branch 'master' of https://github.com/Adarshkumar14/chaos-runner 2023-07-03 14:52:10 +05:30
Adarsh kumar f441876f7a Upgrading go version
Signed-off-by: Adarsh kumar <adarsh.kumar@harness.io>
2023-07-03 14:52:06 +05:30
Shubham Chaudhary e2040a77a8
fixing the docker buildx progess argument (#202)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-06-20 11:54:01 +05:30
Vedant Shrotria 583f5316a4
Merge pull request #200 from Jonsy13/group-optional-runner
Upgrading chaos-operator version (making group optional in k8s probe)
2023-06-05 19:00:42 +05:30
Jonsy13 d18f671758
Added changes for operator upgrade
Signed-off-by: Jonsy13 <vedant.shrotria@harness.io>
2023-06-05 13:12:24 +05:30
Vedant Shrotria e97bbebf52
Merge pull request #199 from Adarshkumar14/master
upgrading go version to 1.19 to fix vulnerabilities
2023-05-26 15:32:04 +05:30
Adarsh kumar 58cf26a08b upgrading go version
Signed-off-by: Adarsh kumar <adarsh.kumar@harness.io>
2023-05-26 15:22:09 +05:30
Shubham Chaudhary bb185d5c91
chore(fields): Updating optional fields to pointer type (#197)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-04-25 12:10:46 +05:30
Shubham Chaudhary 01d959c862
run workflow on dispatch event and use token from secrets (#196)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-04-20 13:22:25 +05:30
Shubham Chaudhary 7ecca3c448
chore(unit): Adding units to the duration fields (#195)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-04-18 13:07:50 +05:30
Amit Kumar Das c05b15a1b8
Updated go mod for slo probes (#194)
* Updated go mod for slo probes

Signed-off-by: Amit Kumar Das <amit.das@harness.io>

* go mod tidy

Signed-off-by: Amit Kumar Das <amit.das@harness.io>

---------

Signed-off-by: Amit Kumar Das <amit.das@harness.io>
2023-04-08 22:54:04 +05:30
Shubham Chaudhary 1238882011
Updating the probe schema (#193)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-03-09 21:26:03 +05:30
Shubham Chaudhary 5cbdaa576e
update(crd): updating the chaosengine crd (#192)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-03-08 13:21:02 +05:30
Shubham Chaudhary 09a62c04cb
chore(sidecar): adding sidecar to the experiment pod (#190)
* chore(sidecar): adding sidecar to the experiment  pod

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

* updating go mod

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

* chore(sidecar): adding env and envFrom fields

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2023-01-10 12:19:36 +05:30
Shubham Chaudhary 8357b15524
chore(engine): Adding chaosengine labels to experiment pod (#189)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-12-26 13:57:33 +05:30
Shubham Chaudhary 60295079df
feat(statuschecktimeout): Fixing case when only one of the timeout or delay is defined (#188)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-11-29 20:01:06 +05:30
Shubham Chaudhary b48382b39a
Adding selectors inside the chaosengine (#187)
* feat(selectors): Adding selectors inside the chaosengine (#181)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-11-21 22:16:56 +05:30
Soumya Ghosh Dastidar b3593a3c8c
feat: update chaos operator (#184)
Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>

Signed-off-by: Soumya Ghosh Dastidar <gdsoumya@gmail.com>
2022-11-14 12:14:39 +05:30
Udit Gaurav 092097295c
Chore(healthCheck): Update default healthCheck tunable in engine (#183)
Signed-off-by: uditgaurav <udit@chaosnative.com>
2022-10-06 14:28:48 +05:30
Udit Gaurav 360649a7cb
Core(docs): Pull new cmdProbe inherit field and Removal of responseTimeout field into chaos-runner (#180)
* Pull cmdProbe inherit changes into runner

Signed-off-by: uditgaurav <udit@chaosnative.com>
2022-09-29 16:03:54 +05:30
Shubham Chaudhary 9996579456
update(sdk): updating operator sdk version (#179)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>

Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-09-28 09:59:20 +05:30
Shubham Chaudhary 421160c0a7
chore(env): Adding service acccount as an env (#178)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-09-05 12:10:09 +05:30
Udit Gaurav d0e8dfb20d
Merge pull request #177 from uditgaurav/fix_test_e2e_pipeline
Fix K3s cluster creation in the pipeline by providing version
2022-08-16 18:41:04 +05:30
uditgaurav a539b63e37 Fix K3s cluster creation in the pipeline by providing version
Signed-off-by: uditgaurav <udit@chaosnative.com>
2022-08-16 18:31:59 +05:30
Shubham Chaudhary b2962a7069
chore(cmdProbe): Updating go.mod for cmd probe changes (#176)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-08-04 19:46:06 +05:30
Shubham Chaudhary 44a8ecac17
updating the go.mod for the source cmdProbe (#175)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-07-14 19:54:19 +05:30
Shubham Chaudhary 3afc7b9456
Updating litmus-clientset and k8s version to 1.21.2 (#174)
Signed-off-by: Shubham Chaudhary <shubham.chaudhary@harness.io>
2022-07-14 18:48:37 +05:30
Udit Gaurav fb1776f9ec
Merge pull request #171 from uditgaurav/update_build_flag
Chore(build): Fix the go build by providing -buildvcs=false flag
2022-04-16 22:42:36 +05:30
uditgaurav a1d0e0e07a Chore(build): Fix the go build by providing -buildvcs=false flag
Signed-off-by: uditgaurav <udit@chaosnative.com>
2022-04-16 14:18:03 +05:30
Udit Gaurav d7c558baa7
Chore(snyk): Fix snyk security scan on chaos-runner (#170)
Signed-off-by: uditgaurav <udit@chaosnative.com>
2022-03-15 08:12:54 +05:30
Udit Gaurav c06566bc86
Chore(vulnerability): Update Go mod pkgs (#169)
Signed-off-by: udit <udit@chaosnative.com>
2022-03-03 18:30:49 +05:30
Udit Gaurav a764c000b8
Merge pull request #168 from uditgaurav/go_version_for_release
Update go version to 1.17 for release build
2021-12-15 17:50:10 +05:30
udit 5469b60df4 Update go version to 1.17 for release build
Signed-off-by: udit <udit@chaosnative.com>
2021-12-15 17:48:23 +05:30
Udit Gaurav 521d6244be
Merge pull request #167 from uditgaurav/update_go.sum
Chore(update): Update go.sum and go version to 1.17 for chaos-runner
2021-12-15 17:09:46 +05:30
udit f3c0cd68f1 Chore(update): Update go.sum for chaos-runner
Signed-off-by: udit <udit@chaosnative.com>
2021-12-15 15:33:56 +05:30
Shubham Chaudhary 177e29c6e0
chore(retry): adding retries for the kubeapi request (#165)
Signed-off-by: shubham chaudhary <shubham@chaosnative.com>
2021-11-10 10:27:41 +05:30
Shubham Chaudhary fa379acb65
Merge pull request #164 from ispeakc0de/health-check
chore(charts): update readme, contributor guide and github actions
2021-10-13 12:56:33 +05:30
shubham chaudhary 6b38232454 chore(charts): update readme, contributor guide and github actions
Signed-off-by: shubham chaudhary <shubham@chaosnative.com>
2021-10-11 13:50:07 +05:30
Udit Gaurav 03ccb255ba
Chore(image): Add the secured litmus hardend alpine base image for chaos-runner (#161)
* Chore(image): update the base image to minimal scratch image for chaos-runner

Signed-off-by: udit <udit@chaosnative.com>

* Resolve Conflict

Signed-off-by: udit <udit@chaosnative.com>

* Chore(hardened-image): Add litmus hardened alpine image and migrate from circle-ci to github actions

Signed-off-by: udit <udit@chaosnative.com>
2021-09-24 16:10:34 +05:30
Shubham Chaudhary fcfcc164df
feat(default-checks): Adding default checks as tunable (#159)
* feat(default-checks): Adding default checks as tunable

Signed-off-by: shubham chaudhary <shubham@chaosnative.com>

* renaming defaultChecks to defaultAppHealthCheck

Signed-off-by: shubham chaudhary <shubham@chaosnative.com>
2021-09-06 16:25:29 +05:30
Shubham Chaudhary 69a3784a0a
fixed the typo and lint issue (#157)
Signed-off-by: shubham chaudhary <shubham@chaosnative.com>
2021-09-06 13:31:46 +05:30
Shubham Chaudhary efcedfc850
(docs): adding host-network for the cmdProbe (#160)
Signed-off-by: shubham chaudhary <shubham@chaosnative.com>
2021-08-18 10:30:08 +05:30
Jakub Stejskal 1adf15ba2a
Add option to change default container registry during image build (#155)
Signed-off-by: Jakub Stejskal <xstejs24@gmail.com>
2021-07-07 11:31:47 +05:30
Julian Nodorp d81b2387c8
feat(command): Pass command from experiment spec to runner (#153)
Closes #152

Signed-off-by: Julian Nodorp <jnodorp@jaconi.io>
2021-06-22 17:26:55 +05:30
Shubham Chaudhary 725864c6f0
fix(bdd): adding retries to wait until job creation (#154)
Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-06-17 14:37:05 +05:30
Udit Gaurav f09f0dcf75
Fix: Print error for some error events (#150)
Signed-off-by: udit <udit@chaosnative.com>
2021-06-11 11:03:38 +05:30
Shubham Chaudhary a767908aea
chore(vendors): updating the target details vendors (#149)
Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-06-11 11:01:52 +05:30
Shubham Chaudhary cbcb1fadc5
chore(chaosresult): updating verdict and status in chaosengine and chaosresult (#148)
Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-06-09 11:29:41 +05:30
Shubham Chaudhary 50480d8d41
chore(env): updated the env getter function & default jobCleanUpPolicy as retain (#144)
* chore(env): improved the env getter function

Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>

* marked default jobCleanupPolicy to retain

Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-06-01 13:57:22 +05:30
Shubham Chaudhary 089308934c
rm(vendor): removing the vendor directory from litmus-go (#147)
Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-05-26 21:44:33 +05:30
Karthik Satchitanand 12916b6b6f
(chore)ci: upper higher run time for bdd (#142)
Signed-off-by: ksatchit <karthik.s@mayadata.io>
2021-05-01 15:44:35 +05:30
Shubham Chaudhary d6334cc66f
log(error): logging the error messages (#140)
* log(errror): logging the error messages

Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>

* update(vendor): updating the vendors

Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-04-30 13:40:01 +05:30
Shubham Chaudhary b44ab7ea0a
chore(terminationGracePeriodSeconds): Add support for termination grace period seconds (#138)
Signed-off-by: shubhamchaudhary <shubham@chaosnative.com>
2021-03-25 16:03:24 +05:30
Udit Gaurav 41722207c3
chore(test): Add unit test to chaos runner (#135)
* chore(test): Add unit test to chaos runner

Signed-off-by: udit <udit@chaosnative.com>
2021-03-15 15:13:02 +05:30
OUM NIVRATHI KALE a13b911fed
update(vendor): updating vendors for Response Timeout (http probe) (#136)
* response timeout for api call

Signed-off-by: oumkale <oum.kale@mayadata.io>

* vendor update

Signed-off-by: oumkale <oum.kale@mayadata.io>
2021-03-15 12:14:37 +05:30
Shubham Chaudhary 0043918b25
feat(imagePullPolicy): Passing lIBImagePullPolicy ENV (#134)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2021-02-22 10:13:50 +05:30
Udit Gaurav 10676e040b
Chore(unit-test): Add new tests to chaos-runner (#132)
* Chore(unit-test): Add new tests to chaos-runner

Signed-off-by: udit <udit.gaurav@mayadata.io>
2021-02-15 21:31:42 +05:30
Shubham Chaudhary e0ca83e3df
update(vendor): updating vendors for http probe (#133)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2021-02-15 19:33:39 +05:30
Udit Gaurav 47c25d8813
chore(push): Update push script to push the latest tag on release (#124)
Signed-off-by: udit <udit.gaurav@mayadata.io>

Co-authored-by: Shubham Chaudhary <shubham.chaudhary@mayadata.io>
2021-01-21 00:09:44 +05:30
Chris Vermeulen 5bcda9213d
Add support for overriding file type in hostFileVolume mount (#129)
* Add support for overriding file type in hostFileVolume mount, and defaulting to `File` as is currently.

This enables overriding the file mount type for things such as sockets (/var/run/crio/crio.sock in this case), which OpenShift >4.6 does not support mounting as `File`

Signed-off-by: Christiaan Vermeulen <chrism.vermeulen@gmail.com>

* Update chaos-operator version to include new volume type

Signed-off-by: Christiaan Vermeulen <chrism.vermeulen@gmail.com>
2021-01-18 22:02:21 +05:30
Karthik Satchitanand 2ce9946529
(chore)vendor: update vendor based on latest changes to chaos operator (#127)
Signed-off-by: ksatchit <karthik.s@mayadata.io>
2021-01-08 23:20:08 +05:30
Shubham Chaudhary ef8cf9be68
chore(events): update event message with increment (#125)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2021-01-05 07:18:17 +05:30
Shubham Chaudhary 52f1b762da
chore(volumes): fix the volume builder operations (#122)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-12-20 20:46:23 +05:30
Kazuki Nitta b242b96bc4
Move ENVs to corev1.EnvVar instead of custom struct (#121)
* move ENVs to corev1.EnvVar instead of custom struct

Previously, ENVs can only use plain text.
This patch allows to use corev1.EnvVar at CR schema so that it can get value from secret/configmap/downwardAPI.

Fixes https://github.com/litmuschaos/litmus/issues/2368

Signed-off-by: kazukousen <mmchari.0228@gmail.com>

* Refactored slightly BDD

Signed-off-by: kazukousen <mmchari.0228@gmail.com>
2020-12-15 10:37:25 +05:30
Shubham Chaudhary d4e4e50cad
chore(runner): fix the experiment not found events (#120)
* refactor(chaos-runner): refectored the chaos-runner

Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>

* chore(runner): fix the experiment not found events

Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-12-12 16:00:16 +05:30
Shubham Chaudhary 85301043f7
refactor(chaos-runner): refectored the chaos-runner (#117)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-12-12 14:52:34 +05:30
Shubham Chaudhary 9e575e9e13
chore(vendors): updating vendors for probe (#119)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-12-11 11:55:47 +05:30
Shubham Chaudhary e48cef5ec8
chore(promql-cli): updating vendors for promProbe (#118)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-12-03 12:41:44 +05:30
Shubham Chaudhary 09ce557586
chore(imagePullPolicy): passing libImagePullPolicy env to exp job (#116)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-11-25 11:52:17 +05:30
Udit Gaurav 1e1e242162
chore(image): Reduce image size of runner (#115)
Signed-off-by: Udit Gaurav <udit.gaurav@mayadata.io>
2020-11-24 21:47:24 +05:30
Shubham Chaudhary 009dd1f96c
chore(jobcleanup): Delete the helper pod based on jobCleanupPolicy (#114)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-11-23 16:44:40 +05:30
Shubham Chaudhary 3947247250
chore(annotation): multi app annotation support (#113)
* chore(annotation): multi app annotation support

Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-11-19 13:09:07 +05:30
Udit Gaurav 2087b282f3
chore(add): Add multiarch image into master chaos-runner (#111)
Signed-off-by: Udit Gaurav <udit.gaurav@mayadata.io>
2020-11-15 18:13:08 +05:30
Karthik Satchitanand 1559f09efd
(chore)vendor: update latest dependencies (#109)
Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-11-14 21:11:14 +05:30
Karthik Satchitanand 77aa094fd7
(enhancement)runner: specify tolerations for experiment pod (#106)
* (enhancement)runner: specify tolerations for experiment pod

Signed-off-by: ksatchit <karthik.s@mayadata.io>

* (chore)vendor: update vendor info

Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-10-28 20:57:40 +05:30
Shubham Chaudhary 49f4df2d24
chore(events): Replacing recorder by v1.configmaps (#104)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-10-22 22:33:02 +05:30
Karthik Satchitanand 80d344410c
(chore)vendor: update to latest vendored litmus dependencies (#102)
Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-10-15 18:02:02 +05:30
Shubham Chaudhary fc508f65c0
fix(timeout): fixing the timeout for pending state (#100)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-10-14 18:17:34 +05:30
UDIT GAURAV 729f19b187
chore(status_fix): Pending status fix of runner (#98)
Signed-off-by: Udit Gaurav <udit.gaurav@mayadata.io>
2020-10-10 15:14:07 +05:30
UDIT GAURAV 1b3c6cbd58
fix(timeout): Add timeout when experiment pod fails (#97)
Signed-off-by: Udit Gaurav <udit.gaurav@mayadata.io>
2020-10-06 20:27:01 +05:30
Shubham Chaudhary 9913f639c0
chore(probe): restructure the probe schema (#92)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-10-05 16:01:31 +05:30
Shubham Chaudhary b6413a1dfc
update(vendors): enhancing k8s probe to support CRUD operations (#96)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-10-05 14:28:14 +05:30
Karthik Satchitanand 917f4e576c
(enhancement)runner: specify imagepullsecrets for experiment pod (#95)
* (enhancement)runner: specify imagepullsecrets for experiment pod

Signed-off-by: ksatchit <karthik.s@mayadata.io>

* (chore)vendor: pull the vendor dependencies

Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-09-30 21:25:23 +05:30
Shubham Chaudhary d0055cc7d4
chore(resourceRequirements): Adding resource requirements in chaos pod (#93)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-09-29 19:06:43 +05:30
Shubham Chaudhary 8198727f70
chore(log-mgmt): Refactoring code for better log/error mgmt (#91)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-09-21 15:51:34 +05:30
Shubham Chaudhary 7e132858eb
chore(k8sprobe): updating the chaos-operator vendors, pull k8sProbe changes in ci tag (#89)
* chore(k8sprobe): updating the chaos-operator vendors, pull k8sProbe changes in ci tag

Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-09-17 22:23:32 +05:30
Shubham Chaudhary cb03276feb
chore(exp-name): Passing experiment from chaos-runner as env (#86)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-09-07 15:22:32 +05:30
Shubham Chaudhary 6684401ad9
chore(chaos-result): appending the instance_id with the chaos-result name (#85)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-09-04 18:56:35 +05:30
Shubham Chaudhary 183436041a
chore(continuous-probe): Adding the continuous probes in go experiments (#84)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-09-03 18:03:26 +05:30
UDIT GAURAV 646365d50b
chore(Dockerfile): Add user_id in the dockerfile of chaos-runner (#83)
Signed-off-by: Udit Gaurav <uditgaurav@gmail.com>
2020-08-20 17:11:57 +05:30
Shubham Chaudhary 06bfa18213
chore(developer-guide): Adding the developer-guide in chaos-runner (#81)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-08-15 19:49:54 +05:30
Shubham Chaudhary c095cb68f9
update(chaos-pod): Changing the restart policy of chaos-pod (#80)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-08-15 11:32:52 +05:30
Shubham Chaudhary 88b298d05d
update(chaosresult): Updating the chaosresult_types.go file (#79)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-08-13 15:13:32 +05:30
Shubham Chaudhary 424b0030dd
feat(probe): Adding the probe for the chaosexperiments (#78)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-08-13 11:43:03 +05:30
Karthik Satchitanand e2446438d8
(enhancement)engine_status_patch: enhance engine status patch logic based on updated schema (#76)
* (enhancement)engine_status_patch: enhance engine statuspatch logic based on updated schema

Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-07-31 22:12:21 +05:30
Shubham Chaudhary 558adf8fe8
feat(timeout): Adding the timeout in the experiments (#75)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-07-29 22:37:27 +05:30
Shubham Chaudhary f9f0bdf5e8
feat(istio): Adding support to run in istio enabled mode (#73)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-07-17 21:35:26 +05:30
Karthik Satchitanand 7d10d376d7
(feature)runner: add host file mount support for experiment job (#72)
* (feature)runner: add hostfile mount support for experiment job

Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-07-16 16:39:06 +05:30
Karthik Satchitanand ed59926e83
(enhance)experiment_pod: add support for nodeSelector on experiment pods (#70)
* (enhance)experiment_pod: add support for nodeSelector on experiment pods

Signed-off-by: ksatchit <karthik.s@mayadata.io>

* (chore)vendor: update runner dependencies

Signed-off-by: ksatchit <karthik.s@mayadata.io>

* (chore)add: remove unused comments and align spaces

Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-07-04 18:09:57 +05:30
Shubham Chaudhary 987c6fde54
feat(security-context): adding the security context in experiment CR (#68)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-07-03 20:07:55 +05:30
Shubham Chaudhary cf59ccde4d
update(image): override the chaos experiment image from chaos engine (#69)
* update(image): override the chaos experiment image from chaos engine

Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-07-02 18:04:05 +05:30
Karthik Satchitanand 7c5ff8646b
(enhancement)experiment: add experiment imagePullPolicy attribute (#67)
* (enhancement)experiment: add imagepullpolicy attribute

Signed-off-by: ksatchit <karthik.s@mayadata.io>

* (chore)dependencies: update the go mod to work with latest operator changes

Signed-off-by: ksatchit <karthik.s@mayadata.io>

* (fix)runner: add default value for pull policy

Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-06-11 16:12:20 +05:30
Rahul M Chheda 6c5632d108
Added go.mod and deprecated dep (#66)
* Added go.mod and deprecated dep ensure

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-05-08 00:52:22 +05:30
Rahul M Chheda 92717ba67b
(chore) Updated vendor for latest changes (#65)
* (chore) Updated Vendor (litmuchaos/elves:master, litmuschaos/chaos-operator:master)
    - Replaced break with continue, to continue from next experiment
    - Removed validation for empty annotation

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>

* (fix) CleanUp Policy String

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>

* Removed check for Annotations

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>

* Fixed recorder message

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>

* Removed unnecessary comments

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>

* Added Annotation in Builder

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-05-02 14:32:07 +05:30
vijayto 2becc3557f
Updating chaos runner for https://github.com/litmuschaos/litmus/issues/1421 (#62)
* Updating runner to add annotations to experiment pod based on engine specification

Signed-off-by: Vijay Thomas <vijay_thomas@intuit.com>
2020-04-23 19:27:30 +05:30
Rahul M Chheda c8a28251f6
(fix) Fixed BDD URL issues (#64)
Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-04-15 18:49:52 +05:30
Raj Babu Das 60e08240e5
(feat): Adding trivy security check to circle ci (#58)
* Adding trivy security scan for chaos runner docker image

Signed-off-by: Raj <raj.das@mayadata.io>
2020-04-14 16:00:16 +05:30
Karthik Satchitanand 6ad2f1923b
(fix)bdd: remove checks for monitor pods (#61)
Signed-off-by: ksatchit <karthik.s@mayadata.io>
2020-04-12 11:31:09 +05:30
Rahul M Chheda 53bcf3bb01
(fix) Creation of Multiple ExperimentStatuses fixed (#60)
* (fix) ExperimentStatuses Fixed

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-04-08 08:12:47 +05:30
Shubham Chaudhary 2d7464790b
refcator(chaos_ns): Rename Engine_NAMESPACE to CHAOS_NAMESPACE (#57)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-03-30 19:40:48 +05:30
Rahul M Chheda 2116f17c4f
(feat) Added support for AdminMode (#45)
* (feat) Added support for AdminMode
    - Replaced EngineDetails.AppNamespace to EngineDetails.EngineNamespace
    - Added logic to check for resources in chaosEngine namespace
    - Only job will be created in AppNamespace (experiment.Namespace)
    - Synced the experiment.Namespace = engineDetails.EngineNamespace, when AdminMode is true
    - Added OS ENV as ENGINE_NAMESPACE passed from operator, into engineDetails struct
* Sycned experiment.Namespace and engineDetails.EngineNamespace

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-03-27 12:00:15 +05:30
Rahul M Chheda e2ed7c2fad
(fix) Added name of the runner Pod in ChaosEngine Events (#56)
Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-03-17 14:43:30 +05:30
fossabot 2c1c04e04f
Add license scan report and status (#46)
Signed-off-by: fossabot <badges@fossa.io>
2020-03-13 22:53:34 +05:30
Karthik Satchitanand 33384e3e4b
(chore)notice: add license notice (#51)
* (chore)add license notice

Signed-off-by: ksatchit <ksatchit@mayadata.io>
2020-03-13 22:44:38 +05:30
Rahul M Chheda 0b52735e3a
(feat) Added Event of ChaosEngine for Skipped Experiments (#52)
* (feat) Added Event for SkippedExperimnet

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-03-12 17:14:30 +05:30
Shubham Chaudhary 2291b4f980
Merge pull request #50 from ispeakc0de/update-result
update(chaos-result) Getting Verdict from the chaos-result status
2020-03-09 20:16:42 +05:30
shubhamchaudhary 02a30ee34d update(chaos-result) Getting Verdict from the chaosResult status
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-03-09 19:47:56 +05:30
Rahul M Chheda 996890b718
(feat) Addition of events in Chaos-Runner (#47)
* (feat) Addition of events in Chaos-Runner

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-03-09 13:27:20 +05:30
Rahul M Chheda a6946f767f
(chore) Changed DefaultMode of Job Deletion as Always (#48)
Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>

Co-authored-by: Karthik Satchitanand <karthik.s@mayadata.io>
2020-03-09 13:05:11 +05:30
Shubham Chaudhary f341b8f1ce
feat(downwardAPI): Adding steps to get job-pod name using downward api (#49)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-03-07 13:24:02 +05:30
Rahul M Chheda 9599175389
(fix) Renamed misspelled keywords (#44)
Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-02-19 14:24:26 +05:30
Rahul M Chheda 9761ffdd31
(chore) Rename litmuschaos/kube-helper to litmuschaos/elves (#43)
Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-02-11 12:27:33 +05:30
Rahul M Chheda fac3a1e5c4
(chore) Rename Chaos-executor to Chaos-runner (#41)
* (chore) Rename Chaos-operator into Chaos-runner

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-02-11 10:27:49 +05:30
Shubham Chaudhary 78c8a7c456
(refactor): Updated engineUID to chaosUID (#40)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-02-10 18:59:38 +05:30
Shubham Chaudhary 8e157b4b10 (feat): Pass chaosUID as labels in chaos Job (#38)
* (feat): Pass engineUID as labels in chaos Job

Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-02-10 15:24:24 +05:30
Rahul M Chheda 67299ac9a7
(feat) Enhanced Logging Mechanism (#35)
- Added logging based on k8s.io/klog & used error package to wrap errors.

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-02-07 16:51:34 +05:30
Shubham Chaudhary 7edf28be4b
(feat): Add support for Pass engine UUID to dependent resources in experiments (#34)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-02-07 15:54:41 +05:30
Rahul M Chheda 36581291e4
(feat) Added DockerPull Count in README.md (#37)
Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-02-06 23:12:37 +05:30
Rahul M Chheda 8bec241af3
(bdd/chore) Basic BDD testcase for chaos-executor (#25)
* (feat) bdd-test: Added a basic BDD test

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-02-03 01:46:07 +05:30
Shubham Chaudhary c8c9a939ad (feat): Add ability to get ConfigMaps/Secret from ChaosEngine (#33)
(feat): Add ability to provide ConfigMaps/Secret in engine
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-01-21 15:29:18 +05:30
Shubham Chaudhary d57a3dbc6e (feat): Add auxiliaryAppInfo in go-executor (#31)
Signed-off-by: shubhamchaudhary <shubham.chaudhary@mayadata.io>
2020-01-14 13:21:39 +05:30
Rahul M Chheda aff2dcdcc0 (chore) Added GoReport Card, and BCH Card in README.md (#28)
* (chore) Added GoReport Card, and BCH Card in README.md

Signed-off-by: Rahul M Chheda <rahul.chheda@mayadata.io>
2020-01-10 18:09:10 +05:30
Karthik Satchitanand ec610af138 (fix)typo in circle ci config (#27)
Signed-off-by: ksatchit <ksatchit@mayadata.io>
2020-01-10 13:23:00 +05:30
1831 changed files with 7273 additions and 847536 deletions

View File

@ -1,114 +0,0 @@
# Golang CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-go/ for more details
version: 2.1
jobs:
build:
machine:
image: circleci/classic:201808-01
working_directory: ~/go/src/github.com/litmuschaos/chaos-executor
environment:
K8S_VERSION: v1.12.0
KUBECONFIG: /home/circleci/.kube/config
MINIKUBE_VERSION: v1.3.1
HOME: /home/circleci
CHANGE_MINIKUBE_NONE_USER: true
REPONAME: litmuschaos
IMGNAME: chaos-executor
IMGTAG: ci
steps:
- checkout
# - run:
# name: Setup kubectl
# command: |
# curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/${K8S_VERSION}/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/
# - run:
# name: Setup minikube
# command: |
# curl -Lo minikube https://storage.googleapis.com/minikube/releases/${MINIKUBE_VERSION}/minikube-linux-amd64 && chmod +x minikube
# sudo install minikube /usr/local/bin
# - run:
# name: Start minikube
# command: |
# sudo -E minikube start --vm-driver=none --cpus 2 --memory 4096 --kubernetes-version=${K8S_VERSION}
- run: mkdir -p workspace
- run:
name: Setup ENV
command: |
echo 'export GOPATH="$HOME/go"' >> workspace/env-vars
echo 'export PATH="$GOPATH/bin:$PATH"' >> workspace/env-vars
echo 'export REPONAME="litmuschaos"' >> workspace/env-vars
echo 'export IMGNAME="chaos-executor"' >> workspace/env-vars
echo 'export IMGTAG="ci"' >> workspace/env-vars
cat workspace/env-vars >> $BASH_ENV
source $BASH_ENV
- run: make all
- run: |
docker save -o workspace/image.tar ${REPONAME}/${IMGNAME}:${IMGTAG}
- persist_to_workspace:
root: workspace
paths:
- image.tar
- env-vars
push:
machine:
image: circleci/classic:201808-01
environment:
IMGTAG: ci
working_directory: ~/go/src/github.com/litmuschaos/chaos-executor
steps:
- attach_workspace:
at: /tmp/workspace
- run: |
cat /tmp/workspace/env-vars >> $BASH_ENV
- checkout
- run: |
docker load -i /tmp/workspace/image.tar
~/go/src/github.com/litmuschaos/chaos-executor/build/push --type=ci
release:
machine:
image: circleci/classic:201808-01
environment:
IMGTAG: ci
working_directory: ~/go/src/github.com/litmuschaos/chaos-executor
steps:
- attach_workspace:
at: /tmp/workspace
- run: |
cat /tmp/workspace/env-vars >> $BASH_ENV
- checkout
- run: |
docker load -i /tmp/workspace/image.tar
~/go/src/github.com/litmuschaos/chaos-executor/builds/push --type=release
workflows:
version: 2
executor_build_deploy:
jobs:
- build:
filters:
## build jobs needs to be run for branch commits as well as tagged releases
tags:
only: /.*/
- push:
requires:
- build
filters:
## push jobs needs to be run for branch commits as well as tagged releases
## docker images push won't be performed for PRs due to ENV not being applied
tags:
only: /.*/
- release:
requires:
- build
filters:
## release jobs needs to be run for tagged releases alone & not for any branch commits
branches:
ignore: /.*/
tags:
only: /.*/

5
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,5 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
# These owners will be the default owners for everything in the repo.
* @ispeakc0de @ksatchit @uditgaurav

23
.github/auto-merge.yml vendored Normal file
View File

@ -0,0 +1,23 @@
# Configuration for probot-auto-merge - https://github.com/bobvanderlinden/probot-auto-merge
reportStatus: true
updateBranch: false
deleteBranchAfterMerge: true
mergeMethod: squash
minApprovals:
COLLABORATOR: 0
maxRequestedChanges:
NONE: 0
blockingLabels:
- DO NOT MERGE
- WIP
- blocked
# Will merge whenever the above conditions are met, but also
# the owner has approved or merge label was added.
rules:
- minApprovals:
OWNER: 1
- requiredLabels:
- merge

115
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,115 @@
name: build-pipeline
on:
pull_request:
branches: [master]
jobs:
pre-checks:
runs-on: ubuntu-latest
steps:
# Install golang
- uses: actions/setup-go@v2
with:
go-version: 1.20.0
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
- name: gofmt check
run: make gofmt-check
- name: golangci-lint
uses: reviewdog/action-golangci-lint@v1
- name: unused-package check
run: make unused-package-check
gitleaks-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Run GitLeaks
run: |
wget https://github.com/gitleaks/gitleaks/releases/download/v8.18.2/gitleaks_8.18.2_linux_x64.tar.gz && \
tar -zxvf gitleaks_8.18.2_linux_x64.tar.gz && \
sudo mv gitleaks /usr/local/bin && gitleaks detect --source . -v
trivy:
needs: pre-checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: Build an image from Dockerfile
run: |
docker build -f build/Dockerfile -t docker.io/litmuschaos/chaos-runner:${{ github.sha }} . --build-arg TARGETPLATFORM=linux/amd64
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'docker.io/litmuschaos/chaos-runner:${{ github.sha }}'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'
image-build:
runs-on: ubuntu-latest
needs: pre-checks
steps:
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
- name: Build Docker Image
env:
DOCKER_REPO: litmuschaos
DOCKER_IMAGE: chaos-runner
DOCKER_TAG: ci
run: |
make build-amd64
tests:
runs-on: ubuntu-latest
needs: image-build
steps:
# Install golang
- uses: actions/setup-go@v2
with:
go-version: 1.22.0
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
#Install and configure a kind cluster
- name: Installing Prerequisites (K3S Cluster)
env:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
run: |
curl -sfL https://get.k3s.io | INSTALL_K3S_VERSION=v1.21.11+k3s1 sh -s - --docker --write-kubeconfig-mode 664
kubectl wait node --all --for condition=ready --timeout=90s
mkdir -p $HOME/.kube
cp /etc/rancher/k3s/k3s.yaml $HOME/.kube/config
kubectl get nodes
- name: Dependency checks
env:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
run: |
make deps
- name: Running Go BDD Test
env:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
run: |
make test

61
.github/workflows/push.yml vendored Normal file
View File

@ -0,0 +1,61 @@
name: push-pipeline
on:
push:
branches:
- master
tags-ignore:
- '**'
jobs:
pre-checks:
runs-on: ubuntu-latest
steps:
# Install golang
- uses: actions/setup-go@v2
with:
go-version: 1.22.0
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
- name: gofmt check
run: make gofmt-check
- name: golangci-lint
uses: reviewdog/action-golangci-lint@v2
- name: unused-package check
run: make unused-package-check
image-build:
runs-on: ubuntu-latest
steps:
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: login to GitHub Container Registry
run: echo ${{ secrets.DPASS }} | docker login -u ${{ secrets.DNAME }} --password-stdin
- name: Build & Push Docker Image
env:
DOCKER_REPO: ${{ secrets.DOCKER_REPO }}
DOCKER_IMAGE: ${{ secrets.DOCKER_IMAGE }}
DOCKER_TAG: ci
DNAME: ${{ secrets.DNAME }}
DPASS: ${{ secrets.DPASS }}
run: make push-chaos-runner

65
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: release-pipeline
on:
create:
tags:
- '**'
jobs:
pre-checks:
runs-on: ubuntu-latest
steps:
# Install golang
- uses: actions/setup-go@v2
with:
go-version: 1.22.0
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
image-build:
runs-on: ubuntu-latest
steps:
# Checkout to the latest commit
# On specific directory/path
- name: Checkout
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
with:
version: latest
- name: login to GitHub Container Registry
run: echo ${{ secrets.DPASS }} | docker login -u ${{ secrets.DNAME }} --password-stdin
- name: Set Tag
run: |
TAG="${GITHUB_REF#refs/*/}"
echo "TAG=${TAG}" >> $GITHUB_ENV
echo "RELEASE_TAG=${TAG}" >> $GITHUB_ENV
- name: Build & Push Docker Image with version tag
env:
DOCKER_REPO: ${{ secrets.DOCKER_REPO }}
DOCKER_IMAGE: ${{ secrets.DOCKER_IMAGE }}
DOCKER_TAG: ${RELEASE_TAG}
DNAME: ${{ secrets.DNAME }}
DPASS: ${{ secrets.DPASS }}
run: make push-chaos-runner
- name: Build & Push Docker Image with latest
env:
DOCKER_REPO: ${{ secrets.DOCKER_REPO }}
DOCKER_IMAGE: ${{ secrets.DOCKER_IMAGE }}
DOCKER_TAG: latest
DNAME: ${{ secrets.DNAME }}
DPASS: ${{ secrets.DPASS }}
run: make push-chaos-runner

24
.github/workflows/security-scan.yml vendored Normal file
View File

@ -0,0 +1,24 @@
---
name: Security Scan
on:
workflow_dispatch:
jobs:
trivy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Build an image from Dockerfile
run: |
docker build -f build/Dockerfile -t docker.io/litmuschaos/chaos-runner:${{ github.sha }} . --build-arg TARGETARCH=amd64
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'docker.io/litmuschaos/chaos-runner:${{ github.sha }}'
format: 'table'
exit-code: '1'
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH'

2
.gitignore vendored
View File

@ -1 +1,3 @@
build/_output/
.idea/
vendor/modules.txt

62
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,62 @@
# Contributing to Litmus Chaos-Runner
Litmus is an Apache 2.0 Licensed project and uses the standard GitHub pull requests process to review and accept contributions.
There are several areas of Litmus that could use your help. For starters, you could help in improving the sections in this document by either creating a new issue describing the improvement or submitting a pull request to this repository.
- If you are a first-time contributor, please see [Steps to Contribute](#steps-to-contribute).
- If you would like to suggest new tests to be added to litmus, please go ahead and [create a new issue](https://github.com/litmuschaos/litmus/issues/new) describing your test. All you need to do is specify the workload type and the operations that you would like to perform on the workload.
- If you would like to work on something more involved, please connect with the Litmus Contributors.
- If you would like to make code contributions, all your commits should be signed with Developer Certificate of Origin. See [Sign your work](#sign-your-work).
## Steps to Contribute
- Find an issue to work on or create a new issue. The issues are maintained at [litmuschaos/litmus](https://github.com/litmuschaos/litmus/issues). You can pick up from a list of [good-first-issues](https://github.com/litmuschaos/litmus/labels/good%20first%20issue).
- Claim your issue by commenting your intent to work on it to avoid duplication of efforts.
- Fork the repository on GitHub.
- Create a branch from where you want to base your work (usually master).
- Make your changes.
- Relevant coding style guidelines are the [Go Code Review Comments](https://code.google.com/p/go-wiki/wiki/CodeReviewComments) and the _Formatting and style_ section of Peter Bourgon's [Go: Best Practices for Production Environments](http://peter.bourgon.org/go-in-production/#formatting-and-style).
- Commit your changes by making sure the commit messages convey the need and notes about the commit.
- Push your changes to the branch in your fork of the repository.
- Submit a pull request to the original repository. See [Pull Request checklist](#pull-request-checklist)
## Pull Request Checklist
- Rebase to the current master branch before submitting your pull request.
- Commits should be as small as possible. Each commit should follow the checklist below:
- For code changes, add tests relevant to the fixed bug or new feature
- Pass the compile and tests - includes spell checks, formatting, etc
- Commit header (first line) should convey what changed
- Commit body should include details such as why the changes are required and how the proposed changes
- DCO Signed
- If your PR is not getting reviewed or you need a specific person to review it, please reach out to the Litmus contributors at the [Litmus slack channel](https://app.slack.com/client/T09NY5SBT/CNXNB0ZTN)
## Sign your work
We use the Developer Certificate of Origin (DCO) as an additional safeguard for the LitmusChaos project. This is a well established and widely used mechanism to assure that contributors have confirmed their right to license their contribution under the project's license. Please add a line to every git commit message:
```sh
Signed-off-by: Random J Developer <random@developer.example.org>
```
Use your real name (sorry, no pseudonyms or anonymous contributions). The email id should match the email id provided in your GitHub profile.
If you set your `user.name` and `user.email` in git config, you can sign your commit automatically with `git commit -s`.
You can also use git [aliases](https://git-scm.com/book/tr/v2/Git-Basics-Git-Aliases) like `git config --global alias.ci 'commit -s'`. Now you can commit with `git ci` and the commit will be signed.
## Setting up your Development Environment
This project is implemented using Go and uses the standard golang tools for development and build. In addition, this project heavily relies on Docker and Kubernetes. It is expected that the contributors.
- are familiar with working with Go
- are familiar with Docker containers
- are familiar with Kubernetes and have access to a Kubernetes cluster or Minikube to test the changes.
For setting up a Development environment on your local host, see the detailed instructions [here](./docs/developer.md).
## Community
The litmus community will have a monthly community sync-up on 3rd Wednesday 22.00-23.00IST / 18.30-19.30CEST
- The community meeting details are available [here](https://hackmd.io/a4Zu_sH4TZGeih-xCimi3Q). Please feel free to join the community meeting.

550
Gopkg.lock generated
View File

@ -1,550 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:a2682518d905d662d984ef9959984ef87cecb777d379bfa9d9fe40e78069b3e4"
name = "github.com/PuerkitoBio/purell"
packages = ["."]
pruneopts = "UT"
revision = "44968752391892e1b0d0b821ee79e9a85fa13049"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:c739832d67eb1e9cc478a19cc1a1ccd78df0397bf8a32978b759152e205f644b"
name = "github.com/PuerkitoBio/urlesc"
packages = ["."]
pruneopts = "UT"
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
[[projects]]
digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73"
version = "v1.1.1"
[[projects]]
digest = "1:e15b0065da1011473634ffa0dda17731ea1d5f6fb8bb87905216d95fa29eaec0"
name = "github.com/emicklei/go-restful"
packages = [
".",
"log"
]
pruneopts = "UT"
revision = "99f05a26a0a1c71e664ebe6a76d29b2c80333056"
version = "v2.11.1"
[[projects]]
digest = "1:ed15647db08b6d63666bf9755d337725960c302bbfa5e23754b4b915a4797e42"
name = "github.com/go-openapi/jsonpointer"
packages = ["."]
pruneopts = "UT"
revision = "ed123515f087412cd7ef02e49b0b0a5e6a79a360"
version = "v0.19.3"
[[projects]]
digest = "1:451fe53c19443c6941be5d4295edc973a3eb16baccb940efee94284024be03b0"
name = "github.com/go-openapi/jsonreference"
packages = ["."]
pruneopts = "UT"
revision = "82f31475a8f7a12bc26962f6e26ceade8ea6f66a"
version = "v0.19.3"
[[projects]]
digest = "1:a60f47e736cc96075451c3c3502274d86ec6d96588c03d1ab56142ccbb8a5364"
name = "github.com/go-openapi/spec"
packages = ["."]
pruneopts = "UT"
revision = "772572fd19ebcc983369e53bfaed4bde2077fe0c"
version = "v0.19.5"
[[projects]]
digest = "1:d79ccf65d7721efa48200dc7fc827f8fb29e1fba4db4d55be675fdec84e7cfe1"
name = "github.com/go-openapi/swag"
packages = ["."]
pruneopts = "UT"
revision = "8a84ec635f1b280a7062edeab609f0667a053248"
version = "v0.19.6"
[[projects]]
digest = "1:582e25eccee928dc12416ea4c23b6dae8f3b5687730632aa1473ebebe80a2359"
name = "github.com/gogo/protobuf"
packages = [
"proto",
"sortkeys"
]
pruneopts = "UT"
revision = "5628607bb4c51c3157aacc3a50f0ab707582b805"
version = "v1.3.1"
[[projects]]
digest = "1:f5ce1529abc1204444ec73779f44f94e2fa8fcdb7aca3c355b0c95947e4005c6"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
]
pruneopts = "UT"
revision = "6c65a5562fc06764971b7c5d05c76c75e84bdbf7"
version = "v1.3.2"
[[projects]]
digest = "1:a6181aca1fd5e27103f9a920876f29ac72854df7345a39f3b01e61c8c94cc8af"
name = "github.com/google/gofuzz"
packages = ["."]
pruneopts = "UT"
revision = "f140a6486e521aad38f5917de355cbf147cc0496"
version = "v1.0.0"
[[projects]]
digest = "1:ca4524b4855ded427c7003ec903a5c854f37e7b1e8e2a93277243462c5b753a8"
name = "github.com/googleapis/gnostic"
packages = [
"OpenAPIv2",
"compiler",
"extensions"
]
pruneopts = "UT"
revision = "ab0dd09aa10e2952b28e12ecd35681b20463ebab"
version = "v0.3.1"
[[projects]]
digest = "1:78d28d5b84a26159c67ea51996a230da4bc07cac648adaae1dfb5fc0ec8e40d3"
name = "github.com/imdario/mergo"
packages = ["."]
pruneopts = "UT"
revision = "1afb36080aec31e0d1528973ebe6721b191b0369"
version = "v0.3.8"
[[projects]]
branch = "master"
digest = "1:289a0aeeff2a467506d6faacb71c783a8610e53639e8f065e1997a569e622d6b"
name = "github.com/jpillora/go-ogle-analytics"
packages = ["."]
pruneopts = "UT"
revision = "14b04e0594ef6a9fd943363b135656f0ec8c9d0e"
[[projects]]
digest = "1:beb5b4f42a25056f0aa291b5eadd21e2f2903a05d15dfe7caf7eaee7e12fa972"
name = "github.com/json-iterator/go"
packages = ["."]
pruneopts = "UT"
revision = "03217c3e97663914aec3faafde50d081f197a0a2"
version = "v1.1.8"
[[projects]]
digest = "1:31e761d97c76151dde79e9d28964a812c46efc5baee4085b86f68f0c654450de"
name = "github.com/konsorten/go-windows-terminal-sequences"
packages = ["."]
pruneopts = "UT"
revision = "f55edac94c9bbba5d6182a4be46d86a2c9b5b50e"
version = "v1.0.2"
[[projects]]
branch = "master"
digest = "1:d749e6135d365e52dcd8f5abba397a71837039f3f64f837a24626ec1efb3beb2"
name = "github.com/litmuschaos/chaos-operator"
packages = [
"pkg/apis/litmuschaos/v1alpha1",
"pkg/client/clientset/versioned",
"pkg/client/clientset/versioned/scheme",
"pkg/client/clientset/versioned/typed/litmuschaos/v1alpha1"
]
pruneopts = "UT"
revision = "256ec605bf91f6e251cf5301363251f82c60d3c0"
[[projects]]
branch = "master"
digest = "1:ce2d3165cb048c71ecbaa2688cd2cc8b4743b2286820f96a3c847910acf7e6cc"
name = "github.com/litmuschaos/kube-helper"
packages = [
"kubernetes/container",
"kubernetes/job",
"kubernetes/jobspec",
"kubernetes/podtemplatespec",
"kubernetes/volume/v1alpha1"
]
pruneopts = "UT"
revision = "ca5e1d6ff7c71f7bc4092bc88b377658844966c7"
[[projects]]
digest = "1:927762c6729b4e72957ba3310e485ed09cf8451c5a637a52fd016a9fe09e7936"
name = "github.com/mailru/easyjson"
packages = [
"buffer",
"jlexer",
"jwriter"
]
pruneopts = "UT"
revision = "1b2b06f5f209fea48ff5922d8bfb2b9ed5d8f00b"
version = "v0.7.0"
[[projects]]
digest = "1:33422d238f147d247752996a26574ac48dcf472976eda7f5134015f06bf16563"
name = "github.com/modern-go/concurrent"
packages = ["."]
pruneopts = "UT"
revision = "bacd9c7ef1dd9b15be4a9909b8ac7a4e313eec94"
version = "1.0.3"
[[projects]]
digest = "1:e32bdbdb7c377a07a9a46378290059822efdce5c8d96fe71940d87cb4f918855"
name = "github.com/modern-go/reflect2"
packages = ["."]
pruneopts = "UT"
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
version = "1.0.1"
[[projects]]
digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
version = "v1.4.2"
[[projects]]
digest = "1:524b71991fc7d9246cc7dc2d9e0886ccb97648091c63e30eef619e6862c955dd"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "2e9d26c8c37aae03e3f9d4e90b7116f5accb7cab"
version = "v1.0.5"
[[projects]]
branch = "master"
digest = "1:bbe51412d9915d64ffaa96b51d409e070665efc5194fcf145c4a27d4133107a4"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
pruneopts = "UT"
revision = "e9b2fee46413994441b28dfca259d911d963dfed"
[[projects]]
branch = "master"
digest = "1:e4a92f168e339bc2e697401a883588748fa58e0dd3ae14160f9bd890ca37382d"
name = "golang.org/x/net"
packages = [
"context",
"context/ctxhttp",
"http/httpguts",
"http2",
"http2/hpack",
"idna"
]
pruneopts = "UT"
revision = "c0dbc17a35534bf2e581d7a942408dc936316da4"
[[projects]]
branch = "master"
digest = "1:55d36eb35766a3f105fa36cfb2faf053cc33c019108a6adee799c4e7d59d62b2"
name = "golang.org/x/oauth2"
packages = [
".",
"internal"
]
pruneopts = "UT"
revision = "858c2ad4c8b6c5d10852cb89079f6ca1c7309787"
[[projects]]
branch = "master"
digest = "1:472d3f5adc36e1c0b9120bc3830d692a090134aa5461335f39caabd0fdb7feac"
name = "golang.org/x/sys"
packages = [
"unix",
"windows"
]
pruneopts = "UT"
revision = "ac6580df4449443a05718fd7858c1f91ad5f8d20"
[[projects]]
digest = "1:66a2f252a58b4fbbad0e4e180e1d85a83c222b6bce09c3dcdef3dc87c72eda7c"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/language",
"internal/language/compact",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
"width"
]
pruneopts = "UT"
revision = "342b2e1fbaa52c93f31447ad2c6abc048c63e475"
version = "v0.3.2"
[[projects]]
branch = "master"
digest = "1:a2f668c709f9078828e99cb1768cb02e876cb81030545046a32b54b2ac2a9ea8"
name = "golang.org/x/time"
packages = ["rate"]
pruneopts = "UT"
revision = "555d28b269f0569763d25dbe1a237ae74c6bcc82"
[[projects]]
digest = "1:e0a1881f9e0564bebdeac98c75e59f07acdde6ed3a5396e0e613eaad31194866"
name = "google.golang.org/appengine"
packages = [
"internal",
"internal/base",
"internal/datastore",
"internal/log",
"internal/remote_api",
"internal/urlfetch",
"urlfetch"
]
pruneopts = "UT"
revision = "971852bfffca25b069c31162ae8f247a3dba083b"
version = "v1.6.5"
[[projects]]
digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a"
name = "gopkg.in/inf.v0"
packages = ["."]
pruneopts = "UT"
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
digest = "1:b75b3deb2bce8bc079e16bb2aecfe01eb80098f5650f9e93e5643ca8b7b73737"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "1f64d6156d11335c3f22d9330b0ad14fc1e789ce"
version = "v2.2.7"
[[projects]]
digest = "1:d89afbf3588e87d2c9e6efdd5528d249b32d23a12fbd7ec324f3cb373c6fb76c"
name = "k8s.io/api"
packages = [
"admissionregistration/v1beta1",
"apps/v1",
"apps/v1beta1",
"apps/v1beta2",
"auditregistration/v1alpha1",
"authentication/v1",
"authentication/v1beta1",
"authorization/v1",
"authorization/v1beta1",
"autoscaling/v1",
"autoscaling/v2beta1",
"autoscaling/v2beta2",
"batch/v1",
"batch/v1beta1",
"batch/v2alpha1",
"certificates/v1beta1",
"coordination/v1",
"coordination/v1beta1",
"core/v1",
"events/v1beta1",
"extensions/v1beta1",
"networking/v1",
"networking/v1beta1",
"node/v1alpha1",
"node/v1beta1",
"policy/v1beta1",
"rbac/v1",
"rbac/v1alpha1",
"rbac/v1beta1",
"scheduling/v1",
"scheduling/v1alpha1",
"scheduling/v1beta1",
"settings/v1alpha1",
"storage/v1",
"storage/v1alpha1",
"storage/v1beta1"
]
pruneopts = "UT"
revision = "bd6ac527cfd24ad928f66798231e5bcf7f37321b"
version = "kubernetes-1.15.4"
[[projects]]
digest = "1:8733f07672df473e76966afde4e65ca7abdb0f61108f7be56bfe90909cc7158c"
name = "k8s.io/apimachinery"
packages = [
"pkg/api/errors",
"pkg/api/meta",
"pkg/api/resource",
"pkg/apis/meta/v1",
"pkg/apis/meta/v1/unstructured",
"pkg/conversion",
"pkg/conversion/queryparams",
"pkg/fields",
"pkg/labels",
"pkg/runtime",
"pkg/runtime/schema",
"pkg/runtime/serializer",
"pkg/runtime/serializer/json",
"pkg/runtime/serializer/protobuf",
"pkg/runtime/serializer/recognizer",
"pkg/runtime/serializer/streaming",
"pkg/runtime/serializer/versioning",
"pkg/selection",
"pkg/types",
"pkg/util/clock",
"pkg/util/errors",
"pkg/util/framer",
"pkg/util/intstr",
"pkg/util/json",
"pkg/util/naming",
"pkg/util/net",
"pkg/util/runtime",
"pkg/util/sets",
"pkg/util/validation",
"pkg/util/validation/field",
"pkg/util/yaml",
"pkg/version",
"pkg/watch",
"third_party/forked/golang/reflect"
]
pruneopts = "UT"
revision = "f2f3a405f61d6c2cdc0d00687c1b5d90de91e9f0"
version = "kubernetes-1.15.4"
[[projects]]
digest = "1:3d090f853403f5f24fc5ddd14149409476f4759fb59a5a505b328c9040dc6ad2"
name = "k8s.io/client-go"
packages = [
"discovery",
"kubernetes",
"kubernetes/scheme",
"kubernetes/typed/admissionregistration/v1beta1",
"kubernetes/typed/apps/v1",
"kubernetes/typed/apps/v1beta1",
"kubernetes/typed/apps/v1beta2",
"kubernetes/typed/auditregistration/v1alpha1",
"kubernetes/typed/authentication/v1",
"kubernetes/typed/authentication/v1beta1",
"kubernetes/typed/authorization/v1",
"kubernetes/typed/authorization/v1beta1",
"kubernetes/typed/autoscaling/v1",
"kubernetes/typed/autoscaling/v2beta1",
"kubernetes/typed/autoscaling/v2beta2",
"kubernetes/typed/batch/v1",
"kubernetes/typed/batch/v1beta1",
"kubernetes/typed/batch/v2alpha1",
"kubernetes/typed/certificates/v1beta1",
"kubernetes/typed/coordination/v1",
"kubernetes/typed/coordination/v1beta1",
"kubernetes/typed/core/v1",
"kubernetes/typed/events/v1beta1",
"kubernetes/typed/extensions/v1beta1",
"kubernetes/typed/networking/v1",
"kubernetes/typed/networking/v1beta1",
"kubernetes/typed/node/v1alpha1",
"kubernetes/typed/node/v1beta1",
"kubernetes/typed/policy/v1beta1",
"kubernetes/typed/rbac/v1",
"kubernetes/typed/rbac/v1alpha1",
"kubernetes/typed/rbac/v1beta1",
"kubernetes/typed/scheduling/v1",
"kubernetes/typed/scheduling/v1alpha1",
"kubernetes/typed/scheduling/v1beta1",
"kubernetes/typed/settings/v1alpha1",
"kubernetes/typed/storage/v1",
"kubernetes/typed/storage/v1alpha1",
"kubernetes/typed/storage/v1beta1",
"pkg/apis/clientauthentication",
"pkg/apis/clientauthentication/v1alpha1",
"pkg/apis/clientauthentication/v1beta1",
"pkg/version",
"plugin/pkg/client/auth/exec",
"rest",
"rest/watch",
"tools/auth",
"tools/clientcmd",
"tools/clientcmd/api",
"tools/clientcmd/api/latest",
"tools/clientcmd/api/v1",
"tools/metrics",
"tools/reference",
"transport",
"util/cert",
"util/connrotation",
"util/flowcontrol",
"util/homedir",
"util/keyutil"
]
pruneopts = "UT"
revision = "06eb1244587a17d4994ff9de4ce93756a2f16f0b"
version = "kubernetes-1.15.4"
[[projects]]
digest = "1:93e82f25d75aba18436ad1ac042cb49493f096011f2541075721ed6f9e05c044"
name = "k8s.io/klog"
packages = ["."]
pruneopts = "UT"
revision = "2ca9ad30301bf30a8a6e0fa2110db6b8df699a91"
version = "v1.0.0"
[[projects]]
branch = "master"
digest = "1:a25f899659892020bfbc94c5f9395a708fdbb7f9beeede9c286f4cd377bc5753"
name = "k8s.io/kube-openapi"
packages = ["pkg/common"]
pruneopts = "UT"
revision = "30be4d16710ac61bce31eb28a01054596fe6a9f1"
[[projects]]
branch = "master"
digest = "1:8a5e4720aca8a94c876d960a2b86afcaf98e8ded4b5bd7fe42d920806b292c57"
name = "k8s.io/utils"
packages = ["integer"]
pruneopts = "UT"
revision = "6ca3b61696b65b0e81f1a39b4937fc2d2994ed6a"
[[projects]]
digest = "1:68cd93696d4de06936c94bf0c510802140c2d3c956b557f3d001430d58cf676c"
name = "sigs.k8s.io/controller-runtime"
packages = [
"pkg/runtime/scheme",
"pkg/scheme"
]
pruneopts = "UT"
revision = "d21241119ea4de139f1892ba2f5bc72c96d07f3f"
version = "v0.3.0"
[[projects]]
digest = "1:7719608fe0b52a4ece56c2dde37bedd95b938677d1ab0f84b8a7852e4c59f849"
name = "sigs.k8s.io/yaml"
packages = ["."]
pruneopts = "UT"
revision = "fd68e9863619f6ec2fdd8625fe1f02e7c877e480"
version = "v1.1.0"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/jpillora/go-ogle-analytics",
"github.com/litmuschaos/chaos-operator/pkg/apis/litmuschaos/v1alpha1",
"github.com/litmuschaos/chaos-operator/pkg/client/clientset/versioned",
"github.com/litmuschaos/kube-helper/kubernetes/container",
"github.com/litmuschaos/kube-helper/kubernetes/job",
"github.com/litmuschaos/kube-helper/kubernetes/jobspec",
"github.com/litmuschaos/kube-helper/kubernetes/podtemplatespec",
"github.com/litmuschaos/kube-helper/kubernetes/volume/v1alpha1",
"github.com/sirupsen/logrus",
"k8s.io/api/batch/v1",
"k8s.io/api/core/v1",
"k8s.io/apimachinery/pkg/apis/meta/v1",
"k8s.io/client-go/kubernetes",
"k8s.io/client-go/rest",
"k8s.io/client-go/tools/clientcmd"
]
solver-name = "gps-cdcl"
solver-version = 1

View File

@ -1,58 +0,0 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/Sirupsen/logrus"
version = "1.4.1"
[[override]]
name = "k8s.io/api"
version = "kubernetes-1.15.4"
[[override]]
name = "k8s.io/apimachinery"
version = "kubernetes-1.15.4"
[[override]]
name = "k8s.io/client-go"
version = "kubernetes-1.15.4"
[[override]]
name = "sigs.k8s.io/controller-runtime"
version = "=v0.3.0"
[[override]]
name = "gopkg.in/fsnotify.v1"
source = "https://github.com/fsnotify/fsnotify.git"
[[override]]
name = "github.com/litmuschaos/chaos-operator"
branch = "master"
[prune]
go-tests = true
unused-packages = true

124
Makefile
View File

@ -1,31 +1,28 @@
# Makefile for building Litmus and its tools
# Reference Guide - https://www.gnu.org/software/make/manual/make.html
REGISTRY ?= litmuschaos
IMG_NAME ?= chaos-executor
PACKAGE_VERSION ?= ci
DOCKER_REGISTRY ?= docker.io
DOCKER_REPO ?= litmuschaos
DOCKER_IMAGE ?= chaos-runner
DOCKER_TAG ?= ci
IS_DOCKER_INSTALLED = $(shell which docker >> /dev/null 2>&1; echo $$?)
HOME = $(shell echo $$HOME)
# list only our namespaced directories
PACKAGES = $(shell go list ./... | grep -v '/vendor/')
.PHONY: all
all: godeps format lint build test dockerops
.PHONY: help
help:
@echo ""
@echo "Usage:-"
@echo "\tmake all -- [default] builds the chaos exporter container"
@echo ""
@echo "\tmake deps -- sets up dependencies for image build"
@echo "\tmake build-chaos-runner -- builds multi-arch image"
@echo "\tmake push-chaos-runner -- pushes the multi-arch image"
@echo "\tmake build-amd64 -- builds the amd64 image"
@echo ""
.PHONY: godeps
godeps:
@echo ""
@echo "INFO:\tverifying dependencies for chaos exporter build ..."
@go get -u -v golang.org/x/lint/golint
@go get -u -v golang.org/x/tools/cmd/goimports
@go get -u -v github.com/golang/dep/cmd/dep
.PHONY: all
all: deps unused-package-check build-chaos-runner test
.PHONY: deps
deps: _build_check_docker godeps
_build_check_docker:
@if [ $(IS_DOCKER_INSTALLED) -eq 1 ]; \
@ -35,48 +32,67 @@ _build_check_docker:
&& exit 1; \
fi;
.PHONY: deps
deps: _build_check_docker godeps
.PHONY: format
format:
@echo "------------------"
@echo "--> Running go fmt"
@echo "------------------"
@go fmt $(PACKAGES)
.PHONY: lint
lint:
@echo "------------------"
@echo "--> Running golint"
@echo "------------------"
@golint $(PACKAGES)
@echo "------------------"
@echo "--> Running go vet"
@echo "------------------"
@go vet $(PACKAGES)
.PHONY: build
build:
@echo "------------------"
@echo "--> Building Chaos-executor binary..."
@echo "------------------"
@go build -o build/_output/bin/chaos-executor ./bin
.PHONY: godeps
godeps:
@echo ""
@echo "INFO:\tverifying dependencies for chaos runner build ..."
@go get -u -v golang.org/x/lint/golint
@go get -u -v golang.org/x/tools/cmd/goimports
.PHONY: test
test:
@echo "------------------"
@echo "--> Run Go Test"
@echo "------------------"
@go test ./... -v -count=1
@go test ./... -coverprofile=coverage.txt -covermode=atomic -v -count=1
.PHONY: dockerops
dockerops:
@echo "------------------"
@echo "--> Build Chaos-executor image..."
@echo "------------------"
sudo docker build . -f build/Dockerfile -t $(REGISTRY)/$(IMG_NAME):$(PACKAGE_VERSION)
.PHONY: push
push:
sudo docker push $(REGISTRY)/$(IMG_NAME):$(PACKAGE_VERSION)
.PHONY: build-chaos-runner
build-chaos-runner:
@echo "-------------------------"
@echo "--> Build chaos-runner image"
@echo "-------------------------"
@docker buildx build --file build/Dockerfile --progress plain --no-cache --platform linux/arm64,linux/amd64 --tag $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(DOCKER_IMAGE):$(DOCKER_TAG) .
.PHONY: push-chaos-runner
push-chaos-runner:
@echo "------------------------------"
@echo "--> Pushing image"
@echo "------------------------------"
@docker buildx build --file build/Dockerfile --progress plain --no-cache --push --platform linux/arm64,linux/amd64 --tag $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(DOCKER_IMAGE):$(DOCKER_TAG) .
.PHONY: build-amd64
build-amd64:
@echo "--------------------------------------"
@echo "--> Build chaos-runner image for amd64"
@echo "--------------------------------------"
@docker build -f build/Dockerfile --no-cache -t $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(DOCKER_IMAGE):$(DOCKER_TAG) . --build-arg TARGETPLATFORM="linux/amd64"
.PHONY: push-amd64
push-amd64:
@echo "--------------------------------------"
@echo "--> Push chaos-runner image for amd64"
@echo "--------------------------------------"
@docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO)/$(DOCKER_IMAGE):$(DOCKER_TAG)
.PHONY: unused-package-check
unused-package-check:
@echo "------------------"
@echo "--> Check unused packages for the chaos-runner"
@echo "------------------"
@tidy=$$(go mod tidy); \
if [ -n "$${tidy}" ]; then \
echo "go mod tidy checking failed!"; echo "$${tidy}"; echo; \
fi
gofmt-check:
@echo "------------------"
@echo "--> Check unused packages for the chaos-runner"
@echo "------------------"
@gfmt=$$(gofmt -s -l . | wc -l); \
if [ "$${gfmt}" -ne 0 ]; then \
echo "The following files were found to be not go formatted:"; \
gofmt -s -l .; \
exit 1; \
fi

5
NOTICE.md Normal file
View File

@ -0,0 +1,5 @@
The source code developed for the LitmusChaos Project is licensed under Apache 2.0.
However, the LitmusChaos project contains unmodified subcomponents from other Open Source Projects with separate copyright notices and license terms.
Your use of the source code for these subcomponents is subject to the terms and conditions as defined by those source projects.

View File

@ -1,15 +1,26 @@
# CHAOS RUNNER
[![Slack Channel](https://img.shields.io/badge/Slack-Join-purple)](https://slack.litmuschaos.io)
![GitHub Workflow](https://github.com/litmuschaos/chaos-runner/actions/workflows/push.yml/badge.svg?branch=master)
[![Docker Pulls](https://img.shields.io/docker/pulls/litmuschaos/chaos-runner.svg)](https://hub.docker.com/r/litmuschaos/chaos-runner)
[![GitHub issues](https://img.shields.io/github/issues/litmuschaos/chaos-runner)](https://github.com/litmuschaos/chaos-runner/issues)
[![Twitter Follow](https://img.shields.io/twitter/follow/litmuschaos?style=social)](https://twitter.com/LitmusChaos)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5291/badge)](https://bestpractices.coreinfrastructure.org/projects/5291)
[![Go Report Card](https://goreportcard.com/badge/github.com/litmuschaos/chaos-runner)](https://goreportcard.com/report/github.com/litmuschaos/chaos-runner)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flitmuschaos%2Fchaos-runner.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flitmuschaos%2Fchaos-runner?ref=badge_shield)
[![YouTube Channel](https://img.shields.io/badge/YouTube-Subscribe-red)](https://www.youtube.com/channel/UCa57PMqmz_j0wnteRa9nCaw)
<br><br>
The chaos Runner is an operational bridge between the Chaos-Operator and the LitmusChaos experiment jobs.
- It is launched as a pod in the app namespace & reconciled by the Litmus Chaos Operator
- It is launched as a pod in the chaos namespace(where chaosengine is running) & reconciled by the Litmus Chaos Operator
- Reads the chaos parameters from the experiment CR & overrides with values from the ChaosEngine, constructs the experiment job after
validating dependencies such as configmap/secret volumes & launches it (along with the monitor/chaos-exporter deployment if engine's monitoring policy is true)
- Monitors the experiment job until completion
- Monitors the experiment pod until completion
- Cleans up the experiment job post completion based on the engine's jobCleanUpPolicy (delete or retain)
- Patches the ChaosEngine with the verdict of the experiment
- Patches the ChaosEngine with the verdict of the experiment and creates the events for the different phases inside chaosengine.
This repo consists of the go-version of the currently used ansible-based runner/executor. The motivation includes:
Objective behind chaos-runner creation:
- Support a contextual/audit logging framework in litmus where the sequence of events from creation of the engine to its eventual removal
(with the experiment execution summary in b/w) is traceable
@ -22,39 +33,22 @@ This repo consists of the go-version of the currently used ansible-based runner/
- Support dependency management of experiments in case of batch runs with possible parallel / asynchronous execution & thereby patching of the ChaosEngine.
- Increased execution performance (today, the time taken to construct and launch the experiment job is directly proportional to the number of environment
variables passed to it. The Kafka chaos experiments are seen to take, on an average, ~60-70s before launching the job itself.)
- Allow multiple combinations of random execution in case of future support for Chaos Scheduling, where it may be necessary for the job execution to be
randomized based on different conditions (iteration count, minimum intervals etc.,)
## How to use the Go Chaos Runner
- Provide the appropriate values in the ChaosEngine spec as shown below:
```
apiVersion: litmuschaos.io/v1alpha1
kind: ChaosEngine
metadata:
name: engine
namespace: litmus
spec:
appinfo:
appkind: deployment
applabel: app=nginx
appns: litmus
chaosServiceAccount: litmus
components:
runner:
type: "go"
image: "litmuschaos/chaos-executor:ci"
experiments:
- name: pod-delete
spec:
components: null
monitoring: false
```
## Further Improvements
- The Go Chaos Runner is in alpha stage with further improvements coming soon!!
- The Go Chaos Runner is in beta stage with further improvements coming soon!!
## How to get started?
Refer the [LitmusChaos documentation](https://docs.litmuschaos.io) and [Experiment Documentation](https://litmuschaos.github.io/litmus/experiments/concepts/chaos-resources/contents/)
## How do I contribute?
You can contribute by raising issues, improving the documentation, contributing to the core framework and tooling, etc.
Head over to the [Contribution guide](CONTRIBUTING.md)
## License
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flitmuschaos%2Fchaos-runner.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Flitmuschaos%2Fchaos-runner?ref=badge_large)

View File

@ -1,120 +0,0 @@
package main
import (
"flag"
"time"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/litmuschaos/chaos-executor/pkg/utils"
"github.com/litmuschaos/chaos-executor/pkg/utils/analytics"
)
// getKubeConfig setup the config for access cluster resource
func getKubeConfig() (*rest.Config, error) {
kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
flag.Parse()
// Use in-cluster config if kubeconfig path is specified
if *kubeconfig == "" {
config, err := rest.InClusterConfig()
if err != nil {
return config, err
}
}
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
return config, err
}
return config, err
}
func main() {
engineDetails := utils.EngineDetails{}
clients := utils.ClientSets{}
// Getting the kubeconfig
config, err := getKubeConfig()
if err != nil {
log.Fatalf("Error in fetching the config, error : %v", err)
}
// clientSet creation for further use.
if err = clients.GenerateClientSets(config); err != nil {
log.Fatalf("Unable to create ClientSets")
}
// Fetching all the ENV's needed
utils.GetOsEnv(&engineDetails)
log.Infoln("Experiments List: ", engineDetails.Experiments, " ", "Engine Name: ", engineDetails.Name, " ", "appLabels : ", engineDetails.AppLabel, " ", "appNamespace: ", engineDetails.AppNamespace, " ", "appKind: ", engineDetails.AppKind, " ", "Service Account Name: ", engineDetails.SvcAccount)
// Steps for each Experiment
for i := range engineDetails.Experiments {
// Sending event to GA instance
if engineDetails.ClientUUID != "" {
analytics.TriggerAnalytics(engineDetails.Experiments[i], engineDetails.ClientUUID)
}
experiment := utils.NewExperimentDetails()
experiment.SetValueFromChaosEngine(engineDetails, i)
experiment.SetValueFromChaosExperiment(clients)
experiment.SetENV(engineDetails, clients)
experimentStatus := utils.ExperimentStatus{}
experimentStatus.IntialExperimentStatus(experiment)
experimentStatus.InitialPatchEngine(engineDetails, clients)
log.Infof("Preparing to run Chaos Experiment: %v", experiment.Name)
// isFound will return the status of experiment in that namespace
// 1 -> found, 0 -> not-found
isFound, err := experiment.CheckExistence(clients)
log.Infoln("Experiment Found Status : ", isFound)
// If not found in AppNamespace skip the further steps
if !isFound {
engineDetails.ExperimentNotFoundPatchEngine(experiment, clients)
log.Infof("Unable to list Chaos Experiment: %v, in Namespace: %v, skipping execution, with error: %v", experiment.Name, experiment.Namespace, err)
break
}
// Patch ConfigMaps to ChaosExperiment Job
if err := experiment.PatchConfigMaps(clients); err != nil {
log.Infof("Unable to patch ConfigMaps, due to: %v", err)
break
}
// Patch Secrets to ChaosExperiment Job
if err = experiment.PatchSecrets(clients); err != nil {
log.Infof("Unable to patch Secrets, due to: %v", err)
break
}
experiment.VolumeOpts.VolumeOperations(experiment.ConfigMaps, experiment.Secrets)
// Creation of PodTemplateSpec, and Final Job
if err = utils.BuildingAndLaunchJob(experiment, clients); err != nil {
log.Infof("Unable to construct chaos experiment job due to: %v", err)
break
}
time.Sleep(5 * time.Second)
// Watching the Job till Completion
if err = engineDetails.WatchJobForCompletion(experiment, clients); err != nil {
log.Infof("Unable to Watch the Job, error: %v", err)
break
}
// Will Update the chaosEngine Status
if err = engineDetails.UpdateEngineWithResult(experiment, clients); err != nil {
log.Infof("Unable to Update ChaosEngine Status due to: %v", err)
}
// Delete / retain the Job, using the jobCleanUpPolicy
if err = engineDetails.DeleteJobAccordingToJobCleanUpPolicy(experiment, clients); err != nil {
log.Infof("Unable to Delete chaosExperiment Job due to: %v", err)
}
}
}

152
bin/runner.go Normal file
View File

@ -0,0 +1,152 @@
package main
import (
"context"
"errors"
"os"
"github.com/litmuschaos/chaos-runner/pkg/log"
"github.com/litmuschaos/chaos-runner/pkg/telemetry"
"github.com/litmuschaos/chaos-runner/pkg/utils"
"github.com/litmuschaos/chaos-runner/pkg/utils/analytics"
"github.com/sirupsen/logrus"
"go.opentelemetry.io/otel"
)
func init() {
// Log as JSON instead of the default ASCII formatter.
logrus.SetFormatter(&logrus.TextFormatter{
FullTimestamp: true,
DisableSorting: true,
DisableLevelTruncation: true,
})
}
func main() {
ctx := context.Background()
// Set up Observability.
if otelExporterEndpoint := os.Getenv(telemetry.OTELExporterOTLPEndpoint); otelExporterEndpoint != "" {
shutdown, err := telemetry.InitOTelSDK(ctx, otelExporterEndpoint)
if err != nil {
log.Errorf("Failed to initialize OTel SDK: %v", err)
return
}
defer func() {
err = errors.Join(err, shutdown(ctx))
}()
ctx = telemetry.GetTraceParentContext()
}
engineDetails := utils.EngineDetails{}
clients := utils.ClientSets{}
ctx, span := otel.Tracer(telemetry.TracerName).Start(ctx, "ExecuteChaosRunner")
defer span.End()
// Getting kubeConfig and Generate ClientSets
if err := clients.GenerateClientSetFromKubeConfig(); err != nil {
log.Errorf("unable to create ClientSets, error: %v", err)
return
}
// Fetching all the ENVs passed from the chaos-operator
// create and initialize the experimentList
if err := engineDetails.SetEngineDetails().SetEngineUID(clients); err != nil {
log.Errorf("unable to get ChaosEngineUID, error: %v", err)
return
}
experimentList := engineDetails.CreateExperimentList()
log.InfoWithValues("Experiments details are as follows", logrus.Fields{
"Experiments List": engineDetails.Experiments,
"Engine Name": engineDetails.Name,
"Targets": engineDetails.Targets,
"Service Account Name": engineDetails.SvcAccount,
"Engine Namespace": engineDetails.EngineNamespace,
})
if err := utils.InitialPatchEngine(engineDetails, clients, experimentList); err != nil {
log.Errorf("unable to patch Initial ExperimentStatus in ChaosEngine, error: %v", err)
return
}
// Steps for each Experiment
for _, experiment := range experimentList {
// Sending event to GA instance
if engineDetails.ClientUUID != "" {
analytics.TriggerAnalytics(experiment.Name, engineDetails.ClientUUID)
}
// check the existence of chaosexperiment inside the cluster
if err := experiment.HandleChaosExperimentExistence(engineDetails, clients); err != nil {
log.Errorf("unable to get ChaosExperiment name: %v, in namespace: %v, error: %v", experiment.Name, experiment.Namespace, err)
experiment.ExperimentSkipped(utils.ExperimentNotFoundErrorReason, engineDetails, clients)
continue
}
// derive the required field from the experiment & engine and set into experimentDetails struct
if err := experiment.SetValueFromChaosResources(&engineDetails, clients); err != nil {
log.Errorf("unable to set values from Chaos Resources, error: %v", err)
experiment.ExperimentSkipped(utils.ExperimentNotFoundErrorReason, engineDetails, clients)
engineDetails.ExperimentSkippedPatchEngine(&experiment, clients)
continue
}
// derive the envs from the chaos experiment and override their values from chaosengine if any
if err := experiment.SetENV(ctx, engineDetails, clients); err != nil {
log.Errorf("unable to patch ENV, error: %v", err)
experiment.ExperimentSkipped(utils.ExperimentEnvParseErrorReason, engineDetails, clients)
engineDetails.ExperimentSkippedPatchEngine(&experiment, clients)
continue
}
// derive the sidecar details from chaosengine
if err := experiment.SetSideCarDetails(engineDetails.Name, clients); err != nil {
log.Errorf("unable to get sidecar details, error: %v", err)
experiment.ExperimentSkipped(utils.ExperimentSideCarPatchErrorReason, engineDetails, clients)
engineDetails.ExperimentSkippedPatchEngine(&experiment, clients)
continue
}
log.Infof("Preparing to run Chaos Experiment: %v", experiment.Name)
if err := experiment.PatchResources(engineDetails, clients); err != nil {
log.Errorf("unable to patch Chaos Resources required for Chaos Experiment: %v, error: %v", experiment.Name, err)
experiment.ExperimentSkipped(utils.ExperimentDependencyCheckReason, engineDetails, clients)
engineDetails.ExperimentSkippedPatchEngine(&experiment, clients)
continue
}
// generating experiment dependency check event inside chaosengine
experiment.ExperimentDependencyCheck(engineDetails, clients)
// Creation of PodTemplateSpec, and Final Job
if err := utils.BuildingAndLaunchJob(ctx, &experiment, clients); err != nil {
log.Errorf("unable to construct chaos experiment job, error: %v", err)
experiment.ExperimentSkipped(utils.ExperimentDependencyCheckReason, engineDetails, clients)
engineDetails.ExperimentSkippedPatchEngine(&experiment, clients)
continue
}
experiment.ExperimentJobCreate(engineDetails, clients)
log.Infof("Started Chaos Experiment Name: %v, with Job Name: %v", experiment.Name, experiment.JobName)
// Watching the chaos container till Completion
if err := engineDetails.WatchChaosContainerForCompletion(&experiment, clients); err != nil {
log.Errorf("unable to Watch the chaos container, error: %v", err)
experiment.ExperimentSkipped(utils.ExperimentChaosContainerWatchErrorReason, engineDetails, clients)
engineDetails.ExperimentSkippedPatchEngine(&experiment, clients)
continue
}
log.Infof("Chaos Pod Completed, Experiment Name: %v, with Job Name: %v", experiment.Name, experiment.JobName)
// Will Update the chaosEngine Status
if err := engineDetails.UpdateEngineWithResult(&experiment, clients); err != nil {
log.Errorf("unable to Update ChaosEngine Status, error: %v", err)
}
log.Infof("Chaos Engine has been updated with result, Experiment Name: %v", experiment.Name)
// Delete/Retain the Job, based on the jobCleanUpPolicy
jobCleanUpPolicy, err := engineDetails.DeleteJobAccordingToJobCleanUpPolicy(&experiment, clients)
if err != nil {
log.Errorf("unable to Delete ChaosExperiment Job, error: %v", err)
}
experiment.ExperimentJobCleanUp(string(jobCleanUpPolicy), engineDetails, clients)
}
}

View File

@ -1,9 +1,31 @@
FROM registry.access.redhat.com/ubi7-dev-preview/ubi-minimal:7.6
# Multi-stage docker build
# Build stage
FROM golang:alpine AS builder
LABEL maintainer="LitmusChaos"
ENV EXECUTOR=/usr/local/bin/chaos-executor
ARG TARGETPLATFORM
COPY build/_output/bin/chaos-executor ${EXECUTOR}
ADD . /chaos-runner
WORKDIR /chaos-runner
ENTRYPOINT ["/usr/local/bin/chaos-executor"]
RUN export GOOS=$(echo ${TARGETPLATFORM} | cut -d / -f1) && \
export GOARCH=$(echo ${TARGETPLATFORM} | cut -d / -f2)
RUN go env
RUN CGO_ENABLED=0 go build -buildvcs=false -o /output/chaos-runner -v ./bin
# Packaging stage
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.4
LABEL maintainer="LitmusChaos"
ENV RUNNER=/usr/local/bin/chaos-runner
COPY --from=builder /output/chaos-runner ${RUNNER}
RUN chown 65534:0 ${RUNNER} && chmod 755 ${RUNNER}
USER 65534
ENTRYPOINT ["/usr/local/bin/chaos-runner"]

View File

@ -5,7 +5,8 @@ set -e
function push_ci_image(){
echo "Pushing ${REPONAME}/${IMGNAME}:${IMGTAG} ..."
docker push ${REPONAME}/${IMGNAME}:${IMGTAG}
docker buildx build --file build/Dockerfile --push --platform linux/arm64,linux/amd64 --tag ${REPONAME}/${IMGNAME}:${IMGTAG} .
}
@ -18,13 +19,12 @@ function push_release_image(){
# When github is tagged with a release, then CircleCI will
# set the release tag in env CIRCLE_TAG
echo "Pushing ${REPONAME}/${IMGNAME}:${CIRCLE_TAG} ..."
docker tag ${IMAGEID} ${REPONAME}/${IMGNAME}:${CIRCLE_TAG}
docker push ${REPONAME}/${IMGNAME}:${CIRCLE_TAG}
docker buildx build --file build/Dockerfile --progress plane --push --platform linux/arm64,linux/amd64 --tag ${REPONAME}/${IMGNAME}:${CIRCLE_TAG} .
echo "Pushing ${REPONAME}/${IMGNAME}:ci ..."
docker buildx build --file build/Dockerfile --progress plane --push --platform linux/arm64,linux/amd64 --tag ${REPONAME}/${IMGNAME}:ci .
echo "Pushing ${REPONAME}/${IMGNAME}:latest ..."
docker tag ${IMAGEID} ${REPONAME}/${IMGNAME}:latest
docker push ${REPONAME}/${IMGNAME}:latest
docker buildx build --file build/Dockerfile --progress plane --push --platform linux/arm64,linux/amd64 --tag ${REPONAME}/${IMGNAME}:latest .
fi;
}

95
go.mod Normal file
View File

@ -0,0 +1,95 @@
module github.com/litmuschaos/chaos-runner
go 1.22
require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24
github.com/jpillora/go-ogle-analytics v0.0.0-20161213085824-14b04e0594ef
github.com/litmuschaos/chaos-operator v0.0.0-20240601063404-e96a7ee7f1f7
github.com/litmuschaos/elves v0.0.0-20230607095010-c7119636b529
github.com/litmuschaos/litmus-go v0.0.0-20230605073551-d73728198577
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.15.0
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.9.0
go.opentelemetry.io/otel v1.27.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0
go.opentelemetry.io/otel/sdk v1.27.0
k8s.io/api v0.26.0
k8s.io/apimachinery v0.26.0
k8s.io/client-go v12.0.0+incompatible
)
require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/evanphx/json-patch v4.11.0+incompatible // indirect
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/googleapis/gnostic v0.5.5 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/imdario/mergo v0.3.12 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/nxadm/tail v1.4.8 // indirect
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
go.opentelemetry.io/otel/metric v1.27.0 // indirect
go.opentelemetry.io/otel/trace v1.27.0 // indirect
go.opentelemetry.io/proto/otlp v1.2.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/term v0.15.0 // indirect
golang.org/x/text v0.15.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 // indirect
google.golang.org/grpc v1.64.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/klog/v2 v2.80.1 // indirect
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
sigs.k8s.io/controller-runtime v0.10.0 // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
sigs.k8s.io/yaml v1.2.0 // indirect
)
// Pinned to kubernetes-1.21.2
replace (
k8s.io/api => k8s.io/api v0.21.2
k8s.io/apimachinery => k8s.io/apimachinery v0.21.2
k8s.io/cli-runtime => k8s.io/cli-runtime v0.21.2
k8s.io/client-go => k8s.io/client-go v0.21.2
k8s.io/cloud-provider => k8s.io/cloud-provider v0.21.2
k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.21.2
k8s.io/component-base => k8s.io/component-base v0.21.2
k8s.io/cri-api => k8s.io/cri-api v0.21.2
k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.21.2
k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.21.2
k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.21.2
k8s.io/kube-proxy => k8s.io/kube-proxy v0.21.2
k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.21.2
k8s.io/kubectl => k8s.io/kubectl v0.21.2
k8s.io/kubelet => k8s.io/kubelet v0.21.2
k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.21.2
k8s.io/metrics => k8s.io/metrics v0.21.2
k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.21.2
)
replace github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 // Required by Helm
replace golang.org/x/net => golang.org/x/net v0.17.0

810
go.sum Normal file
View File

@ -0,0 +1,810 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest/autorest v0.11.12/go.mod h1:eipySxLmqSyC5s5k1CLupqet0PSENBEDP93LQ9a8QYw=
github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A=
github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74=
github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k=
github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo=
github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA=
github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
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 v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g=
github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
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=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg=
github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU=
github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw=
github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/jpillora/go-ogle-analytics v0.0.0-20161213085824-14b04e0594ef h1:jLpa0vamfyIGeIJ/CfUJEWoKriw4ODeOgF1XxDvgMZ4=
github.com/jpillora/go-ogle-analytics v0.0.0-20161213085824-14b04e0594ef/go.mod h1:PlwhC7q1VSK73InDzdDatVetQrTsQHIbOvcJAZzitY0=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/litmuschaos/chaos-operator v0.0.0-20240601063404-e96a7ee7f1f7 h1:W4+NpHoBJnbPL4x9WTDJWIg219ElfQjjjs4V7VfDBKM=
github.com/litmuschaos/chaos-operator v0.0.0-20240601063404-e96a7ee7f1f7/go.mod h1:7aAslOjCI8sens0OA3gtQDDa7PO0af3n9U15PXGhpXI=
github.com/litmuschaos/elves v0.0.0-20230607095010-c7119636b529 h1:Id7WZy5wXg7RYHbunkzkXFRolrfAerZzZkpjZ6MEZ/4=
github.com/litmuschaos/elves v0.0.0-20230607095010-c7119636b529/go.mod h1:N4ljNnCRBeKgKw1zThi6wbQGQ2b6tlXb4eCVQRLJIvE=
github.com/litmuschaos/litmus-go v0.0.0-20230605073551-d73728198577 h1:8+3Ucb0VEY9F441vlweT1UEb1/4wbYxMXxGLad0It9M=
github.com/litmuschaos/litmus-go v0.0.0-20230605073551-d73728198577/go.mod h1:/88zb8T8g75lMSqt6LjXdCEYCxPAWVCbViymkPcWbjg=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU=
github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177 h1:nRlQD0u1871kaznCnn1EvYiMbum36v7hw1DLPEjds4o=
github.com/palantir/stacktrace v0.0.0-20161112013806-78658fd2d177/go.mod h1:ao5zGxj8Z4x60IOVYZUbDSmt3R8Ddo080vEgPosHpak=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4=
go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo=
go.opentelemetry.io/otel v1.27.0 h1:9BZoF3yMK/O1AafMiQTVu0YDj5Ea4hPhxCs7sGva+cg=
go.opentelemetry.io/otel v1.27.0/go.mod h1:DMpAK8fzYRzs+bi3rS5REupisuqTheUlSZJ1WnZaPAQ=
go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0 h1:R9DE4kQ4k+YtfLI2ULwX82VtNQ2J8yZmA7ZIF/D+7Mc=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.27.0/go.mod h1:OQFyQVrDlbe+R7xrEyDr/2Wr67Ol0hRUgsfA+V5A95s=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU=
go.opentelemetry.io/otel/metric v1.27.0 h1:hvj3vdEKyeCi4YaYfNjv2NUje8FqKqUY8IlF0FxV/ik=
go.opentelemetry.io/otel/metric v1.27.0/go.mod h1:mVFgmRlhljgBiuk/MP/oKylr4hs85GZAylncepAX/ak=
go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw=
go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc=
go.opentelemetry.io/otel/sdk v1.27.0 h1:mlk+/Y1gLPLn84U4tI8d3GNJmGT/eXe3ZuOXN9kTWmI=
go.opentelemetry.io/otel/sdk v1.27.0/go.mod h1:Ha9vbLwJE6W86YstIywK2xFfPjbWlCuwPtMkKdz/Y4A=
go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE=
go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE=
go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw=
go.opentelemetry.io/otel/trace v1.27.0 h1:IqYb813p7cmbHk0a5y6pD5JPakbVfftRXABGt5/Rscw=
go.opentelemetry.io/otel/trace v1.27.0/go.mod h1:6RiD1hkAprV4/q+yd2ln1HG9GoPx39SuvvstaLBl+l4=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94=
go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
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-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3 h1:QW9+G6Fir4VcRXVH8x3LilNAb6cxBGLa6+GM4hRwexE=
google.golang.org/genproto/googleapis/api v0.0.0-20240610135401-a8a62080eff3/go.mod h1:kdrSS/OiLkPrNUpzD4aHgCq2rVuC/YRxok32HXZ4vRE=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3 h1:9Xyg6I9IWQZhRVfCWjKK+l6kI0jHcPesVlMnT//aHNo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240610135401-a8a62080eff3/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.64.0 h1:KH3VH9y/MgNQg1dE7b3XfVK0GsPSIzJwdF617gUSbvY=
google.golang.org/grpc v1.64.0/go.mod h1:oxjF8E3FBnjp+/gVFYdWacaLDx9na1aqy9oovLpxQYg=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.21.2 h1:vz7DqmRsXTCSa6pNxXwQ1IYeAZgdIsua+DZU+o+SX3Y=
k8s.io/api v0.21.2/go.mod h1:Lv6UGJZ1rlMI1qusN8ruAp9PUBFyBwpEHAdG24vIsiU=
k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c=
k8s.io/apimachinery v0.21.2 h1:vezUc/BHqWlQDnZ+XkrpXSmnANSLbpnlpwo0Lhk0gpc=
k8s.io/apimachinery v0.21.2/go.mod h1:CdTY8fU/BlvAbJ2z/8kBwimGki5Zp8/fbVuLY8gJumM=
k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400=
k8s.io/client-go v0.21.2 h1:Q1j4L/iMN4pTw6Y4DWppBoUxgKO8LbffEMVEV00MUp0=
k8s.io/client-go v0.21.2/go.mod h1:HdJ9iknWpbl3vMGtib6T2PyI/VYxiZfq936WNVHBRrA=
k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o=
k8s.io/component-base v0.21.2/go.mod h1:9lvmIThzdlrJj5Hp8Z/TOgIkdfsNARQ1pT+3PByuiuc=
k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E=
k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.8.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM=
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw=
k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA=
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg=
sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U=
sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg=
sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.0/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw=
sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

61
pkg/log/log.go Normal file
View File

@ -0,0 +1,61 @@
package log
import (
logrus "github.com/sirupsen/logrus"
)
// Fatalf Logs first and then calls `logger.Exit(1)`
// logging level is set to Panic.
func Fatalf(msg string, err error) {
logrus.WithFields(logrus.Fields{}).Fatalf(msg, err)
}
// Fatal Logs first and then calls `logger.Exit(1)`
// logging level is set to Panic.
func Fatal(msg string) {
logrus.WithFields(logrus.Fields{}).Fatal(msg)
}
// Infof log the General operational entries about what's going on inside the application
func Infof(msg string, val ...interface{}) {
logrus.WithFields(logrus.Fields{}).Infof(msg, val...)
}
// Info log the General operational entries about what's going on inside the application
func Info(msg string) {
logrus.WithFields(logrus.Fields{}).Infof(msg)
}
// InfoWithValues log the General operational entries about what's going on inside the application
// It also print the extra key values pairs
func InfoWithValues(msg string, val map[string]interface{}) {
logrus.WithFields(val).Info(msg)
}
// ErrorWithValues log the Error entries happening inside the code
// It also print the extra key values pairs
func ErrorWithValues(msg string, val map[string]interface{}) {
logrus.WithFields(val).Error(msg)
}
// Warn log the Non-critical entries that deserve eyes.
func Warn(msg string) {
logrus.WithFields(logrus.Fields{}).Warn(msg)
}
// Warnf log the Non-critical entries that deserve eyes.
func Warnf(msg string, val ...interface{}) {
logrus.WithFields(logrus.Fields{}).Warnf(msg, val...)
}
// Errorf used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service.
func Errorf(msg string, err ...interface{}) {
logrus.WithFields(logrus.Fields{}).Errorf(msg, err...)
}
// Error used for errors that should definitely be noted.
// Commonly used for hooks to send errors to an error tracking service
func Error(msg string) {
logrus.WithFields(logrus.Fields{}).Error(msg)
}

89
pkg/telemetry/otel.go Normal file
View File

@ -0,0 +1,89 @@
package telemetry
import (
"context"
"errors"
log "github.com/sirupsen/logrus"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/resource"
"go.opentelemetry.io/otel/sdk/trace"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.25.0"
)
// TODO: change endpoint to be configurable
const OTELExporterOTLPEndpoint = "OTEL_EXPORTER_OTLP_ENDPOINT"
const OTELServiceName = "chaos_runner"
func InitOTelSDK(ctx context.Context, endpoint string) (shutdown func(context.Context) error, err error) {
var shutdownFuncs []func(context.Context) error
shutdown = func(ctx context.Context) error {
var err error
for _, fn := range shutdownFuncs {
err = errors.Join(err, fn(ctx))
}
shutdownFuncs = nil
return err
}
handleErr := func(inErr error) {
err = errors.Join(inErr, shutdown(ctx))
}
tracerProvider, err := newTracerProvider(ctx, endpoint)
if err != nil {
handleErr(err)
return
}
prop := newPropagator()
otel.SetTextMapPropagator(prop)
shutdownFuncs = append(shutdownFuncs, tracerProvider.Shutdown)
otel.SetTracerProvider(tracerProvider)
log.Info("OTel SDK initialized")
// TODO: need to add metrics & logging provider
return
}
func newPropagator() propagation.TextMapPropagator {
return propagation.NewCompositeTextMapPropagator(
propagation.TraceContext{},
propagation.Baggage{},
)
}
func newTracerProvider(ctx context.Context, endpoint string) (*trace.TracerProvider, error) {
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceNameKey.String(OTELServiceName),
),
)
traceExporter, err := otlptrace.New(
ctx,
otlptracegrpc.NewClient(
// TODO: add secure option
otlptracegrpc.WithInsecure(),
otlptracegrpc.WithEndpoint(endpoint),
),
)
if err != nil {
return nil, err
}
batchSpanProcessor := sdktrace.NewBatchSpanProcessor(traceExporter)
tracerProvider := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithResource(res),
sdktrace.WithSpanProcessor(batchSpanProcessor),
)
return tracerProvider, nil
}

52
pkg/telemetry/tracing.go Normal file
View File

@ -0,0 +1,52 @@
package telemetry
import (
"context"
"encoding/json"
"os"
"github.com/litmuschaos/chaos-runner/pkg/log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/propagation"
)
const (
TracerName = "litmuschaos.io/chaos-runner"
TraceParent = "TRACE_PARENT"
)
func GetTraceParentContext() context.Context {
traceParent := os.Getenv(TraceParent)
pro := otel.GetTextMapPropagator()
carrier := make(map[string]string)
if err := json.Unmarshal([]byte(traceParent), &carrier); err != nil {
log.Fatal(err.Error())
}
return pro.Extract(context.Background(), propagation.MapCarrier(carrier))
}
// GetMarshalledSpanFromContext Extract spanContext from the context and return it as json encoded string
func GetMarshalledSpanFromContext(ctx context.Context) string {
carrier := make(map[string]string)
pro := otel.GetTextMapPropagator()
pro.Inject(ctx, propagation.MapCarrier(carrier))
if len(carrier) == 0 {
log.Error("spanContext not present in the context, unable to marshall")
return ""
}
marshalled, err := json.Marshal(carrier)
if err != nil {
log.Error(err.Error())
return ""
}
if len(marshalled) >= 1024 {
log.Error("marshalled span context is too large, unable to marshall")
return ""
}
return string(marshalled)
}

View File

@ -2,7 +2,7 @@ package analytics
import (
ga "github.com/jpillora/go-ogle-analytics"
log "github.com/sirupsen/logrus"
"github.com/litmuschaos/chaos-runner/pkg/log"
)
const (
@ -20,15 +20,15 @@ const (
action string = "Execution"
)
// TriggerAnalytics is reponsible for sending out events
// TriggerAnalytics is responsible for sending out events
func TriggerAnalytics(experimentName string, uuid string) {
client, err := ga.NewClient(clientID)
if err != nil {
log.Error(err, "GA Client ID Error")
log.Errorf("unable to create GA client, error: %v", err)
}
client.ClientID(uuid)
err = client.Send(ga.NewEvent(category, action).Label(experimentName))
if err != nil {
log.Infoln("Unable to send GA event", err)
log.Errorf("unable to send GA event, error: %v", err)
}
}

View File

@ -1,15 +1,20 @@
package utils
import (
log "github.com/sirupsen/logrus"
"context"
"reflect"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/telemetry"
"github.com/litmuschaos/elves/kubernetes/container"
"github.com/litmuschaos/elves/kubernetes/job"
"github.com/litmuschaos/elves/kubernetes/jobspec"
"github.com/litmuschaos/elves/kubernetes/podtemplatespec"
"github.com/pkg/errors"
"go.opentelemetry.io/otel"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
"github.com/litmuschaos/kube-helper/kubernetes/container"
"github.com/litmuschaos/kube-helper/kubernetes/job"
jobspec "github.com/litmuschaos/kube-helper/kubernetes/jobspec"
"github.com/litmuschaos/kube-helper/kubernetes/podtemplatespec"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// PodTemplateSpec is struct for creating the *core1.PodTemplateSpec
@ -17,120 +22,236 @@ type PodTemplateSpec struct {
Object *corev1.PodTemplateSpec
}
// Builder struct for getting the error as well with the template
type Builder struct {
podtemplatespec *PodTemplateSpec
errs []error
}
// BuildContainerSpec builds a Container with following properties
func BuildContainerSpec(experiment *ExperimentDetails, envVar []corev1.EnvVar) *container.Builder {
func buildContainerSpec(experiment *ExperimentDetails, envVars []corev1.EnvVar) (*container.Builder, error) {
containerSpec := container.NewBuilder().
WithName(experiment.JobName).
WithImage(experiment.ExpImage).
WithCommandNew([]string{"/bin/bash"}).
WithCommandNew(experiment.ExpCommand).
WithArgumentsNew(experiment.ExpArgs).
WithImagePullPolicy("Always").
//WithVolumeMountsNew(volumeMounts).
WithEnvsNew(envVar)
WithImagePullPolicy(experiment.ExpImagePullPolicy).
WithEnvsNew(envVars)
if !reflect.DeepEqual(experiment.SecurityContext.ContainerSecurityContext, corev1.SecurityContext{}) {
containerSpec.WithSecurityContext(experiment.SecurityContext.ContainerSecurityContext)
}
if !reflect.DeepEqual(experiment.ResourceRequirements, corev1.ResourceRequirements{}) {
containerSpec.WithResourceRequirements(experiment.ResourceRequirements)
}
if experiment.VolumeOpts.VolumeMounts != nil {
log.Infof("Building ChaosExperiment Job with VolumeMounts from ConfigMaps, and Secrets provided.")
containerSpec.WithVolumeMountsNew(experiment.VolumeOpts.VolumeMounts)
}
_, err := containerSpec.Build()
if err != nil {
log.Info(err)
return nil, err
}
return containerSpec
return containerSpec, err
}
func getEnvFromMap(env map[string]string) []corev1.EnvVar {
var envVar []corev1.EnvVar
for k, v := range env {
var perEnv corev1.EnvVar
perEnv.Name = k
perEnv.Value = v
envVar = append(envVar, perEnv)
// buildSideCarSpec builds a Container with following properties
func buildSideCarSpec(experiment *ExperimentDetails) ([]*container.Builder, error) {
var sidecarContainers []*container.Builder
for _, sidecar := range experiment.SideCars {
var volumeOpts VolumeOpts
if len(sidecar.Secrets) != 0 {
volumeOpts.NewVolumeMounts().BuildVolumeMountsForSecrets(sidecar.Secrets)
}
containerSpec := container.NewBuilder().
WithName(experiment.JobName + "-sidecar-" + RandomString(6)).
WithImage(sidecar.Image).
WithImagePullPolicy(sidecar.ImagePullPolicy).
WithEnvsNew(sidecar.ENV)
if !reflect.DeepEqual(experiment.ResourceRequirements, corev1.ResourceRequirements{}) {
containerSpec.WithResourceRequirements(experiment.ResourceRequirements)
}
if volumeOpts.VolumeMounts != nil {
containerSpec.WithVolumeMountsNew(volumeOpts.VolumeMounts)
}
if len(sidecar.EnvFrom) != 0 {
containerSpec.WithEnvsFrom(sidecar.EnvFrom)
}
if _, err := containerSpec.Build(); err != nil {
return nil, err
}
sidecarContainers = append(sidecarContainers, containerSpec)
}
return envVar
return sidecarContainers, err
}
func getEnvFromMap(m map[string]corev1.EnvVar) []corev1.EnvVar {
var envVars []corev1.EnvVar
for _, v := range m {
envVars = append(envVars, v)
}
// Add env for getting pod name using downward API
envVars = append(envVars, corev1.EnvVar{
Name: "POD_NAME",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
APIVersion: "v1",
FieldPath: "metadata.name",
},
},
})
return envVars
}
// BuildingAndLaunchJob builds Job, and then launch it.
func BuildingAndLaunchJob(experiment *ExperimentDetails, clients ClientSets) error {
envVar := getEnvFromMap(experiment.Env)
func BuildingAndLaunchJob(ctx context.Context, experiment *ExperimentDetails, clients ClientSets) error {
ctx, span := otel.Tracer(telemetry.TracerName).Start(ctx, "BuildingAndLaunchJob")
defer span.End()
experiment.VolumeOpts.VolumeOperations(experiment)
envVars := getEnvFromMap(experiment.envMap)
//Build Container to add in the Pod
containerForPod := BuildContainerSpec(experiment, envVar)
// Will build a PodSpecTemplate
pod := BuildPodTemplateSpec(experiment, containerForPod)
// Build JobSpec Template
jobspec := BuildJobSpec(pod)
job, err := experiment.BuildJob(pod, jobspec)
containerForPod, err := buildContainerSpec(experiment, envVars)
if err != nil {
log.Infof("Unable to build ChaosExperiment Job, due to: %v", err)
return err
return errors.Errorf("unable to build Container for Chaos Experiment, error: %v", err)
}
containers := []*container.Builder{containerForPod}
if len(experiment.SideCars) != 0 {
sidecars, err := buildSideCarSpec(experiment)
if err != nil {
return errors.Errorf("unable to build sidecar Container for Chaos Experiment, error: %v", err)
}
containers = append(containers, sidecars...)
}
// Will build a PodSpecTemplate
pod, err := buildPodTemplateSpec(experiment, containers...)
if err != nil {
return errors.Errorf("unable to build PodTemplateSpec for Chaos Experiment, error: %v", err)
}
// Build JobSpec Template
jobspec, err := buildJobSpec(pod)
if err != nil {
return errors.Errorf("unable to build JobSpec for Chaos Experiment, error: %v", err)
}
//Build Job
job, err := experiment.buildJob(jobspec)
if err != nil {
return errors.Errorf("unable to Build ChaosExperiment Job, error: %v", err)
}
// Creating the Job
if err = experiment.launchJob(job, clients); err != nil {
return err
return errors.Errorf("unable to launch ChaosExperiment Job, error: %v", err)
}
return nil
}
// launchJob spawn a kubernetes Job using the job Object recieved.
func (experiment *ExperimentDetails) launchJob(job *batchv1.Job, clients ClientSets) error {
_, err := clients.KubeClient.BatchV1().Jobs(experiment.Namespace).Create(job)
if err != nil {
log.Info("Unable to create the Job with the clientSet : ", err)
}
return nil
// launchJob spawn a kubernetes Job using the job Object received.
func (expDetails *ExperimentDetails) launchJob(job *batchv1.Job, clients ClientSets) error {
_, err := clients.KubeClient.BatchV1().Jobs(expDetails.Namespace).Create(context.Background(), job, v1.CreateOptions{})
return err
}
// BuildPodTemplateSpec return a PodTempplateSpec
func BuildPodTemplateSpec(experiment *ExperimentDetails, containerForPod *container.Builder) *podtemplatespec.Builder {
// BuildPodTemplateSpec return a PodTemplateSpec
func buildPodTemplateSpec(experiment *ExperimentDetails, containers ...*container.Builder) (*podtemplatespec.Builder, error) {
podtemplate := podtemplatespec.NewBuilder().
WithName(experiment.JobName).
WithNamespace(experiment.Namespace).
WithLabels(experiment.ExpLabels).
WithServiceAccountName(experiment.SvcAccount).
WithRestartPolicy(corev1.RestartPolicyOnFailure).
WithRestartPolicy(corev1.RestartPolicyNever).
WithVolumeBuilders(experiment.VolumeOpts.VolumeBuilders).
WithContainerBuildersNew(containerForPod)
WithAnnotations(experiment.Annotations).
WithContainerBuildersNew(containers...)
if experiment.TerminationGracePeriodSeconds != 0 {
podtemplate.WithTerminationGracePeriodSeconds(experiment.TerminationGracePeriodSeconds)
}
if !reflect.DeepEqual(experiment.SecurityContext.PodSecurityContext, corev1.PodSecurityContext{}) {
podtemplate.WithSecurityContext(experiment.SecurityContext.PodSecurityContext)
}
if len(experiment.SideCars) != 0 {
secrets := setSidecarSecrets(experiment)
if len(secrets) != 0 {
var volumeOpts VolumeOpts
volumeOpts.NewVolumeBuilder().BuildVolumeBuilderForSecrets(secrets)
podtemplate.WithVolumeBuilders(volumeOpts.VolumeBuilders)
}
}
if experiment.HostPID {
podtemplate.WithHostPID(experiment.HostPID)
}
if experiment.ImagePullSecrets != nil {
podtemplate.WithImagePullSecrets(experiment.ImagePullSecrets)
}
if len(experiment.NodeSelector) != 0 {
podtemplate.WithNodeSelector(experiment.NodeSelector)
}
if experiment.Tolerations != nil {
podtemplate.WithTolerations(experiment.Tolerations...)
}
if _, err := podtemplate.Build(); err != nil {
log.Infof("Unable to build ChaosExperiment Job, due to: %v", err)
return nil
return nil, err
}
return podtemplate
return podtemplate, nil
}
func setSidecarSecrets(experiment *ExperimentDetails) []v1alpha1.Secret {
var secrets []v1alpha1.Secret
secretMap := make(map[string]bool)
for _, sidecar := range experiment.SideCars {
for _, secret := range sidecar.Secrets {
if _, ok := secretMap[secret.Name]; !ok {
secretMap[secret.Name] = true
secrets = append(secrets, secret)
}
}
}
return secrets
}
// BuildJobSpec returns a JobSpec
func BuildJobSpec(pod *podtemplatespec.Builder) *jobspec.Builder {
func buildJobSpec(pod *podtemplatespec.Builder) (*jobspec.Builder, error) {
jobSpecObj := jobspec.NewBuilder().
WithPodTemplateSpecBuilder(pod)
_, err := jobSpecObj.Build()
if err != nil {
log.Errorln(err)
return nil, err
}
return jobSpecObj
return jobSpecObj, nil
}
// BuildJob will build the JobObject for creation
func (experiment *ExperimentDetails) BuildJob(pod *podtemplatespec.Builder, jobspec *jobspec.Builder) (*batchv1.Job, error) {
//restartPolicy := corev1.RestartPolicyOnFailure
func (expDetails *ExperimentDetails) buildJob(jobspec *jobspec.Builder) (*batchv1.Job, error) {
jobObj, err := job.NewBuilder().
WithJobSpecBuilder(jobspec).
WithName(experiment.JobName).
WithNamespace(experiment.Namespace).
WithLabels(experiment.ExpLabels).
WithAnnotations(expDetails.Annotations).
WithName(expDetails.JobName).
WithNamespace(expDetails.Namespace).
WithLabels(expDetails.ExpLabels).
Build()
if err != nil {
log.Errorln(err)
return jobObj, err
}
return jobObj, nil
return jobObj, err
}

View File

@ -0,0 +1,81 @@
package utils
import (
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"testing"
)
func FuzzBuildContainerSpec(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzzConsumer := fuzz.NewConsumer(data)
targetStruct := &struct {
ExpDetails *ExperimentDetails
EnvVars []corev1.EnvVar
}{}
err := fuzzConsumer.GenerateStruct(targetStruct)
if err != nil {
return
}
containerSpec, err := buildContainerSpec(targetStruct.ExpDetails, targetStruct.EnvVars)
if err != nil {
return
}
container, err := containerSpec.Build()
if err != nil {
return
}
require.Equal(t, targetStruct.ExpDetails.JobName, container.Name)
require.Equal(t, targetStruct.ExpDetails.ExpImage, container.Image)
require.Equal(t, targetStruct.ExpDetails.ExpCommand, container.Command)
require.Equal(t, targetStruct.ExpDetails.ExpArgs, container.Args)
require.Equal(t, targetStruct.ExpDetails.ExpImagePullPolicy, container.ImagePullPolicy)
require.Equal(t, targetStruct.EnvVars, container.Env)
})
}
func FuzzGetEnvFromMap(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzzConsumer := fuzz.NewConsumer(data)
targetStruct := &struct {
m map[string]corev1.EnvVar
}{}
err := fuzzConsumer.GenerateStruct(targetStruct)
if err != nil {
return
}
envs := getEnvFromMap(targetStruct.m)
var envCount = len(envs)
require.Equal(t, envCount, len(targetStruct.m)+1)
})
}
func FuzzSetSidecarSecrets(f *testing.F) {
f.Fuzz(func(t *testing.T, data []byte) {
fuzzConsumer := fuzz.NewConsumer(data)
targetStruct := &struct {
experiment *ExperimentDetails
}{}
err := fuzzConsumer.GenerateStruct(targetStruct)
if err != nil {
return
}
if targetStruct.experiment != nil {
secrets := setSidecarSecrets(targetStruct.experiment)
require.GreaterOrEqual(t, len(secrets), 1)
for _, sidecar := range targetStruct.experiment.SideCars {
for _, secret := range sidecar.Secrets {
for _, s := range secrets {
require.Equal(t, s.Name, secret.Name)
}
}
}
}
})
}

View File

@ -7,10 +7,10 @@ import (
)
// RandomString will generate a random string of length 6
func RandomString() string {
func RandomString(length int) string {
rand.Seed(time.Now().UnixNano())
chars := []rune("abcdefghijklmnopqrstuvwxyz" + "0123456789")
length := 6
var b strings.Builder
for i := 0; i < length; i++ {
b.WriteRune(chars[rand.Intn(len(chars))])

View File

@ -0,0 +1,38 @@
package utils
import (
"strings"
"testing"
)
func FuzzRandomString(f *testing.F) {
f.Add(6)
f.Fuzz(func(t *testing.T, n int) {
randomString := RandomString(n)
// Perform checks on the generated string
// Check if the length matches the expected length
if n >= 0 && len(randomString) != n {
t.Errorf("Generated string length doesn't match expected length")
}
// Check if the string contains only valid characters
if !isValidString(randomString) {
t.Errorf("Generated string contains invalid characters")
}
})
}
func isValidString(s string) bool {
// Define the set of valid characters
validChars := "abcdefghijklmnopqrstuvwxyz0123456789"
// Iterate over each character in the string
for _, char := range s {
// Check if the character is not in the set of valid characters
if !strings.ContainsRune(validChars, char) {
return false
}
}
return true
}

View File

@ -1,59 +1,108 @@
package utils
import (
"errors"
log "github.com/sirupsen/logrus"
"context"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
//PatchConfigMaps patches configmaps in experimentDetails struct.
func (expDetails *ExperimentDetails) PatchConfigMaps(clients ClientSets) error {
expDetails.SetConfigMaps(clients)
log.Infof("Validating configmaps specified in the ChaosExperiment")
err := expDetails.ValidateConfigMaps(clients)
if err != nil {
log.Infof("Error Validating configMaps, skipping Execution")
// PatchConfigMaps patches configmaps in experimentDetails struct.
func (expDetails *ExperimentDetails) PatchConfigMaps(clients ClientSets, engineDetails EngineDetails) error {
if err := expDetails.SetConfigMaps(clients, engineDetails); err != nil {
return err
}
if len(expDetails.ConfigMaps) != 0 {
log.Info("Validating configmaps specified in the ChaosExperiment & ChaosEngine")
if err := expDetails.ValidateConfigMaps(clients); err != nil {
return err
}
}
return nil
}
// ValidateConfigMap validates the configMap, before checking or creating them.
func (clientSets ClientSets) ValidateConfigMap(configMapName string, experiment *ExperimentDetails) error {
_, err := clientSets.KubeClient.CoreV1().ConfigMaps(experiment.Namespace).Get(configMapName, metav1.GetOptions{})
if err != nil {
return err
}
return nil
// ValidatePresenceOfConfigMapResourceInCluster validates the configMap, before checking or creating them.
func (clientSets ClientSets) ValidatePresenceOfConfigMapResourceInCluster(configMapName, namespace string) error {
_, err := clientSets.KubeClient.CoreV1().ConfigMaps(namespace).Get(context.Background(), configMapName, metav1.GetOptions{})
return err
}
// SetConfigMaps sets the value of configMaps in Experiment Structure
func (expDetails *ExperimentDetails) SetConfigMaps(clients ClientSets) {
func (expDetails *ExperimentDetails) SetConfigMaps(clients ClientSets, engineDetails EngineDetails) error {
chaosExperimentObj, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
experimentConfigMaps, err := expDetails.getConfigMapsFromChaosExperiment(clients)
if err != nil {
log.Infof("Unable to get ChaosEXperiment Resource, wouldn't not be able to patch ConfigMaps")
return err
}
configMaps := chaosExperimentObj.Spec.Definition.ConfigMaps
expDetails.ConfigMaps = configMaps
engineConfigMaps, err := expDetails.getConfigMapsFromChaosEngine(clients, engineDetails)
if err != nil {
return err
}
// Overriding the ConfigMaps from the ChaosEngine
expDetails.getOverridingConfigMapsFromChaosEngine(experimentConfigMaps, engineConfigMaps)
return nil
}
// ValidateConfigMaps checks for configMaps in the Application Namespace
// ValidateConfigMaps checks for configMaps in the Chaos Namespace
func (expDetails *ExperimentDetails) ValidateConfigMaps(clients ClientSets) error {
for _, v := range expDetails.ConfigMaps {
if v.Name == "" || v.MountPath == "" {
//log.Infof("Incomplete Information in ConfigMap, will skip execution")
return errors.New("Incomplete Information in ConfigMap, will skip execution")
return errors.Errorf("Incomplete Information in ConfigMap, will skip execution")
}
err := clients.ValidateConfigMap(v.Name, expDetails)
err := clients.ValidatePresenceOfConfigMapResourceInCluster(v.Name, expDetails.Namespace)
if err != nil {
log.Infof("Unable to get ConfigMap with Name: %v, in namespace: %v", v.Name, expDetails.Namespace)
} else {
log.Infof("Succesfully Validated ConfigMap: %v", v.Name)
return errors.Errorf("unable to get ConfigMap with Name: %v, in namespace: %v, error: %v", v.Name, expDetails.Namespace, err)
}
log.Infof("Successfully Validated ConfigMap: %v", v.Name)
}
return nil
}
func (expDetails *ExperimentDetails) getConfigMapsFromChaosExperiment(clients ClientSets) ([]v1alpha1.ConfigMap, error) {
chaosExperimentObj, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(context.Background(), expDetails.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Errorf("unable to get ChaosExperiment Resource, error: %v", err)
}
experimentConfigMaps := chaosExperimentObj.Spec.Definition.ConfigMaps
return experimentConfigMaps, nil
}
func (expDetails *ExperimentDetails) getConfigMapsFromChaosEngine(clients ClientSets, engineDetails EngineDetails) ([]v1alpha1.ConfigMap, error) {
chaosEngineObj, err := engineDetails.GetChaosEngine(clients)
if err != nil {
return nil, errors.Errorf("unable to get ChaosEngine Resource, error: %v", err)
}
experimentsList := chaosEngineObj.Spec.Experiments
for i := range experimentsList {
if experimentsList[i].Name == expDetails.Name {
engineConfigMaps := experimentsList[i].Spec.Components.ConfigMaps
return engineConfigMaps, nil
}
}
return nil, errors.Errorf("No experiment found with %v name in ChaosEngine", expDetails.Name)
}
// getOverridingConfigMapsFromChaosEngine will override configmaps from ChaosEngine
func (expDetails *ExperimentDetails) getOverridingConfigMapsFromChaosEngine(experimentConfigMaps []v1alpha1.ConfigMap, engineConfigMaps []v1alpha1.ConfigMap) {
for i := range engineConfigMaps {
flag := false
for j := range experimentConfigMaps {
if engineConfigMaps[i].Name == experimentConfigMaps[j].Name {
flag = true
if engineConfigMaps[i].MountPath != experimentConfigMaps[j].MountPath {
experimentConfigMaps[j].MountPath = engineConfigMaps[i].MountPath
}
}
}
if !flag {
experimentConfigMaps = append(experimentConfigMaps, engineConfigMaps[i])
}
}
expDetails.ConfigMaps = experimentConfigMaps
}

View File

@ -0,0 +1,527 @@
package utils
import (
"context"
"reflect"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
)
func TestPatchConfigMaps(t *testing.T) {
fakeConfigMap := "fake configmap"
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
chaosexperiment *v1alpha1.ChaosExperiment
configmap v1.ConfigMap
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMap,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
configmap: v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfigMap,
Namespace: experiment.Namespace,
},
Data: map[string]string{
"my-fake-key": "myfake-val",
}},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMap,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.KubeClient.CoreV1().ConfigMaps(experiment.Namespace).Create(context.Background(), &mock.configmap, metav1.CreateOptions{})
if err != nil {
t.Fatalf("configmap not created for %v test, err: %v", name, err)
}
_, err = client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
_, err = client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
err = experiment.PatchConfigMaps(client, engineDetails)
if !mock.isErr && err != nil {
t.Fatalf("fail to patch the configmap, err: %v", err)
}
if mock.isErr && err == nil {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
if !mock.isErr {
actualResult := len(experiment.ConfigMaps)
expectedResult := 1
if actualResult != expectedResult {
t.Fatalf("Test %q failed: expected length of configmap is %v but the actual length is %v", name, expectedResult, actualResult)
}
}
})
}
}
func TestValidateConfigMaps(t *testing.T) {
fakeConfigMapName := "fake configmap"
fakeNamespace := "fake-namespace"
tests := map[string]struct {
configmap v1.ConfigMap
experiment ExperimentDetails
isErr bool
}{
"Test Positive-1": {
configmap: v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfigMapName,
Namespace: fakeNamespace,
},
Data: map[string]string{
"my-fake-key": "myfake-val",
}},
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-job-name",
StatusCheckTimeout: 10,
ConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMapName,
MountPath: "fake mountpath",
},
},
},
isErr: false,
},
"Test Negative-1": {
configmap: v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfigMapName,
Namespace: fakeNamespace,
},
Data: map[string]string{
"my-fake-key": "myfake-val",
}},
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-job-name",
StatusCheckTimeout: 10,
ConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMapName,
},
},
},
isErr: true,
},
"Test Negative-2": {
configmap: v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfigMapName,
Namespace: fakeNamespace,
},
Data: map[string]string{
"my-fake-key": "myfake-val",
}},
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-job-name",
StatusCheckTimeout: 10,
ConfigMaps: []v1alpha1.ConfigMap{
{
MountPath: "fake mountpath",
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.KubeClient.CoreV1().ConfigMaps(fakeNamespace).Create(context.Background(), &mock.configmap, metav1.CreateOptions{})
if err != nil {
t.Fatalf("configmap not created for %v test, err: %v", name, err)
}
err = mock.experiment.ValidateConfigMaps(client)
if (!mock.isErr && err != nil) || (mock.isErr && err == nil) {
t.Fatalf("Validation for presence of configmap failed for %v test, err: %v", name, err)
}
})
}
}
func TestValidatePresenceOfConfigMapResourceInCluster(t *testing.T) {
fakeConfigMap := "fake configmap"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
tests := map[string]struct {
configmap v1.ConfigMap
isErr bool
}{
"Test Positive-1": {
configmap: v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: fakeConfigMap,
Namespace: experiment.Namespace,
},
Data: map[string]string{
"my-fake-key": "myfake-val",
}},
isErr: false,
},
"Test Negative-1": {
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
_, err := client.KubeClient.CoreV1().ConfigMaps(experiment.Namespace).Create(context.Background(), &mock.configmap, metav1.CreateOptions{})
if err != nil {
t.Fatalf("configmap not created for %v test, err: %v", name, err)
}
}
err := client.ValidatePresenceOfConfigMapResourceInCluster(fakeConfigMap, experiment.Namespace)
if (!mock.isErr && err != nil) || (mock.isErr && err == nil) {
t.Fatalf("Validation for presence of configmap failed for %v test, err: %v", name, err)
}
})
}
}
func TestSetConfigMaps(t *testing.T) {
fakeConfigMap := "fake configmap"
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
chaosengine *v1alpha1.ChaosEngine
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMap,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMap,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
}
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
err = experiment.SetConfigMaps(client, engineDetails)
if (!mock.isErr && err != nil) || (mock.isErr && err == nil) {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
actualResult := experiment.ConfigMaps
expectedResult := mock.chaosengine.Spec.Experiments[0].Spec.Components.ConfigMaps
if !reflect.DeepEqual(expectedResult, actualResult) && !mock.isErr {
t.Fatalf("%v Test Failed the expectedResult '%v' is not equal to actual result '%v'", name, expectedResult, actualResult)
}
})
}
}
func TestGetConfigMapsFromChaosExperiment(t *testing.T) {
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
isErr bool
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
isErr: false,
},
"Test Negative-1": {
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
}
experimentConfigMaps, err := experiment.getConfigMapsFromChaosExperiment(client)
if (!mock.isErr && err != nil) || (mock.isErr && err == nil) {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
if !mock.isErr {
if experimentConfigMaps != nil {
t.Fatalf("Test %q failed to get the config map from experiment: ", name)
}
}
})
}
}
func TestGetOverridingConfigMapsFromChaosEngine(t *testing.T) {
fakeConfigMapName := "fake-configmap"
tests := map[string]struct {
experiment ExperimentDetails
engineConfigMaps []v1alpha1.ConfigMap
isErr bool
}{
"Test Positive-1": {
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
},
engineConfigMaps: []v1alpha1.ConfigMap{
{
Name: fakeConfigMapName,
MountPath: "fake-mount-path",
},
},
isErr: false,
},
"Test Negative-1": {
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
},
engineConfigMaps: []v1alpha1.ConfigMap{},
isErr: false,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
var err error
mock.experiment.getOverridingConfigMapsFromChaosEngine(mock.engineConfigMaps, mock.engineConfigMaps)
if err != nil {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
actualResult := mock.engineConfigMaps
expectedResult := mock.experiment.ConfigMaps
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed: expected configmap is %v but the we get is '%v' from the experiment", name, expectedResult, actualResult)
} else if !reflect.DeepEqual(expectedResult, actualResult) && mock.isErr {
t.Fatalf("Test %q failed: expected configmap is %v and the we get is '%v' from the experiment", name, expectedResult, actualResult)
}
})
}
}

259
pkg/utils/engineHelper.go Normal file
View File

@ -0,0 +1,259 @@
package utils
import (
"context"
"fmt"
"reflect"
"strconv"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
litmuschaosv1alpha1 "github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
)
const (
SideCarEnabled = "sidecar/enabled"
SideCarPrefix = "SIDECAR"
)
// SetInstanceAttributeValuesFromChaosEngine set the value from the chaosengine
func (expDetails *ExperimentDetails) SetInstanceAttributeValuesFromChaosEngine(engine *EngineDetails, clients ClientSets) error {
chaosEngine, err := engine.GetChaosEngine(clients)
if err != nil {
return errors.Errorf("unable to get chaosEngine in namespace: %s", engine.EngineNamespace)
}
// fetch all the values from chaosengine and set into expDetails struct
expDetails.SetExpAnnotationFromEngine(chaosEngine).
SetEngineLabels(chaosEngine).
SetExpNodeSelectorFromEngine(chaosEngine).
SetResourceRequirementsFromEngine(chaosEngine).
SetImagePullSecretsFromEngine(chaosEngine).
SetTolerationsFromEngine(chaosEngine).
SetExpImageFromEngine(chaosEngine).
SetTerminationGracePeriodSecondsFromEngine(chaosEngine).
SetDefaultHealthCheck(chaosEngine)
return nil
}
// SetExpImageFromEngine will override the default exp image with the one provided in the chaosEngine
func (expDetails *ExperimentDetails) SetExpImageFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expRefList := engine.Spec.Experiments
for i := range expRefList {
if expRefList[i].Name == expDetails.Name {
if expRefList[i].Spec.Components.ExperimentImage != "" {
expDetails.ExpImage = expRefList[i].Spec.Components.ExperimentImage
}
}
}
return expDetails
}
// SetExpAnnotationFromEngine override the default exp annotation with the one provided in the chaosEngine
func (expDetails *ExperimentDetails) SetExpAnnotationFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expRefList := engine.Spec.Experiments
for i := range expRefList {
if expRefList[i].Name == expDetails.Name {
expDetails.Annotations = expRefList[i].Spec.Components.ExperimentAnnotations
}
}
return expDetails
}
// SetResourceRequirementsFromEngine add the resource requirements provided inside chaosengine
func (expDetails *ExperimentDetails) SetResourceRequirementsFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expRefList := engine.Spec.Experiments
for i := range expRefList {
if expRefList[i].Name == expDetails.Name {
expDetails.ResourceRequirements = expRefList[i].Spec.Components.Resources
}
}
return expDetails
}
// SetImagePullSecretsFromEngine add the image pull secrets provided inside chaosengine
func (expDetails *ExperimentDetails) SetImagePullSecretsFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expRefList := engine.Spec.Experiments
for i := range expRefList {
if expRefList[i].Name == expDetails.Name {
if expRefList[i].Spec.Components.ExperimentImagePullSecrets != nil {
expDetails.ImagePullSecrets = expRefList[i].Spec.Components.ExperimentImagePullSecrets
}
}
}
return expDetails
}
// SetExpNodeSelectorFromEngine add the nodeSelector attribute based on the key/value provided in the chaosEngine
func (expDetails *ExperimentDetails) SetExpNodeSelectorFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expRefList := engine.Spec.Experiments
for i := range expRefList {
if expRefList[i].Name == expDetails.Name {
expDetails.NodeSelector = expRefList[i].Spec.Components.NodeSelector
}
}
return expDetails
}
// SetTerminationGracePeriodSecondsFromEngine set the terminationGracePeriodSeconds for experiment pod provided in the chaosEngine
func (expDetails *ExperimentDetails) SetTerminationGracePeriodSecondsFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expDetails.TerminationGracePeriodSeconds = engine.Spec.TerminationGracePeriodSeconds
return expDetails
}
// SetTolerationsFromEngine add the tolerations based on the key/operator/effect provided in the chaosEngine
func (expDetails *ExperimentDetails) SetTolerationsFromEngine(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expRefList := engine.Spec.Experiments
for i := range expRefList {
if expRefList[i].Name == expDetails.Name {
expDetails.Tolerations = expRefList[i].Spec.Components.Tolerations
}
}
return expDetails
}
// SetDefaultHealthCheck sets th default health checks provided inside the chaosEngine
func (expDetails *ExperimentDetails) SetDefaultHealthCheck(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
expDetails.DefaultHealthCheck = strconv.FormatBool(engine.Spec.DefaultHealthCheck)
return expDetails
}
// SetOverrideEnvFromChaosEngine override the default envs with envs passed inside the chaosengine
func (expDetails *ExperimentDetails) SetOverrideEnvFromChaosEngine(engineName string, clients ClientSets) error {
engineSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(expDetails.Namespace).Get(context.Background(), engineName, metav1.GetOptions{})
if err != nil {
return errors.Errorf("unable to get ChaosEngine Resource in namespace: %v", expDetails.Namespace)
}
expList := engineSpec.Spec.Experiments
for _, exp := range expList {
if exp.Name == expDetails.Name {
envVars := exp.Spec.Components.ENV
for _, env := range envVars {
expDetails.envMap[env.Name] = env
// extracting and storing instance id explicitly
// as we need this variable while generating chaos-result name
if env.Name == "INSTANCE_ID" {
expDetails.InstanceID = env.Value
}
}
delay, timeout := getStatusCheckDelayAndTimeout(exp)
expDetails.envMap["STATUS_CHECK_DELAY"] = v1.EnvVar{
Name: "STATUS_CHECK_DELAY",
Value: strconv.Itoa(delay),
}
expDetails.envMap["STATUS_CHECK_TIMEOUT"] = v1.EnvVar{
Name: "STATUS_CHECK_TIMEOUT",
Value: strconv.Itoa(timeout),
}
expDetails.StatusCheckTimeout = timeout
}
}
// set the job cleanup policy
expDetails.envMap["JOB_CLEANUP_POLICY"] = v1.EnvVar{
Name: "JOB_CLEANUP_POLICY",
Value: string(engineSpec.Spec.JobCleanUpPolicy),
}
return nil
}
func (expDetails *ExperimentDetails) SetSideCarDetails(engineName string, clients ClientSets) error {
engineSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(expDetails.Namespace).Get(context.Background(), engineName, metav1.GetOptions{})
if err != nil {
return errors.Errorf("unable to get ChaosEngine Resource in namespace: %v", expDetails.Namespace)
}
if engineSpec.Annotations == nil {
return nil
}
if sidecarEnabled, ok := engineSpec.Annotations[SideCarEnabled]; !ok || (sidecarEnabled == "false") {
return nil
}
if len(engineSpec.Spec.Components.Sidecar) == 0 {
return fmt.Errorf("sidecar image is not set inside chaosengine")
}
expDetails.SideCars = expDetails.getSidecarDetails(engineSpec)
return nil
}
func (expDetails *ExperimentDetails) getSidecarDetails(engineSpec *litmuschaosv1alpha1.ChaosEngine) []SideCar {
var sidecars []SideCar
for _, v := range engineSpec.Spec.Components.Sidecar {
sidecar := SideCar{
Image: v.Image,
ImagePullPolicy: v.ImagePullPolicy,
Secrets: v.Secrets,
ENV: append(v.ENV, getDefaultEnvs(expDetails.JobName)...),
EnvFrom: v.EnvFrom,
}
if sidecar.ImagePullPolicy == "" {
sidecar.ImagePullPolicy = v1.PullIfNotPresent
}
sidecars = append(sidecars, sidecar)
}
return sidecars
}
func getDefaultEnvs(cName string) []v1.EnvVar {
return []v1.EnvVar{
{
Name: "POD_NAME",
ValueFrom: getEnvSource("v1", "metadata.name"),
},
{
Name: "POD_NAMESPACE",
ValueFrom: getEnvSource("v1", "metadata.namespace"),
},
{
Name: "MAIN_CONTAINER",
Value: cName,
},
}
}
// getEnvSource return the env source for the given apiVersion & fieldPath
func getEnvSource(apiVersion string, fieldPath string) *v1.EnvVarSource {
downwardENV := v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
APIVersion: apiVersion,
FieldPath: fieldPath,
},
}
return &downwardENV
}
// SetEngineLabels sets the engine labels
func (expDetails *ExperimentDetails) SetEngineLabels(engine *litmuschaosv1alpha1.ChaosEngine) *ExperimentDetails {
for k, v := range engine.Labels {
expDetails.ExpLabels[k] = v
}
return expDetails
}
func getStatusCheckDelayAndTimeout(exp litmuschaosv1alpha1.ExperimentList) (int, int) {
delay, timeout := 2, 180
if sc := exp.Spec.Components.StatusCheckTimeouts; !reflect.DeepEqual(sc, litmuschaosv1alpha1.StatusCheckTimeout{}) {
if sc.Timeout != 0 {
timeout = sc.Timeout
}
if sc.Delay != 0 {
delay = sc.Delay
}
}
return delay, timeout
}

View File

@ -0,0 +1,425 @@
package utils
import (
"context"
"reflect"
"testing"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
)
func TestSetExpImageFromEngine(t *testing.T) {
fakeNewExperimentImage := "fake-new-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ExperimentImage: fakeNewExperimentImage,
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created, err: %v", err)
}
expDetails := experiment.SetExpImageFromEngine(mock.chaosengine)
actualResult := expDetails.ExpImage
expectedResult := fakeNewExperimentImage
log.Infof("Actual output is: %v", actualResult)
if actualResult != expectedResult {
t.Fatalf("Test %q failed: expectedOutput is: %v but the actualOutput is: %v", name, expectedResult, actualResult)
}
})
}
}
func TestSetExpAnnotationFromEngine(t *testing.T) {
fakeNewExperimentAnnotation := map[string]string{"my-fake-key": "myfake-val"}
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ExperimentAnnotations: fakeNewExperimentAnnotation,
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
expDetails := experiment.SetExpAnnotationFromEngine(mock.chaosengine)
actualResult := expDetails.Annotations
expectedResult := fakeNewExperimentAnnotation
log.Infof("Actual output is: %v", actualResult)
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed: expectedOutput is: %v but the actualOutput is: %v", name, expectedResult, actualResult)
}
})
}
}
func TestSetResourceRequirementsFromEngine(t *testing.T) {
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("500"),
"memory": resource.MustParse("100"),
},
Requests: v1.ResourceList{
"cpu": resource.MustParse("10"),
"memory": resource.MustParse("5"),
},
},
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
expDetails := experiment.SetResourceRequirementsFromEngine(mock.chaosengine)
actualResult := expDetails.ResourceRequirements
expectedResult := mock.chaosengine.Spec.Experiments[0].Spec.Components.Resources
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed: expectedOutput is: '%v' but the actualOutput is: '%v'", name, expectedResult, actualResult)
}
})
}
}
func TestSetImagePullSecretsFromEngine(t *testing.T) {
fakeImagePullSecret := "Fake Image Pull Secret"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
ExperimentImagePullSecrets: []v1.LocalObjectReference{
{
Name: fakeImagePullSecret,
},
},
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
expDetails := experiment.SetImagePullSecretsFromEngine(mock.chaosengine)
actualResult := expDetails.ImagePullSecrets[0].Name
expectedResult := fakeImagePullSecret
if expectedResult != actualResult {
t.Fatalf("Test %q failed: expectedOutput is: '%v' but the actualOutput is: '%v'", name, expectedResult, actualResult)
}
})
}
}
func TestSetExpNodeSelectorFromEngine(t *testing.T) {
fakeNodeSelector := make(map[string]string)
fakeNodeSelector["my-fake-key"] = "my-fake-val"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
NodeSelector: fakeNodeSelector,
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
expDetails := experiment.SetExpNodeSelectorFromEngine(mock.chaosengine)
actualResult := expDetails.NodeSelector
expectedResult := fakeNodeSelector
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed: expectedOutput is: '%v' but the actualOutput is: '%v'", name, expectedResult, actualResult)
}
})
}
}
func TestSetTolerationsFromEngine(t *testing.T) {
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
Tolerations: []v1.Toleration{
{
Key: "fake-key",
Operator: v1.TolerationOperator("Exists"),
Effect: v1.TaintEffect("NoSchedule"),
},
},
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
expDetails := experiment.SetTolerationsFromEngine(mock.chaosengine)
actualResult := expDetails.Tolerations
expectedResult := mock.chaosengine.Spec.Experiments[0].Spec.Components.Tolerations
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed: expectedOutput is: '%v' but the actualOutput is: '%v'", name, expectedResult, actualResult)
}
})
}
}
func TestInstanceAttributeValuesFromChaosEngine(t *testing.T) {
fakeNewExperimentAnnotation := map[string]string{"my-fake-key": "myfake-val"}
fakeNodeSelector := make(map[string]string)
fakeImagePullSecret := "Fake Image Pull Secret"
fakeNewExperimentImage := "fake-new-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
NodeSelector: fakeNodeSelector,
ExperimentAnnotations: fakeNewExperimentAnnotation,
ExperimentImage: fakeNewExperimentImage,
Resources: v1.ResourceRequirements{
Limits: v1.ResourceList{
"cpu": resource.MustParse("500"),
"memory": resource.MustParse("100"),
},
Requests: v1.ResourceList{
"cpu": resource.MustParse("10"),
"memory": resource.MustParse("5"),
},
},
Tolerations: []v1.Toleration{
{
Key: "fake-key",
Operator: v1.TolerationOperator("Exists"),
Effect: v1.TaintEffect("NoSchedule"),
},
},
ExperimentImagePullSecrets: []v1.LocalObjectReference{
{
Name: fakeImagePullSecret,
},
},
},
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created, err: %v", err)
}
if err = experiment.SetInstanceAttributeValuesFromChaosEngine(&engineDetails, client); err != nil {
t.Fatalf("%v test failed, err: %v", name, err)
}
})
}
}

77
pkg/utils/environment.go Normal file
View File

@ -0,0 +1,77 @@
package utils
import (
"context"
"os"
"strconv"
"strings"
"github.com/litmuschaos/chaos-runner/pkg/log"
"github.com/litmuschaos/chaos-runner/pkg/telemetry"
v1 "k8s.io/api/core/v1"
)
// SetEngineDetails adds the ENV's to EngineDetails
func (engineDetails *EngineDetails) SetEngineDetails() *EngineDetails {
engineDetails.Experiments = strings.Split(os.Getenv("EXPERIMENT_LIST"), ",")
engineDetails.Name = os.Getenv("CHAOSENGINE")
engineDetails.EngineNamespace = os.Getenv("CHAOS_NAMESPACE")
engineDetails.SvcAccount = os.Getenv("CHAOS_SVC_ACC")
engineDetails.ClientUUID = os.Getenv("CLIENT_UUID")
engineDetails.AuxiliaryAppInfo = os.Getenv("AUXILIARY_APPINFO")
engineDetails.Targets = os.Getenv("TARGETS")
return engineDetails
}
// SetEngineUID set the chaosengine UID
func (engineDetails *EngineDetails) SetEngineUID(clients ClientSets) error {
chaosEngine, err := engineDetails.GetChaosEngine(clients)
if err != nil {
return err
}
engineDetails.UID = string(chaosEngine.UID)
return nil
}
// SetENV sets ENV values in experimentDetails struct.
func (expDetails *ExperimentDetails) SetENV(ctx context.Context, engineDetails EngineDetails, clients ClientSets) error {
// Setting envs from engine fields other than env
expDetails.setEnv("CHAOSENGINE", engineDetails.Name).
setEnv("TARGETS", engineDetails.Targets).
setEnv("CHAOS_NAMESPACE", engineDetails.EngineNamespace).
setEnv("AUXILIARY_APPINFO", engineDetails.AuxiliaryAppInfo).
setEnv("CHAOS_UID", engineDetails.UID).
setEnv("EXPERIMENT_NAME", expDetails.Name).
setEnv("LIB_IMAGE_PULL_POLICY", string(expDetails.ExpImagePullPolicy)).
setEnv("TERMINATION_GRACE_PERIOD_SECONDS", strconv.Itoa(int(expDetails.TerminationGracePeriodSeconds))).
setEnv("DEFAULT_HEALTH_CHECK", expDetails.DefaultHealthCheck).
setEnv("CHAOS_SERVICE_ACCOUNT", expDetails.SvcAccount).
setEnv("OTEL_EXPORTER_OTLP_ENDPOINT", os.Getenv(telemetry.OTELExporterOTLPEndpoint)).
setEnv("TRACE_PARENT", telemetry.GetMarshalledSpanFromContext(ctx))
// Get the Default ENV's from ChaosExperiment
log.Info("Getting the ENV Variables")
if err := expDetails.SetDefaultEnvFromChaosExperiment(clients); err != nil {
return err
}
// OverWriting the Defaults Varibles from the ChaosEngine ENV
if err := expDetails.SetOverrideEnvFromChaosEngine(engineDetails.Name, clients); err != nil {
return err
}
return nil
}
// setEnv set the env inside experimentDetails struct
func (expDetails *ExperimentDetails) setEnv(key, value string) *ExperimentDetails {
if value == "" {
return expDetails
}
expDetails.envMap[key] = v1.EnvVar{
Name: key,
Value: value,
}
return expDetails
}

121
pkg/utils/event.go Normal file
View File

@ -0,0 +1,121 @@
package utils
import (
"context"
"time"
"github.com/litmuschaos/chaos-runner/pkg/log"
apiv1 "k8s.io/api/core/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientTypes "k8s.io/apimachinery/pkg/types"
)
// CreateEvents create the events in the desired resource
func (engineDetails EngineDetails) CreateEvents(eventAttributes *EventAttributes, clients ClientSets) error {
events := &apiv1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: eventAttributes.Name,
Namespace: engineDetails.EngineNamespace,
},
Source: apiv1.EventSource{
Component: engineDetails.Name + "-runner",
},
Message: eventAttributes.Message,
Reason: eventAttributes.Reason,
Type: eventAttributes.Type,
Count: 1,
FirstTimestamp: metav1.Time{Time: time.Now()},
LastTimestamp: metav1.Time{Time: time.Now()},
InvolvedObject: apiv1.ObjectReference{
APIVersion: "litmuschaos.io/v1alpha1",
Kind: "ChaosEngine",
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
UID: clientTypes.UID(engineDetails.UID),
},
}
_, err := clients.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).Create(context.Background(), events, metav1.CreateOptions{})
return err
}
// GenerateEvents update the events and increase the count by 1, if already present
// else it will create a new event
func (engineDetails EngineDetails) GenerateEvents(eventAttributes *EventAttributes, clients ClientSets) error {
event, err := clients.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).Get(context.Background(), eventAttributes.Name, metav1.GetOptions{})
if err != nil {
if k8serrors.IsNotFound(err) {
if err := engineDetails.CreateEvents(eventAttributes, clients); err != nil {
return err
}
} else {
return err
}
} else {
event.LastTimestamp = metav1.Time{Time: time.Now()}
event.Count = event.Count + 1
event.Message = eventAttributes.Message
_, err = clients.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).Update(context.Background(), event, metav1.UpdateOptions{})
return err
}
return nil
}
// ExperimentSkipped is an standard event spawned just after a ChaosExperiment is skipped
func (expDetails ExperimentDetails) ExperimentSkipped(reason string, engineDetails EngineDetails, clients ClientSets) {
event := EventAttributes{}
msg := "Experiment Job creation failed, skipping Chaos Experiment: " + expDetails.Name
event.SetEventAttributes(reason, "Warning", msg)
event.Name = event.Reason + expDetails.Name + string(engineDetails.UID)
if err := engineDetails.GenerateEvents(&event, clients); err != nil {
log.Errorf("unable to create event, err: %v", err)
}
}
// ExperimentDependencyCheck is an standard event spawned just after validating
// experiment dependent resources such as ChaosExperiment, ConfigMaps and Secrets.
func (expDetails ExperimentDetails) ExperimentDependencyCheck(engineDetails EngineDetails, clients ClientSets) {
event := EventAttributes{}
msg := "Experiment resources validated for Chaos Experiment: " + expDetails.Name
event.SetEventAttributes(ExperimentDependencyCheckReason, "Normal", msg)
event.Name = event.Reason + expDetails.Name + string(engineDetails.UID)
if err := engineDetails.GenerateEvents(&event, clients); err != nil {
log.Errorf("unable to create event, err: %v", err)
}
}
// ExperimentJobCreate is an standard event spawned just after starting ChaosExperiment Job
func (expDetails ExperimentDetails) ExperimentJobCreate(engineDetails EngineDetails, clients ClientSets) {
event := EventAttributes{}
msg := "Experiment Job " + expDetails.JobName + " for Chaos Experiment: " + expDetails.Name
event.SetEventAttributes(ExperimentJobCreateReason, "Normal", msg)
event.Name = event.Reason + expDetails.Name + string(engineDetails.UID)
if err := engineDetails.GenerateEvents(&event, clients); err != nil {
log.Errorf("unable to create event, err: %v", err)
}
}
// ExperimentJobCleanUp is an standard event spawned just after deleting ChaosExperiment Job
func (expDetails ExperimentDetails) ExperimentJobCleanUp(jobCleanUpPolicy string, engineDetails EngineDetails, clients ClientSets) {
event := EventAttributes{}
msg := "Experiment Job " + expDetails.JobName + " will be retained"
if jobCleanUpPolicy == "delete" {
msg = "Experiment Job: " + expDetails.JobName + " will be deleted"
}
event.SetEventAttributes(ExperimentJobCleanUpReason, "Normal", msg)
event.Name = event.Reason + expDetails.Name + string(engineDetails.UID)
if err := engineDetails.GenerateEvents(&event, clients); err != nil {
log.Errorf("unable to create event, err: %v", err)
}
}
// SetEventAttributes set the event attributes for each
func (event *EventAttributes) SetEventAttributes(reason, eventType, msg string) {
event.Message = msg
event.Reason = reason
event.Type = eventType
}

319
pkg/utils/event_test.go Normal file
View File

@ -0,0 +1,319 @@
package utils
import (
"context"
"strings"
"testing"
"time"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientTypes "k8s.io/apimachinery/pkg/types"
)
func TestCreateEvents(t *testing.T) {
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "",
}
eventAtr := EventAttributes{
Reason: "fake-reason",
Message: "fake-message",
Type: "fake-type",
Name: "fake-name",
}
client := CreateFakeClient(t)
err := engineDetails.CreateEvents(&eventAtr, client)
if err != nil {
t.Fatalf("TestCreateEvents failed unable to get event, err: %v", err)
}
events, err := client.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{})
if err != nil || len(events.Items) == 0 {
t.Fatalf("TestCreateEvents failed to get events, err: %v", err)
}
}
func TestGenerateEvents(t *testing.T) {
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "",
}
eventAtr := EventAttributes{
Reason: "fake-reason",
Message: "fake-message",
Type: "fake-type",
Name: "fake-name",
}
tests := map[string]struct {
events v1.Event
isErr bool
}{
"Test Positive-1": {
isErr: false,
},
"Test Positive-2": {
events: v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: eventAtr.Name,
Namespace: engineDetails.EngineNamespace,
},
Source: v1.EventSource{
Component: engineDetails.Name + "-runner",
},
Message: eventAtr.Message,
Reason: eventAtr.Reason,
Type: eventAtr.Type,
Count: 1,
FirstTimestamp: metav1.Time{Time: time.Now()},
LastTimestamp: metav1.Time{Time: time.Now()},
InvolvedObject: v1.ObjectReference{
APIVersion: "litmuschaos.io/v1alpha1",
Kind: "ChaosEngine",
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
UID: clientTypes.UID(engineDetails.UID),
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if mock.isErr {
_, err := client.KubeClient.CoreV1().Events(mock.events.Namespace).Create(context.Background(), &mock.events, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create event for %v test, err: %v", name, err)
}
}
err := engineDetails.GenerateEvents(&eventAtr, client)
if err != nil {
t.Fatalf("%v fail to generate events, err: %v", name, err)
}
events, err := client.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{})
if err != nil || len(events.Items) == 0 {
t.Fatalf("%v fail to get events, err: %v", name, err)
}
})
}
}
func TestExperimentSkipped(t *testing.T) {
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "",
}
eventAtr := EventAttributes{
Reason: "fake-reason",
Message: "fake-message",
Type: "fake-type",
Name: "fake-name",
}
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
tests := map[string]struct {
events v1.Event
isErr bool
}{
"Test Positive-1": {
isErr: false,
},
"Test Positive-2": {
events: v1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: eventAtr.Name,
Namespace: engineDetails.EngineNamespace,
},
Source: v1.EventSource{
Component: engineDetails.Name + "-runner",
},
Message: eventAtr.Message,
Reason: eventAtr.Reason,
Type: eventAtr.Type,
Count: 1,
FirstTimestamp: metav1.Time{Time: time.Now()},
LastTimestamp: metav1.Time{Time: time.Now()},
InvolvedObject: v1.ObjectReference{
APIVersion: "litmuschaos.io/v1alpha1",
Kind: "ChaosEngine",
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
UID: clientTypes.UID(engineDetails.UID),
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if mock.isErr {
_, err := client.KubeClient.CoreV1().Events(mock.events.Namespace).Create(context.Background(), &mock.events, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create event for %v test, err: %v", name, err)
}
}
experiment.ExperimentSkipped(eventAtr.Reason, engineDetails, client)
events, err := client.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{})
if err != nil || len(events.Items) == 0 {
t.Fatalf("%v fail to get events, err: %v", name, err)
}
if mock.isErr && !strings.Contains(events.Items[1].Message, "Experiment Job creation failed, skipping Chaos Experiment") {
t.Fatalf("%v failed to get the skip event message", name)
}
})
}
}
func TestExperimentDependencyCheck(t *testing.T) {
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "",
}
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
tests := map[string]struct {
isErr bool
}{
"Test Positive-1": {
isErr: false,
},
"Test Positive-2": {
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
experiment.ExperimentDependencyCheck(engineDetails, client)
}
events, err := client.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Fatalf("%v fail to get events, err: %v", name, err)
}
if mock.isErr {
require.Equal(t, 0, len(events.Items))
return
}
require.Equal(t, 1, len(events.Items))
require.Contains(t, events.Items[0].Message, "Experiment resources validated for Chaos Experiment")
})
}
}
func TestExperimentJobCreate(t *testing.T) {
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "",
}
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
tests := map[string]struct {
isErr bool
}{
"Test Positive-1": {
isErr: false,
},
"Test Positive-2": {
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
experiment.ExperimentJobCreate(engineDetails, client)
}
events, err := client.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{})
if err != nil {
t.Fatalf("%v fail to get events, err: %v", name, err)
}
if mock.isErr {
require.Equal(t, 0, len(events.Items))
return
}
require.Equal(t, 1, len(events.Items))
require.Contains(t, events.Items[0].Message, "Experiment Job "+experiment.JobName+" for Chaos Experiment: "+experiment.Name)
})
}
}
func TestExperimentJobCleanUp(t *testing.T) {
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "",
}
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
tests := map[string]struct {
jobCleanupPolicy string
}{
"Test Positive-1": {
jobCleanupPolicy: "delete",
},
"Test Positive-2": {
jobCleanupPolicy: "retain",
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
experiment.ExperimentJobCleanUp(mock.jobCleanupPolicy, engineDetails, client)
events, err := client.KubeClient.CoreV1().Events(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{})
if err != nil || len(events.Items) == 0 {
t.Fatalf("%v fail to get events, err: %v", name, err)
}
if mock.jobCleanupPolicy == "retain" {
require.Contains(t, events.Items[0].Message, "Experiment Job "+experiment.JobName+" will be retained")
return
}
if mock.jobCleanupPolicy == "delete" {
require.Contains(t, events.Items[0].Message, "Experiment Job: "+experiment.JobName+" will be deleted")
}
})
}
}

View File

@ -1,15 +1,16 @@
package utils
import (
log "github.com/sirupsen/logrus"
"github.com/pkg/errors"
)
// ExperimentNotFoundPatchEngine patches the chaosEngine when ChaosExperiment is not Found
func (engineDetails EngineDetails) ExperimentNotFoundPatchEngine(experiment *ExperimentDetails, clients ClientSets) {
func (engineDetails EngineDetails) ExperimentNotFoundPatchEngine(experiment *ExperimentDetails, clients ClientSets) error {
var expStatus ExperimentStatus
expStatus.NotFoundExperimentStatus(experiment)
expStatus.NotFoundExperimentStatus(experiment.Name, engineDetails.Name)
if err := expStatus.PatchChaosEngineStatus(engineDetails, clients); err != nil {
log.Infof("Unable to Patch ChaosEngine with Status, error: %v", err)
return errors.Errorf("unable to Patch ChaosEngine with Status, error: %v", err)
}
return nil
}

View File

@ -0,0 +1,141 @@
package utils
import (
"context"
litmuschaosv1alpha1 "github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// CreateExperimentList make the list of all experiment, provided inside chaosengine
func (engineDetails *EngineDetails) CreateExperimentList() []ExperimentDetails {
var ExperimentDetailsList []ExperimentDetails
for i := range engineDetails.Experiments {
ExperimentDetailsList = append(ExperimentDetailsList, engineDetails.NewExperimentDetails(i))
}
return ExperimentDetailsList
}
// NewExperimentDetails create and initialize the experimentDetails
func (engineDetails *EngineDetails) NewExperimentDetails(i int) ExperimentDetails {
var experimentDetails ExperimentDetails
experimentDetails.envMap = make(map[string]v1.EnvVar)
experimentDetails.ExpLabels = make(map[string]string)
// set the initial values from the EngineDetails struct
experimentDetails.Name = engineDetails.Experiments[i]
experimentDetails.SvcAccount = engineDetails.SvcAccount
experimentDetails.Namespace = engineDetails.EngineNamespace
// Setting the JobName in Experiment related struct
experimentDetails.JobName = experimentDetails.Name + "-" + RandomString(6)
return experimentDetails
}
// SetDefaultEnvFromChaosExperiment sets the Env's in Experiment Structure
func (expDetails *ExperimentDetails) SetDefaultEnvFromChaosExperiment(clients ClientSets) error {
experimentEnv, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(context.Background(), expDetails.Name, metav1.GetOptions{})
if err != nil {
return errors.Errorf("unable to get the %v ChaosExperiment in %v namespace, error: %v", expDetails.Name, expDetails.Namespace, err)
}
envList := experimentEnv.Spec.Definition.ENVList
for _, env := range envList {
expDetails.envMap[env.Name] = env
}
return nil
}
// SetValueFromChaosResources fetches required values from various Chaos Resources
func (expDetails *ExperimentDetails) SetValueFromChaosResources(engineDetails *EngineDetails, clients ClientSets) error {
if err := expDetails.SetDefaultAttributeValuesFromChaosExperiment(clients, engineDetails); err != nil {
return errors.Errorf("unable to set value from Chaos Experiment, error: %v", err)
}
if err := expDetails.SetInstanceAttributeValuesFromChaosEngine(engineDetails, clients); err != nil {
return errors.Errorf("unable to set value from Chaos Engine, error: %v", err)
}
return nil
}
// HandleChaosExperimentExistence will check the experiment in the app namespace
func (expDetails *ExperimentDetails) HandleChaosExperimentExistence(engineDetails EngineDetails, clients ClientSets) error {
_, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(context.Background(), expDetails.Name, metav1.GetOptions{})
if err != nil {
if err := engineDetails.ExperimentNotFoundPatchEngine(expDetails, clients); err != nil {
return errors.Errorf("unable to patch Chaos Engine Name: %v, namespace: %v, error: %v", engineDetails.Name, engineDetails.EngineNamespace, err)
}
return errors.Errorf("unable to list Chaos Experiment Name: %v,in Namespace: %v, error: %v", expDetails.Name, expDetails.Namespace, err)
}
return nil
}
// SetDefaultAttributeValuesFromChaosExperiment sets value in experimentDetails struct from chaosExperiment
func (expDetails *ExperimentDetails) SetDefaultAttributeValuesFromChaosExperiment(clients ClientSets, engine *EngineDetails) error {
experimentSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(context.Background(), expDetails.Name, metav1.GetOptions{})
if err != nil {
return errors.Errorf("unable to get %v ChaosExperiment instance in namespace: %v", expDetails.Name, expDetails.Namespace)
}
// fetch all the values from chaosexperiment and set into expDetails struct
expDetails.SetImage(experimentSpec).
SetImagePullPolicy(experimentSpec).
SetArgs(experimentSpec).
SetCommand(experimentSpec).
SetLabels(experimentSpec, engine).
SetSecurityContext(experimentSpec).
SetHostPID(experimentSpec)
return nil
}
// SetLabels sets the Experiment Labels, in Experiment Structure
func (expDetails *ExperimentDetails) SetLabels(experimentSpec *litmuschaosv1alpha1.ChaosExperiment, engine *EngineDetails) *ExperimentDetails {
expDetails.ExpLabels = experimentSpec.Spec.Definition.Labels
expDetails.ExpLabels["chaosUID"] = engine.UID
return expDetails
}
// SetImage sets the Experiment Image, in Experiment Structure
func (expDetails *ExperimentDetails) SetImage(experimentSpec *litmuschaosv1alpha1.ChaosExperiment) *ExperimentDetails {
expDetails.ExpImage = experimentSpec.Spec.Definition.Image
return expDetails
}
// SetImagePullPolicy sets the Experiment ImagePullPolicy, in Experiment Structure
func (expDetails *ExperimentDetails) SetImagePullPolicy(experimentSpec *litmuschaosv1alpha1.ChaosExperiment) *ExperimentDetails {
if experimentSpec.Spec.Definition.ImagePullPolicy == "" {
expDetails.ExpImagePullPolicy = DefaultExpImagePullPolicy
} else {
expDetails.ExpImagePullPolicy = experimentSpec.Spec.Definition.ImagePullPolicy
}
return expDetails
}
// SetArgs sets the Experiment Image, in Experiment Structure
func (expDetails *ExperimentDetails) SetArgs(experimentSpec *litmuschaosv1alpha1.ChaosExperiment) *ExperimentDetails {
expDetails.ExpArgs = experimentSpec.Spec.Definition.Args
return expDetails
}
// SetCommand to execute inside the experiment image.
func (expDetails *ExperimentDetails) SetCommand(experimentSpec *litmuschaosv1alpha1.ChaosExperiment) *ExperimentDetails {
expDetails.ExpCommand = experimentSpec.Spec.Definition.Command
return expDetails
}
// SetSecurityContext sets the security context, in Experiment Structure
func (expDetails *ExperimentDetails) SetSecurityContext(experimentSpec *litmuschaosv1alpha1.ChaosExperiment) *ExperimentDetails {
expDetails.SecurityContext = experimentSpec.Spec.Definition.SecurityContext
return expDetails
}
// SetHostPID sets the hostPID, in Experiment Structure
func (expDetails *ExperimentDetails) SetHostPID(experimentSpec *litmuschaosv1alpha1.ChaosExperiment) *ExperimentDetails {
expDetails.HostPID = experimentSpec.Spec.Definition.HostPID
return expDetails
}

View File

@ -0,0 +1,906 @@
package utils
import (
"context"
"reflect"
"testing"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestCreateExperimentList(t *testing.T) {
tests := map[string]struct {
engineDetails EngineDetails
isErr bool
}{
"Test Positive-1": {
engineDetails: EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
Experiments: []string{
"fake-exp-1",
"fake-exp-2",
},
},
isErr: false,
},
"Test Negative-1": {
engineDetails: EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
},
isErr: true,
},
}
for name, moke := range tests {
t.Run(name, func(t *testing.T) {
ExpList := moke.engineDetails.CreateExperimentList()
if len(ExpList) == 0 && !moke.isErr {
t.Fatalf("%v test failed as the experiment list is still empty", name)
} else if len(ExpList) != 0 && moke.isErr {
t.Fatalf("%v test failed as the experiment list is non empty for non empty experiment details on engine", name)
}
for i := range ExpList {
if ExpList[i].Name != moke.engineDetails.Experiments[i] && !moke.isErr {
t.Fatalf("The expected experimentName is %v but got %v", ExpList[i].Name, moke.engineDetails.Experiments[i])
}
}
})
}
}
func TestNewExperimentDetails(t *testing.T) {
tests := map[string]struct {
engineDetails EngineDetails
}{
"Test Positive-1": {
engineDetails: EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
Experiments: []string{
"fake-exp-1",
},
},
},
}
for name, moke := range tests {
t.Run(name, func(t *testing.T) {
ExpDetails := moke.engineDetails.NewExperimentDetails(0)
if ExpDetails.Name != moke.engineDetails.Experiments[0] {
t.Fatalf("%v test failed to create experiment details", name)
}
})
}
}
func TestSetDefaultEnvFromChaosExperiment(t *testing.T) {
fakeExperimentImage := "fake-experiment-image"
fakeTotalChaosDuration := "20"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
isErr bool
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
ENVList: []v1.EnvVar{
{
Name: "TOTAL_CHAOS_DURATION",
Value: fakeTotalChaosDuration,
},
{
Name: "CHAOS_INTERVAL",
Value: "10",
},
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
ENVList: []v1.EnvVar{},
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
err = experiment.SetDefaultEnvFromChaosExperiment(client)
if err != nil {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
expectedResult := experiment.envMap["TOTAL_CHAOS_DURATION"].Value
actualResult := fakeTotalChaosDuration
if expectedResult != actualResult && !mock.isErr {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestSetDefaultAttributeValuesFromChaosExperiment(t *testing.T) {
fakeExperimentImage := "fake-experiment-image"
fakeExperimentImagePullPolicy := "fake-exp-pull-policy"
fakeTotalChaosDuration := "20"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
ExpCommand: []string{},
ConfigMaps: []v1alpha1.ConfigMap{},
Secrets: []v1alpha1.Secret{},
ExpImage: "",
ExpImagePullPolicy: "",
SecurityContext: v1alpha1.SecurityContext{},
HostPID: false,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "fake-chaosuid",
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
isErr bool
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
ImagePullPolicy: v1.PullPolicy(fakeExperimentImagePullPolicy),
Labels: map[string]string{
"fake-label-key": "fake-label-value",
"chaosUID": "",
},
ENVList: []v1.EnvVar{
{
Name: "TOTAL_CHAOS_DURATION",
Value: fakeTotalChaosDuration,
},
{
Name: "CHAOS_INTERVAL",
Value: "10",
},
},
HostPID: false,
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
ImagePullPolicy: v1.PullPolicy(fakeExperimentImagePullPolicy),
Labels: map[string]string{
"fake-label-key": "fake-label-value",
"chaosUID": "",
},
ENVList: []v1.EnvVar{
{
Name: "TOTAL_CHAOS_DURATION",
Value: fakeTotalChaosDuration,
},
{
Name: "CHAOS_INTERVAL",
Value: "10",
},
},
HostPID: false,
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
err = experiment.SetDefaultAttributeValuesFromChaosExperiment(client, &engineDetails)
if err != nil {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
if !mock.isErr {
expectedResult := experiment.ExpImage
actualResult := fakeExperimentImage
if expectedResult != actualResult {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
}
})
}
}
func TestSetValueFromChaosResources(t *testing.T) {
fakeExperimentImage := "fake-exp-image"
fakeExperimentImagePullPolicy := v1.PullPolicy("fake-exp-image-pull-policy")
fakeExperimentArgs := "fake-exp-args"
fakeHostPID := false
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
ConfigMaps: []v1alpha1.ConfigMap{},
Secrets: []v1alpha1.Secret{},
ExpImage: "",
ExpImagePullPolicy: "",
SecurityContext: v1alpha1.SecurityContext{},
HostPID: false,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "fake-chaosuid",
}
expStatus := ExperimentStatus{
Name: "Fake-Exp-Name",
Status: v1alpha1.ExperimentStatusRunning,
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
chaosengine *v1alpha1.ChaosEngine
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusInitialized,
Experiments: []v1alpha1.ExperimentStatuses{
{
Name: expStatus.Name,
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
ImagePullPolicy: fakeExperimentImagePullPolicy,
Labels: map[string]string{
"fake-label-key": "fake-label-value",
},
Args: []string{
fakeExperimentArgs,
},
SecurityContext: v1alpha1.SecurityContext{
PodSecurityContext: v1.PodSecurityContext{
RunAsUser: ptrint64(1000),
},
},
HostPID: fakeHostPID,
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
ImagePullPolicy: fakeExperimentImagePullPolicy,
Labels: map[string]string{
"fake-label-key": "fake-label-value",
},
Args: []string{
fakeExperimentArgs,
},
SecurityContext: v1alpha1.SecurityContext{
PodSecurityContext: v1.PodSecurityContext{
RunAsUser: ptrint64(1000),
},
},
HostPID: fakeHostPID,
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
if !mock.isErr {
_, err = client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
}
err = experiment.SetValueFromChaosResources(&engineDetails, client)
if err != nil && !mock.isErr {
t.Fatalf("%v Test failed unable to set chaos resources, err: %v", name, err)
} else if err == nil && mock.isErr {
t.Fatalf("%v Test failed the expected error should not be nil", name)
}
if !mock.isErr {
expectedResult := experiment.HostPID
actualResult := mock.chaosexperiment.Spec.Definition.HostPID
if expectedResult != actualResult {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
}
})
}
}
func TestSetLabels(t *testing.T) {
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "fake-chaosuid",
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Labels: map[string]string{
"fake-label-key": "fake-label-value",
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetLabels(experimentSpec, &engineDetails)
expectedResult := expDetails.ExpLabels["fake-label-key"]
actualResult := mock.chaosexperiment.Spec.Definition.Labels["fake-label-key"]
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestSetImage(t *testing.T) {
fakeExperimentImage := "fake-exp-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetImage(experimentSpec)
expectedResult := expDetails.ExpImage
actualResult := mock.chaosexperiment.Spec.Definition.Image
if expectedResult != actualResult {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestSetImagePullPolicy(t *testing.T) {
fakeExperimentImage := "fake-exp-image"
fakeExperimentImagePullPolicy := v1.PullPolicy("fake-exp-image-pull-policy")
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
ImagePullPolicy: fakeExperimentImagePullPolicy,
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetImagePullPolicy(experimentSpec)
expectedResult := expDetails.ExpImagePullPolicy
actualResult := mock.chaosexperiment.Spec.Definition.ImagePullPolicy
if expectedResult != actualResult {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestSetArgs(t *testing.T) {
fakeExperimentArgs := "fake-exp-args"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Args: []string{
fakeExperimentArgs,
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetArgs(experimentSpec)
expectedResult := expDetails.ExpArgs
actualResult := mock.chaosexperiment.Spec.Definition.Args
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestSetCommand(t *testing.T) {
fakeExperimentCommand := "fake-exp-command"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Command: []string{
fakeExperimentCommand,
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetCommand(experimentSpec)
expectedResult := expDetails.ExpCommand
actualResult := mock.chaosexperiment.Spec.Definition.Command
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed to set the command from experiment", name)
}
})
}
}
func TestSetSecurityContext(t *testing.T) {
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
SecurityContext: v1alpha1.SecurityContext{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
SecurityContext: v1alpha1.SecurityContext{
PodSecurityContext: v1.PodSecurityContext{
RunAsUser: ptrint64(1000),
},
},
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetSecurityContext(experimentSpec)
expectedResult := expDetails.SecurityContext
actualResult := mock.chaosexperiment.Spec.Definition.SecurityContext
if !reflect.DeepEqual(expectedResult, actualResult) {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestHostPID(t *testing.T) {
fakeHostPID := false
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
HostPID: fakeHostPID,
},
},
},
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSpec, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Get(context.Background(), mock.chaosexperiment.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get the chaosexperiment for %v test, err: %v", name, err)
}
expDetails := experiment.SetHostPID(experimentSpec)
expectedResult := expDetails.HostPID
actualResult := mock.chaosexperiment.Spec.Definition.HostPID
if expectedResult != actualResult {
t.Fatalf("Test %q failed to set the default env from experiment", name)
}
})
}
}
func TestHandleChaosExperimentExistence(t *testing.T) {
fakeHostPID := false
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
envMap: map[string]v1.EnvVar{},
ExpLabels: map[string]string{},
ExpArgs: []string{},
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
UID: "fake-chaosuid",
}
expStatus := ExperimentStatus{
Name: "Fake-Exp-Name",
Status: v1alpha1.ExperimentStatusRunning,
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
chaosengine *v1alpha1.ChaosEngine
isErr bool
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
HostPID: fakeHostPID,
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusInitialized,
Experiments: []v1alpha1.ExperimentStatuses{
{
Name: expStatus.Name,
},
},
},
},
chaosexperiment: nil,
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
} else {
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
}
err := experiment.HandleChaosExperimentExistence(engineDetails, client)
if err != nil && !mock.isErr {
t.Fatalf("%v Test Failed, err: %v", name, err)
} else if err == nil && mock.isErr {
t.Fatalf("%v Test Failed the expected error should not ne nil", name)
}
if mock.isErr {
chaosEngine, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.EngineNamespace).Get(context.Background(), engineDetails.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("%v test failed engine not found, err: %v", name, err)
}
if chaosEngine.Status.Experiments[0].Status != "ChaosExperiment Not Found" {
t.Fatalf("%v test failed experiment status in engine is not updated to not found when no experiment is there, err: %v", name, err)
}
}
})
}
}
func ptrint64(p int64) *int64 {
return &p
}

View File

@ -1,119 +0,0 @@
package utils
import (
log "github.com/sirupsen/logrus"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
//SetValueFromChaosExperiment sets value in experimentDetails struct from chaosExperiment
func (expDetails *ExperimentDetails) SetValueFromChaosExperiment(clients ClientSets) {
expDetails.SetImage(clients)
expDetails.SetArgs(clients)
expDetails.SetLabels(clients)
// Generation of Random String for appending it into Job
randomString := RandomString()
// Setting the JobName in Experiment Realted struct
expDetails.JobName = expDetails.Name + "-" + randomString
}
//SetENV sets ENV values in experimentDetails struct.
func (expDetails *ExperimentDetails) SetENV(engineDetails EngineDetails, clients ClientSets) {
// Get the Default ENV's from ChaosExperiment
log.Infoln("Getting the ENV Variables")
expDetails.SetDefaultEnv(clients)
// OverWriting the Defaults Varibles from the ChaosEngine ENV
expDetails.SetEnvFromEngine(engineDetails.Name, clients)
// Adding some addition ENV's from spec.AppInfo of ChaosEngine
expDetails.Env["CHAOSENGINE"] = engineDetails.Name
expDetails.Env["APP_LABEL"] = engineDetails.AppLabel
expDetails.Env["APP_NAMESPACE"] = engineDetails.AppNamespace
expDetails.Env["APP_KIND"] = engineDetails.AppKind
}
//SetValueFromChaosEngine sets value in experimentDetails struct from chaosEngine
func (expDetails *ExperimentDetails) SetValueFromChaosEngine(engineDetails EngineDetails, i int) {
expDetails.Name = engineDetails.Experiments[i]
expDetails.Namespace = engineDetails.AppNamespace
expDetails.SvcAccount = engineDetails.SvcAccount
}
// NewExperimentDetails initilizes the structure
func NewExperimentDetails() *ExperimentDetails {
var experimentDetails ExperimentDetails
experimentDetails.Env = make(map[string]string)
experimentDetails.ExpLabels = make(map[string]string)
return &experimentDetails
}
// CheckExistence will check the experiment in the app namespace
func (expDetails *ExperimentDetails) CheckExistence(clients ClientSets) (bool, error) {
_, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
if err != nil {
return false, err
}
return true, nil
}
// SetDefaultEnv sets the Env's in Experiment Structure
func (expDetails *ExperimentDetails) SetDefaultEnv(clients ClientSets) {
experimentEnv, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
if err != nil {
log.Infof("Unable to get the Default ENV from ChaosExperiment, error : %v", err)
}
envList := experimentEnv.Spec.Definition.ENVList
expDetails.Env = make(map[string]string)
for i := range envList {
key := envList[i].Name
value := envList[i].Value
expDetails.Env[key] = value
}
}
// SetEnvFromEngine will over-ride the default variables from one provided in the chaosEngine
func (expDetails *ExperimentDetails) SetEnvFromEngine(engineName string, clients ClientSets) {
engineSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(expDetails.Namespace).Get(engineName, metav1.GetOptions{})
if err != nil {
log.Infof("Unable to get the chaosEngine, error : %v", err)
}
envList := engineSpec.Spec.Experiments
for i := range envList {
if envList[i].Name == expDetails.Name {
keyValue := envList[i].Spec.Components
for j := range keyValue {
expDetails.Env[keyValue[j].Name] = keyValue[j].Value
}
}
}
}
// SetLabels sets the Experiment Labels, in Experiment Structure
func (expDetails *ExperimentDetails) SetLabels(clients ClientSets) {
expirementSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
if err != nil {
log.Infoln(err)
}
expDetails.ExpLabels = expirementSpec.Spec.Definition.Labels
}
// SetImage sets the Experiment Image, in Experiment Structure
func (expDetails *ExperimentDetails) SetImage(clients ClientSets) {
expirementSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
if err != nil {
log.Infoln(err)
}
expDetails.ExpImage = expirementSpec.Spec.Definition.Image
}
// SetArgs sets the Experiment Image, in Experiment Structure
func (expDetails *ExperimentDetails) SetArgs(clients ClientSets) {
expirementSpec, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
if err != nil {
log.Infoln(err)
}
expDetails.ExpArgs = expirementSpec.Spec.Definition.Args
}

View File

@ -1,18 +0,0 @@
package utils
import (
"os"
"strings"
)
// GetOsEnv adds the ENV's to EngineDetails
func GetOsEnv(engineDetails *EngineDetails) {
experimentList := os.Getenv("EXPERIMENT_LIST")
engineDetails.Name = os.Getenv("CHAOSENGINE")
engineDetails.AppLabel = os.Getenv("APP_LABEL")
engineDetails.AppNamespace = os.Getenv("APP_NAMESPACE")
engineDetails.AppKind = os.Getenv("APP_KIND")
engineDetails.SvcAccount = os.Getenv("CHAOS_SVC_ACC")
engineDetails.ClientUUID = os.Getenv("CLIENT_UUID")
engineDetails.Experiments = strings.Split(experimentList, ",")
}

View File

@ -0,0 +1,68 @@
package utils
import (
"context"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
)
//NOTE: The hostFileVolumeUtils doesn't contain the function to derive hostFileVols from chaosengine
//and thereby, the corresponding ones to override chaosengine values over experiment.
//This is because, the hostfiles mounted into exp are often for a very specific purpose, such as,
//socket file mounts etc., and are often have fixed paths, i.e., similar to securityContext/hostPID
//and other such mandatory attributes
// PatchHostFileVolumes patches hostFileVolume in experimentDetails struct.
func (expDetails *ExperimentDetails) PatchHostFileVolumes(clients ClientSets, engineDetails EngineDetails) error {
err := expDetails.SetHostFileVolumes(clients, engineDetails)
if err != nil {
return err
}
if len(expDetails.HostFileVolumes) != 0 {
log.Info("Validating HostFileVolumes details specified in the ChaosExperiment")
err = expDetails.ValidateHostFileVolumes()
if err != nil {
return err
}
}
return nil
}
// SetHostFileVolumes sets the value of hostFileVolumes in Experiment Structure
func (expDetails *ExperimentDetails) SetHostFileVolumes(clients ClientSets, engineDetails EngineDetails) error {
experimentHostFileVolumes, err := getHostFileVolumesFromExperiment(clients, expDetails)
if err != nil {
return err
}
expDetails.HostFileVolumes = experimentHostFileVolumes
return nil
}
// ValidateHostFileVolumes validates the hostFileVolume definition in experiment CR spec
func (expDetails *ExperimentDetails) ValidateHostFileVolumes() error {
for _, v := range expDetails.HostFileVolumes {
if v.Name == "" || v.MountPath == "" || v.NodePath == "" {
return errors.Errorf("Incomplete Information in HostFileVolume, will skip execution")
}
log.Infof("Successfully Validated HostFileVolume: %v", v.Name)
}
return nil
}
// getHostFileVolumesFromExperiment obtains the hostFileVolume details from experiment CR spec
func getHostFileVolumesFromExperiment(clients ClientSets, expDetails *ExperimentDetails) ([]v1alpha1.HostFile, error) {
chaosExperimentObj, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(context.Background(), expDetails.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Errorf("unable to get ChaosExperiment Resource, error: %v", err)
}
expHostFileVolumes := chaosExperimentObj.Spec.Definition.HostFileVolumes
return expHostFileVolumes, nil
}

View File

@ -0,0 +1,101 @@
package utils
import (
"context"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
)
func TestPatchHostFileVolumes(t *testing.T) {
fakehostpathname := "fake-hostpath-name"
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
chaosexperiment *v1alpha1.ChaosExperiment
configmap v1.ConfigMap
isErr bool
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
HostFileVolumes: []v1alpha1.HostFile{
{
Name: fakehostpathname,
MountPath: "fake-mount-path",
NodePath: "fake-node-path",
},
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
HostFileVolumes: []v1alpha1.HostFile{
{
Name: "",
MountPath: "",
NodePath: "",
},
},
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", err, name)
}
err = experiment.PatchHostFileVolumes(client, engineDetails)
if !mock.isErr && err != nil {
t.Fatalf("fail to patch the host file volume, err: %v", err)
}
if mock.isErr && err == nil {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
if !mock.isErr {
actualResult := len(experiment.HostFileVolumes)
expectedResult := 1
if actualResult != expectedResult {
t.Fatalf("Test %q failed: expected length of configmap is %v but the actual length is %v", name, expectedResult, actualResult)
}
}
})
}
}

View File

@ -1,28 +1,43 @@
package utils
import (
log "github.com/sirupsen/logrus"
"github.com/litmuschaos/chaos-operator/pkg/apis/litmuschaos/v1alpha1"
"context"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ExperimentStatus is wrapper for v1alpha1.ExperimentStatuses
type ExperimentStatus v1alpha1.ExperimentStatuses
// InitialPatchEngine patches the chaosEngine with the initial ExperimentStatuses
func (expStatus *ExperimentStatus) InitialPatchEngine(engineDetails EngineDetails, clients ClientSets) {
func InitialPatchEngine(engineDetails EngineDetails, clients ClientSets, experimentList []ExperimentDetails) error {
// TODO: check for the status before patching
for range engineDetails.Experiments {
expEngine, err := engineDetails.GetChaosEngine(clients)
if err != nil {
log.Infof("Couldn't Get ChaosEngine: %v, wouldn't be able to update Status in ChaosEngine", err)
}
expEngine.Status.Experiments = append(expEngine.Status.Experiments, v1alpha1.ExperimentStatuses(*expStatus))
//log.Info("Patching Engine")
_, updateErr := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.AppNamespace).Update(expEngine)
if updateErr != nil {
log.Infof("Couldn't Update ChaosEngine: %v, wouldn't be able to update Status in ChaosEngine", updateErr)
}
// Get chaosengine Object
expEngine, err := engineDetails.GetChaosEngine(clients)
if err != nil {
return errors.Errorf("unable to get ChaosEngine, error: %v", err)
}
// patch the experiment status in chaosengine
for _, v := range experimentList {
var expStatus ExperimentStatus
expStatus.InitialExperimentStatus(v.Name, engineDetails.Name)
expEngine.Status.Experiments = append(expEngine.Status.Experiments, v1alpha1.ExperimentStatuses(expStatus))
}
_, updateErr := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.EngineNamespace).Update(context.Background(), expEngine, metav1.UpdateOptions{})
if updateErr != nil {
return errors.Errorf("unable to update ChaosEngine in namespace: %v, error: %v", engineDetails.EngineNamespace, updateErr)
}
return nil
}
// ExperimentSkippedPatchEngine patches the chaosEngine with skipped status
func (engineDetails EngineDetails) ExperimentSkippedPatchEngine(experiment *ExperimentDetails, clients ClientSets) {
var expStatus ExperimentStatus
expStatus.SkippedExperimentStatus(experiment.Name, engineDetails.Name)
if err := expStatus.PatchChaosEngineStatus(engineDetails, clients); err != nil {
log.Errorf("unable to Patch ChaosEngine with Status, error: %v", err)
}
}

View File

@ -1,7 +1,7 @@
package k8s
import (
"fmt"
"github.com/pkg/errors"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
@ -11,7 +11,7 @@ import (
func GenerateK8sClientSet(config *rest.Config) (*kubernetes.Clientset, error) {
k8sClientSet, err := kubernetes.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("unable to generate kubernetes clientSet %s: ", err)
return nil, errors.Errorf("unable to generate kubernetes clientSet, error: %v: ", err)
}
return k8sClientSet, nil
}

View File

@ -1,8 +1,7 @@
package litmus
import (
"fmt"
"github.com/pkg/errors"
"k8s.io/client-go/rest"
clientV1alpha1 "github.com/litmuschaos/chaos-operator/pkg/client/clientset/versioned"
@ -12,7 +11,7 @@ import (
func GenerateLitmusClientSet(config *rest.Config) (*clientV1alpha1.Clientset, error) {
litmusClientSet, err := clientV1alpha1.NewForConfig(config)
if err != nil {
return nil, fmt.Errorf("Unable to create LitmusClientSet: %v", err)
return nil, errors.Errorf("unable to create LitmusClientSet, error: %v", err)
}
return litmusClientSet, nil
}

View File

@ -0,0 +1,23 @@
package utils
import (
"github.com/pkg/errors"
)
// PatchResources function patches chaos Experiment Job with different Kubernetes Resources.
func (expDetails *ExperimentDetails) PatchResources(engineDetails EngineDetails, clients ClientSets) error {
// Patch ConfigMaps to ChaosExperiment Job
if err := expDetails.PatchConfigMaps(clients, engineDetails); err != nil {
return errors.Errorf("unable to patch ConfigMaps to Chaos Experiment, error: %v", err)
}
// Patch Secrets to ChaosExperiment Job
if err := expDetails.PatchSecrets(clients, engineDetails); err != nil {
return errors.Errorf("unable to patch Secrets to Chaos Experiment, error: %v", err)
}
// Patch HostFileVolumes to ChaosExperiment Job
if err := expDetails.PatchHostFileVolumes(clients, engineDetails); err != nil {
return errors.Errorf("unable to patch hostFileVolumes to Chaos Experiment, error: %v", err)
}
return nil
}

View File

@ -1,58 +1,107 @@
package utils
import (
"errors"
log "github.com/sirupsen/logrus"
"context"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
)
// PatchSecrets patches secrets in experimentDetails.
func (expDetails *ExperimentDetails) PatchSecrets(clients ClientSets) error {
log.Infof("Validating secrets specified in the ChaosExperiment")
expDetails.SetSecrets(clients)
err := expDetails.ValidateSecrets(clients)
func (expDetails *ExperimentDetails) PatchSecrets(clients ClientSets, engineDetails EngineDetails) error {
err := expDetails.SetSecrets(clients, engineDetails)
if err != nil {
log.Infof("Error Validating secrets, skipping Execution")
return err
}
if len(expDetails.Secrets) != 0 {
log.Infof("Validating secrets specified in the ChaosExperiment & ChaosEngine")
if err = expDetails.ValidateSecrets(clients); err != nil {
return err
}
}
return nil
}
// ValidateSecrets validates the secrets in application Namespace
func (clientSets ClientSets) ValidateSecrets(secretName string, experiment *ExperimentDetails) error {
_, err := clientSets.KubeClient.CoreV1().Secrets(experiment.Namespace).Get(secretName, metav1.GetOptions{})
if err != nil {
return err
}
return nil
// ValidatePresenceOfSecretResourceInCluster validates the secret in Chaos Namespace
func (clientSets ClientSets) ValidatePresenceOfSecretResourceInCluster(secretName, namespace string) error {
_, err := clientSets.KubeClient.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{})
return err
}
// SetSecrets sets the value of secrets in Experiment Structure
func (expDetails *ExperimentDetails) SetSecrets(clients ClientSets) {
chaosExperimentObj, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(expDetails.Name, metav1.GetOptions{})
func (expDetails *ExperimentDetails) SetSecrets(clients ClientSets, engineDetails EngineDetails) error {
experimentSecrets, err := expDetails.getSecretsFromChaosExperiment(clients)
if err != nil {
log.Infof("Unable to get ChaosEXperiment Resource, wouldn't not be able to patch ConfigMaps")
return err
}
secrets := chaosExperimentObj.Spec.Definition.Secrets
expDetails.Secrets = secrets
engineSecrets, err := expDetails.getSecretsFromChaosEngine(clients, engineDetails)
if err != nil {
return err
}
// Overriding the Secrets from the ChaosEngine
expDetails.getOverridingSecretsFromChaosEngine(experimentSecrets, engineSecrets)
return nil
}
// ValidateSecrets checks for secrets in the Applicaation Namespace
// ValidateSecrets checks for secrets in the Chaos Namespace
func (expDetails *ExperimentDetails) ValidateSecrets(clients ClientSets) error {
for _, v := range expDetails.Secrets {
if v.Name == "" || v.MountPath == "" {
//log.Infof("Incomplete Information in Secret, skipping execution of this ChaosExperiment")
return errors.New("Incomplete Information in Secret, will skip execution")
return errors.Errorf("Incomplete Information in Secret, will skip execution")
}
err := clients.ValidateSecrets(v.Name, expDetails)
err := clients.ValidatePresenceOfSecretResourceInCluster(v.Name, expDetails.Namespace)
if err != nil {
log.Infof("Unable to list Secret: %v, in namespace: %v, skipping execution", v.Name, expDetails.Namespace)
} else {
log.Infof("Succesfully Validated Secret: %v", v.Name)
return errors.Errorf("unable to get Secret with Name: %v, in namespace: %v, error: %v", v.Name, expDetails.Namespace, err)
}
log.Infof("Successfully Validated Secret: %v", v.Name)
}
return nil
}
func (expDetails *ExperimentDetails) getSecretsFromChaosExperiment(clients ClientSets) ([]v1alpha1.Secret, error) {
chaosExperimentObj, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(expDetails.Namespace).Get(context.Background(), expDetails.Name, metav1.GetOptions{})
if err != nil {
return nil, errors.Errorf("unable to get ChaosExperiment Resource, error: %v", err)
}
experimentSecrets := chaosExperimentObj.Spec.Definition.Secrets
return experimentSecrets, nil
}
func (expDetails *ExperimentDetails) getSecretsFromChaosEngine(clients ClientSets, engineDetails EngineDetails) ([]v1alpha1.Secret, error) {
chaosEngineObj, err := engineDetails.GetChaosEngine(clients)
if err != nil {
return nil, errors.Errorf("unable to get ChaosEngine Resource, error: %v", err)
}
experimentsList := chaosEngineObj.Spec.Experiments
for i := range experimentsList {
if experimentsList[i].Name == expDetails.Name {
engineSecrets := experimentsList[i].Spec.Components.Secrets
return engineSecrets, nil
}
}
return nil, errors.Errorf("No experiment found with %v name in ChaosEngine", expDetails.Name)
}
// getOverridingSecretsFromChaosEngine will override secrets from ChaosEngine
func (expDetails *ExperimentDetails) getOverridingSecretsFromChaosEngine(experimentSecrets []v1alpha1.Secret, engineSecrets []v1alpha1.Secret) {
for i := range engineSecrets {
flag := false
for j := range experimentSecrets {
if engineSecrets[i].Name == experimentSecrets[j].Name {
flag = true
if engineSecrets[i].MountPath != experimentSecrets[j].MountPath {
experimentSecrets[j].MountPath = engineSecrets[i].MountPath
}
}
}
if !flag {
experimentSecrets = append(experimentSecrets, engineSecrets[i])
}
}
expDetails.Secrets = experimentSecrets
}

View File

@ -0,0 +1,493 @@
package utils
import (
"context"
"reflect"
"testing"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
)
func TestPatchSecrets(t *testing.T) {
fakeSecretName := "fake secret"
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
chaosexperiment *v1alpha1.ChaosExperiment
secret v1.Secret
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
secret: v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fakeSecretName,
Namespace: experiment.Namespace,
},
StringData: map[string]string{
"my-fake-key": "myfake-val",
}},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.KubeClient.CoreV1().Secrets(experiment.Namespace).Create(context.Background(), &mock.secret, metav1.CreateOptions{})
if err != nil {
t.Fatalf("secret not created for %v test, err: %v", name, err)
}
_, err = client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
_, err = client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
err = experiment.PatchSecrets(client, engineDetails)
if !mock.isErr && err != nil {
t.Fatalf("fail to patch the secret, err: %v", err)
}
if mock.isErr && err == nil {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
if !mock.isErr {
actualResult := len(experiment.Secrets)
expectedResult := 1
if actualResult != expectedResult {
t.Fatalf("Test %q failed: expected length of secret is %v but the actual length is %v", name, expectedResult, actualResult)
}
}
})
}
}
func TestSetSecrets(t *testing.T) {
fakeSecretName := "fake secret"
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
chaosengine *v1alpha1.ChaosEngine
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake mountpath",
},
},
},
},
},
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
if !mock.isErr {
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
}
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
err = experiment.SetSecrets(client, engineDetails)
if (!mock.isErr && err != nil) || (mock.isErr && err == nil) {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
actualResult := experiment.Secrets
expectedResult := mock.chaosengine.Spec.Experiments[0].Spec.Components.Secrets
if !reflect.DeepEqual(expectedResult, actualResult) && !mock.isErr {
t.Fatalf("%v Test Failed the expectedResult '%v' is not equal to actual result '%v'", name, expectedResult, actualResult)
}
})
}
}
func TestValidateSecrets(t *testing.T) {
fakeSecretName := "fake secret"
fakeNamespace := "fake-namespace"
tests := map[string]struct {
secret v1.Secret
experiment ExperimentDetails
isErr bool
}{
"Test Positive-1": {
secret: v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fakeSecretName,
Namespace: fakeNamespace,
},
StringData: map[string]string{
"my-fake-key": "myfake-val",
}},
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-job-name",
StatusCheckTimeout: 10,
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake mountpath",
},
},
},
isErr: false,
},
"Test Negative-1": {
secret: v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fakeSecretName,
Namespace: fakeNamespace,
},
StringData: map[string]string{
"my-fake-key": "myfake-val",
}},
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-job-name",
StatusCheckTimeout: 10,
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
},
},
},
isErr: true,
},
"Test Negative-2": {
secret: v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: fakeSecretName,
Namespace: fakeNamespace,
},
StringData: map[string]string{
"my-fake-key": "myfake-val",
}},
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-job-name",
StatusCheckTimeout: 10,
Secrets: []v1alpha1.Secret{
{
MountPath: "fake mountpath",
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.KubeClient.CoreV1().Secrets(fakeNamespace).Create(context.Background(), &mock.secret, metav1.CreateOptions{})
if err != nil {
t.Fatalf("secret not created for %v test, err: %v", name, err)
}
err = mock.experiment.ValidateSecrets(client)
if (!mock.isErr && err != nil) || (mock.isErr && err == nil) {
t.Fatalf("Validation for presence of secret failed for %v test, err: %v", name, err)
}
})
}
}
func TestGetSecretsFromChaosExperiment(t *testing.T) {
fakeSecretName := "fake secret"
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
tests := map[string]struct {
chaosexperiment *v1alpha1.ChaosExperiment
isErr bool
}{
"Test Positive-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
Secrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake mountpath",
},
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosexperiment: &v1alpha1.ChaosExperiment{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.Name,
Namespace: experiment.Namespace,
},
Spec: v1alpha1.ChaosExperimentSpec{
Definition: v1alpha1.ExperimentDef{
Image: fakeExperimentImage,
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosExperiments(mock.chaosexperiment.Namespace).Create(context.Background(), mock.chaosexperiment, metav1.CreateOptions{})
if err != nil {
t.Fatalf("experiment not created for %v test, err: %v", name, err)
}
experimentSecrets, err := experiment.getSecretsFromChaosExperiment(client)
if err != nil && !mock.isErr {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
if !mock.isErr {
if experimentSecrets[0].Name != fakeSecretName {
t.Fatalf("Test %q failed to get the secret from experiment: ", name)
}
}
})
}
}
func TestGetOverridingSecretsFromChaosEngine(t *testing.T) {
fakeSecretName := "fake-experiment-image"
tests := map[string]struct {
experiment ExperimentDetails
engineSecrets []v1alpha1.Secret
isErr bool
}{
"Test Positive-1": {
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
},
engineSecrets: []v1alpha1.Secret{
{
Name: fakeSecretName,
MountPath: "fake-mount-path",
},
},
isErr: false,
},
"Test Negative-1": {
experiment: ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
},
engineSecrets: []v1alpha1.Secret{},
isErr: false,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
var err error
mock.experiment.getOverridingSecretsFromChaosEngine(mock.engineSecrets, mock.engineSecrets)
if err != nil {
t.Fatalf("%v Test Failed, err: %v", name, err)
}
actualResult := mock.engineSecrets
expectedResult := mock.experiment.Secrets
if !reflect.DeepEqual(expectedResult, actualResult) && !mock.isErr {
t.Fatalf("Test %q failed: expected secret is %v but the we get is '%v' from the experiment", name, expectedResult, actualResult)
} else if !reflect.DeepEqual(expectedResult, actualResult) && mock.isErr {
t.Fatalf("Test %q failed: expected secret is %v and the we get is '%v' from the experiment", name, expectedResult, actualResult)
}
})
}
}

View File

@ -1,40 +1,57 @@
package utils
import (
"github.com/litmuschaos/chaos-operator/pkg/apis/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// IntialExperimentStatus fills up ExperimentStatus Structure with intialValues
func (expStatus *ExperimentStatus) IntialExperimentStatus(experimentDetails *ExperimentDetails) {
expStatus.Name = experimentDetails.JobName
expStatus.Status = "Waiting for Job Creation"
expStatus.Verdict = "Waiting"
// InitialExperimentStatus fills up ExperimentStatus Structure with InitialValues
func (expStatus *ExperimentStatus) InitialExperimentStatus(expName, engineName string) {
expStatus.Name = expName
expStatus.Runner = engineName + "-runner"
expStatus.ExpPod = "Yet to be launched"
expStatus.Status = v1alpha1.ExperimentStatusWaiting
expStatus.Verdict = "N/A"
expStatus.LastUpdateTime = metav1.Now()
}
// AwaitedExperimentStatus fills up ExperimentStatus Structure with Running Status
func (expStatus *ExperimentStatus) AwaitedExperimentStatus(experimentDetails *ExperimentDetails) {
expStatus.Name = experimentDetails.JobName
expStatus.Status = "Running"
func (expStatus *ExperimentStatus) AwaitedExperimentStatus(expName, engineName, experimentPodName string) {
expStatus.Name = expName
expStatus.Runner = engineName + "-runner"
expStatus.ExpPod = experimentPodName
expStatus.Status = v1alpha1.ExperimentStatusRunning
expStatus.Verdict = "Awaited"
expStatus.LastUpdateTime = metav1.Now()
}
// CompletedExperimentStatus fills up ExperimentStatus Structure with values chaosResult
func (expStatus *ExperimentStatus) CompletedExperimentStatus(chaosResult *v1alpha1.ChaosResult, experimentDetails *ExperimentDetails) {
func (expStatus *ExperimentStatus) CompletedExperimentStatus(chaosResult *v1alpha1.ChaosResult, engineName, experimentPodName string) {
//var currExpStatus v1alpha1.ExperimentStatuses
expStatus.Name = experimentDetails.JobName
expStatus.Status = "Execution Successful"
expStatus.Name = chaosResult.Spec.ExperimentName
expStatus.Runner = engineName + "-runner"
expStatus.ExpPod = experimentPodName
expStatus.Status = v1alpha1.ExperimentStatusCompleted
expStatus.LastUpdateTime = metav1.Now()
expStatus.Verdict = chaosResult.Spec.ExperimentStatus.Verdict
//return currExpStatus
expStatus.Verdict = string(chaosResult.Status.ExperimentStatus.Verdict)
}
// NotFoundExperimentStatus initilize experiment struct using the following values.
func (expStatus *ExperimentStatus) NotFoundExperimentStatus(experimentDetails *ExperimentDetails) {
expStatus.Name = experimentDetails.JobName
expStatus.Status = "ChaosExperiment Not Found"
expStatus.Verdict = "Failed"
func (expStatus *ExperimentStatus) NotFoundExperimentStatus(expName, engineName string) {
expStatus.Name = expName
expStatus.Runner = engineName + "-runner"
expStatus.ExpPod = "N/A"
expStatus.Status = v1alpha1.ExperimentStatusNotFound
expStatus.Verdict = "Fail"
expStatus.LastUpdateTime = metav1.Now()
}
// SkippedExperimentStatus fills up ExperimentStatus Structure with skipped value
func (expStatus *ExperimentStatus) SkippedExperimentStatus(expName, engineName string) {
expStatus.Name = expName
expStatus.Runner = engineName + "-runner"
expStatus.ExpPod = "N/A"
expStatus.Status = v1alpha1.ExperimentSkipped
expStatus.Verdict = "Fail"
expStatus.LastUpdateTime = metav1.Now()
}

View File

@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@ -0,0 +1,2 @@
go test fuzz v1
[]byte("0")

View File

@ -1,57 +1,126 @@
package utils
import (
"github.com/litmuschaos/chaos-operator/pkg/apis/litmuschaos/v1alpha1"
"flag"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
clientV1alpha1 "github.com/litmuschaos/chaos-operator/pkg/client/clientset/versioned"
volume "github.com/litmuschaos/kube-helper/kubernetes/volume/v1alpha1"
corev1 "k8s.io/api/core/v1"
volume "github.com/litmuschaos/elves/kubernetes/volume/v1alpha1"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/litmuschaos/chaos-executor/pkg/utils/k8s"
"github.com/litmuschaos/chaos-executor/pkg/utils/litmus"
"github.com/litmuschaos/chaos-runner/pkg/utils/k8s"
"github.com/litmuschaos/chaos-runner/pkg/utils/litmus"
)
// EngineDetails struct is for collecting all the engine-related details
type EngineDetails struct {
Name string
Experiments []string
AppLabel string
SvcAccount string
AppKind string
AppNamespace string
ClientUUID string
Name string
Experiments []string
Targets string
SvcAccount string
ClientUUID string
AuxiliaryAppInfo string
UID string
EngineNamespace string
}
// ExperimentDetails is for collecting all the experiment-related details
type ExperimentDetails struct {
Name string
Env map[string]string
ExpLabels map[string]string
ExpImage string
ExpArgs []string
JobName string
Namespace string
ConfigMaps []v1alpha1.ConfigMap
Secrets []v1alpha1.Secret
VolumeOpts VolumeOpts
SvcAccount string
Name string
envMap map[string]v1.EnvVar
ExpLabels map[string]string
ExpImage string
ExpImagePullPolicy v1.PullPolicy
ExpArgs []string
ExpCommand []string
JobName string
Namespace string
ConfigMaps []v1alpha1.ConfigMap
Secrets []v1alpha1.Secret
HostFileVolumes []v1alpha1.HostFile
VolumeOpts VolumeOpts
SvcAccount string
Annotations map[string]string
NodeSelector map[string]string
Tolerations []v1.Toleration
SecurityContext v1alpha1.SecurityContext
HostPID bool
// InstanceID is passed as env inside chaosengine
// It is separately specified here because this attribute is common for all experiment.
InstanceID string
ResourceRequirements v1.ResourceRequirements
ImagePullSecrets []v1.LocalObjectReference
StatusCheckTimeout int
TerminationGracePeriodSeconds int64
DefaultHealthCheck string
SideCars []SideCar
}
//VolumeOpts is a strcuture for all volume related operations
type SideCar struct {
ENV []v1.EnvVar
Image string
ImagePullPolicy v1.PullPolicy
Secrets []v1alpha1.Secret
EnvFrom []v1.EnvFromSource
}
// VolumeOpts is a strcuture for all volume related operations
type VolumeOpts struct {
VolumeMounts []corev1.VolumeMount
VolumeMounts []v1.VolumeMount
VolumeBuilders []*volume.Builder
}
// ClientSets is a collection of clientSets needed
type ClientSets struct {
KubeClient *kubernetes.Clientset
LitmusClient *clientV1alpha1.Clientset
KubeClient kubernetes.Interface
LitmusClient clientV1alpha1.Interface
}
// GenerateClientSets will generation both ClientSets (k8s, and Litmus)
func (clientSets *ClientSets) GenerateClientSets(config *rest.Config) error {
// EventAttributes is for collecting all the events-related details
type EventAttributes struct {
Reason string
Message string
Type string
Name string
}
var (
// DefaultExpImagePullPolicy contains the defaults value (Always) of imagePullPolicy for exp container
DefaultExpImagePullPolicy v1.PullPolicy = "Always"
)
const (
// ExperimentDependencyCheckReason contains the reason for the dependency check event
ExperimentDependencyCheckReason string = "ExperimentDependencyCheck"
// ExperimentJobCreateReason contains the reason for the job creation event
ExperimentJobCreateReason string = "ExperimentJobCreate"
// ExperimentJobCleanUpReason contains the reason for the job cleanup event
ExperimentJobCleanUpReason string = "ExperimentJobCleanUp"
// ExperimentSkippedReason contains the reason for the experiment skip event
ExperimentSkippedReason string = "ExperimentSkipped"
// ExperimentEnvParseErrorReason contains the reason for the env-parse-error event
ExperimentEnvParseErrorReason string = "EnvParseError"
// ExperimentNotFoundErrorReason contains the reason for the experiment-not-found event
ExperimentNotFoundErrorReason string = "ExperimentNotFound"
// ExperimentJobCreationErrorReason contains the reason for the job-creation-error event
ExperimentJobCreationErrorReason string = "JobCreationError"
// ExperimentChaosContainerWatchErrorReason contains the reason for the watch-job-error event
ExperimentChaosContainerWatchErrorReason string = "ChaosContainerWatchNotPermitted"
// ChaosResourceNotFoundReason contains the reason for the chaos-resources-not-found event
ChaosResourceNotFoundReason string = "ChaosResourceNotFound"
// ExperimentSideCarPatchErrorReason contains the reason for the side-car-patch-error event
ExperimentSideCarPatchErrorReason string = "SideCarPatchError"
)
// GenerateClientSetFromKubeConfig will generation both ClientSets (k8s, and Litmus)
func (clientSets *ClientSets) GenerateClientSetFromKubeConfig() error {
config, err := getKubeConfig()
if err != nil {
return err
}
k8sClientSet, err := k8s.GenerateK8sClientSet(config)
if err != nil {
return err
@ -64,5 +133,12 @@ func (clientSets *ClientSets) GenerateClientSets(config *rest.Config) error {
clientSets.LitmusClient = litmusClientSet
return nil
}
// getKubeConfig setup the config for access cluster resource
func getKubeConfig() (*rest.Config, error) {
kubeconfig := flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
flag.Parse()
// Use in-cluster config if kubeconfig path is specified
return clientcmd.BuildConfigFromFlags("", *kubeconfig)
}

View File

@ -1,92 +1,142 @@
package utils
import (
"github.com/litmuschaos/chaos-operator/pkg/apis/litmuschaos/v1alpha1"
volume "github.com/litmuschaos/kube-helper/kubernetes/volume/v1alpha1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
volume "github.com/litmuschaos/elves/kubernetes/volume/v1alpha1"
corev1 "k8s.io/api/core/v1"
)
// CreateVolumeBuilder build Volume needed in execution of experiments
func CreateVolumeBuilder(configMaps []v1alpha1.ConfigMap, secrets []v1alpha1.Secret) []*volume.Builder {
volumeBuilderList := []*volume.Builder{}
volumeBuilderForConfigMaps := BuildVolumeBuilderForConfigMaps(configMaps)
volumeBuilderList = append(volumeBuilderList, volumeBuilderForConfigMaps...)
volumeBuilderForSecrets := BuildVolumeBuilderForSecrets(secrets)
volumeBuilderList = append(volumeBuilderList, volumeBuilderForSecrets...)
return volumeBuilderList
}
// CreateVolumeMounts mounts Volume needed in execution of experiments
func CreateVolumeMounts(configMaps []v1alpha1.ConfigMap, secrets []v1alpha1.Secret) []corev1.VolumeMount {
var volumeMountsList []corev1.VolumeMount
volumeMountsListForConfigMaps := BuildVolumeMountsForConfigMaps(configMaps)
volumeMountsList = append(volumeMountsList, volumeMountsListForConfigMaps...)
volumeMountsListForSecrets := BuildVolumeMountsForSecrets(secrets)
volumeMountsList = append(volumeMountsList, volumeMountsListForSecrets...)
return volumeMountsList
}
var (
// hostpathTypeFile represents the hostpath type
hostpathTypeFile = corev1.HostPathFile
)
// VolumeOperations filles up VolumeOpts strucuture
func (volumeOpts *VolumeOpts) VolumeOperations(configMaps []v1alpha1.ConfigMap, secrets []v1alpha1.Secret) {
volumeOpts.VolumeBuilders = CreateVolumeBuilder(configMaps, secrets)
volumeOpts.VolumeMounts = CreateVolumeMounts(configMaps, secrets)
func (volumeOpts *VolumeOpts) VolumeOperations(experiment *ExperimentDetails) {
volumeOpts.NewVolumeBuilder().
BuildVolumeBuilderForConfigMaps(experiment.ConfigMaps).
BuildVolumeBuilderForSecrets(experiment.Secrets).
BuildVolumeBuilderForHostFileVolumes(experiment.HostFileVolumes)
volumeOpts.NewVolumeMounts().
BuildVolumeMountsForConfigMaps(experiment.ConfigMaps).
BuildVolumeMountsForSecrets(experiment.Secrets).
BuildVolumeMountsForHostFileVolumes(experiment.HostFileVolumes)
}
// NewVolumeMounts initialize the volume builder
func (volumeOpts *VolumeOpts) NewVolumeMounts() *VolumeOpts {
var volumeMountsList []corev1.VolumeMount
volumeOpts.VolumeMounts = volumeMountsList
return volumeOpts
}
// NewVolumeBuilder initialize the volume builder
func (volumeOpts *VolumeOpts) NewVolumeBuilder() *VolumeOpts {
volumeBuilderList := []*volume.Builder{}
volumeOpts.VolumeBuilders = volumeBuilderList
return volumeOpts
}
// BuildVolumeMountsForConfigMaps builds VolumeMounts for ConfigMaps
func BuildVolumeMountsForConfigMaps(configMaps []v1alpha1.ConfigMap) []corev1.VolumeMount {
func (volumeOpts *VolumeOpts) BuildVolumeMountsForConfigMaps(configMaps []v1alpha1.ConfigMap) *VolumeOpts {
var volumeMountsList []corev1.VolumeMount
if configMaps == nil {
return volumeOpts
}
for _, v := range configMaps {
var volumeMount corev1.VolumeMount
volumeMount.Name = v.Name
volumeMount.MountPath = v.MountPath
volumeMountsList = append(volumeMountsList, volumeMount)
}
return volumeMountsList
volumeOpts.VolumeMounts = append(volumeOpts.VolumeMounts, volumeMountsList...)
return volumeOpts
}
// BuildVolumeMountsForSecrets builds VolumeMounts for Secrets
func BuildVolumeMountsForSecrets(secrets []v1alpha1.Secret) []corev1.VolumeMount {
func (volumeOpts *VolumeOpts) BuildVolumeMountsForSecrets(secrets []v1alpha1.Secret) *VolumeOpts {
var volumeMountsList []corev1.VolumeMount
if secrets == nil {
return volumeOpts
}
for _, v := range secrets {
var volumeMount corev1.VolumeMount
volumeMount.Name = v.Name
volumeMount.MountPath = v.MountPath
volumeMountsList = append(volumeMountsList, volumeMount)
}
return volumeMountsList
volumeOpts.VolumeMounts = append(volumeOpts.VolumeMounts, volumeMountsList...)
return volumeOpts
}
// BuildVolumeMountsForHostFileVolumes builds VolumeMounts for HostFileVolume
func (volumeOpts *VolumeOpts) BuildVolumeMountsForHostFileVolumes(hostFileVolumes []v1alpha1.HostFile) *VolumeOpts {
var volumeMountsList []corev1.VolumeMount
if hostFileVolumes == nil {
return volumeOpts
}
for _, v := range hostFileVolumes {
var volumeMount corev1.VolumeMount
volumeMount.Name = v.Name
volumeMount.MountPath = v.MountPath
volumeMountsList = append(volumeMountsList, volumeMount)
}
volumeOpts.VolumeMounts = append(volumeOpts.VolumeMounts, volumeMountsList...)
return volumeOpts
}
// BuildVolumeBuilderForConfigMaps builds VolumeBuilders for ConfigMaps
func BuildVolumeBuilderForConfigMaps(configMaps []v1alpha1.ConfigMap) []*volume.Builder {
func (volumeOpts *VolumeOpts) BuildVolumeBuilderForConfigMaps(configMaps []v1alpha1.ConfigMap) *VolumeOpts {
volumeBuilderList := []*volume.Builder{}
if configMaps == nil {
return nil
return volumeOpts
}
for _, v := range configMaps {
volumeBuilder := volume.NewBuilder().
WithConfigMap(v.Name)
volumeBuilderList = append(volumeBuilderList, volumeBuilder)
}
return volumeBuilderList
volumeOpts.VolumeBuilders = append(volumeOpts.VolumeBuilders, volumeBuilderList...)
return volumeOpts
}
// BuildVolumeBuilderForSecrets builds VolumeBuilders for Secrets
func BuildVolumeBuilderForSecrets(secrets []v1alpha1.Secret) []*volume.Builder {
func (volumeOpts *VolumeOpts) BuildVolumeBuilderForSecrets(secrets []v1alpha1.Secret) *VolumeOpts {
volumeBuilderList := []*volume.Builder{}
if secrets == nil {
return nil
return volumeOpts
}
for _, v := range secrets {
volumeBuilder := volume.NewBuilder().
WithSecret(v.Name)
volumeBuilderList = append(volumeBuilderList, volumeBuilder)
}
return volumeBuilderList
volumeOpts.VolumeBuilders = append(volumeOpts.VolumeBuilders, volumeBuilderList...)
return volumeOpts
}
// BuildVolumeBuilderForHostFileVolumes builds VolumeBuilders for HostFileVolume
func (volumeOpts *VolumeOpts) BuildVolumeBuilderForHostFileVolumes(hostFileVolumes []v1alpha1.HostFile) *VolumeOpts {
volumeBuilderList := []*volume.Builder{}
if hostFileVolumes == nil {
return volumeOpts
}
for _, v := range hostFileVolumes {
fileType := &hostpathTypeFile
if v.Type != "" {
fileType = &v.Type
}
volumeBuilder := volume.NewBuilder().
WithName(v.Name).
WithHostPathAndType(
v.NodePath,
fileType,
)
volumeBuilderList = append(volumeBuilderList, volumeBuilder)
}
volumeOpts.VolumeBuilders = append(volumeOpts.VolumeBuilders, volumeBuilderList...)
return volumeOpts
}

View File

@ -0,0 +1,123 @@
package utils
import (
"context"
"strings"
"time"
"github.com/litmuschaos/litmus-go/pkg/utils/retry"
"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// GetChaosPod gets the chaos experiment pod object launched by the runner
func GetChaosPod(expDetails *ExperimentDetails, clients ClientSets) (*corev1.Pod, error) {
var chaosPodList *corev1.PodList
var err error
delay := 2
err = retry.
Times(uint(expDetails.StatusCheckTimeout / delay)).
Wait(time.Duration(delay) * time.Second).
Try(func(attempt uint) error {
chaosPodList, err = clients.KubeClient.CoreV1().Pods(expDetails.Namespace).List(context.Background(), metav1.ListOptions{LabelSelector: "job-name=" + expDetails.JobName})
if err != nil || len(chaosPodList.Items) == 0 {
return errors.Errorf("unable to get the chaos pod, error: %v", err)
} else if len(chaosPodList.Items) > 1 {
// Cases where experiment pod is rescheduled by the job controller due to
// issues while the older pod is still not cleaned-up
return errors.Errorf("Multiple pods exist with same job-name label")
}
return nil
})
if err != nil {
return nil, err
}
// Note: We error out upon existence of multiple exp pods for the same experiment
// & hence use index [0]
chaosPod := &chaosPodList.Items[0]
return chaosPod, nil
}
// GetChaosContainerStatus gets status of the chaos container
func GetChaosContainerStatus(experimentDetails *ExperimentDetails, clients ClientSets) (bool, error) {
isCompleted := false
pod, err := GetChaosPod(experimentDetails, clients)
if err != nil {
return false, errors.Errorf("unable to get the chaos pod, error: %v", err)
}
if pod.Status.Phase == corev1.PodSucceeded {
return true, nil
}
if pod.Status.Phase == corev1.PodRunning {
for _, container := range pod.Status.ContainerStatuses {
//NOTE: The name of container inside chaos-pod is same as the chaos job name
// we only have one container inside chaos pod to inject the chaos
// looking the chaos container is completed or not
if strings.Contains(container.Name, experimentDetails.JobName) && container.State.Terminated == nil {
return false, nil
}
}
return true, nil
} else if pod.Status.Phase == corev1.PodPending {
delay := 2
err := retry.
Times(uint(experimentDetails.StatusCheckTimeout / delay)).
Wait(time.Duration(delay) * time.Second).
Try(func(attempt uint) error {
pod, err := GetChaosPod(experimentDetails, clients)
if err != nil {
return errors.Errorf("unable to get the chaos pod, error: %v", err)
}
if pod.Status.Phase == corev1.PodPending {
return errors.Errorf("chaos pod is in %v state", corev1.PodPending)
}
return nil
})
if err != nil {
return isCompleted, err
}
} else if pod.Status.Phase == corev1.PodFailed {
return isCompleted, errors.Errorf("status check failed as chaos pod status is %v", pod.Status.Phase)
}
return isCompleted, nil
}
// WatchChaosContainerForCompletion watches the chaos container for completion
func (engineDetails EngineDetails) WatchChaosContainerForCompletion(experiment *ExperimentDetails, clients ClientSets) error {
//TODO: use watch rather than checking for status manually.
isChaosCompleted := false
var err error
for !isChaosCompleted {
isChaosCompleted, err = GetChaosContainerStatus(experiment, clients)
if err != nil {
return err
}
if isChaosCompleted {
return nil
}
var expStatus ExperimentStatus
chaosPod, err := GetChaosPod(experiment, clients)
if err != nil {
return errors.Errorf("unable to get the chaos pod, error: %v", err)
}
expStatus.AwaitedExperimentStatus(experiment.Name, engineDetails.Name, chaosPod.Name)
if err := expStatus.PatchChaosEngineStatus(engineDetails, clients); err != nil {
return errors.Errorf("unable to patch ChaosEngine in namespace: %v, error: %v", engineDetails.EngineNamespace, err)
}
time.Sleep(2 * time.Second)
}
return nil
}

View File

@ -0,0 +1,412 @@
package utils
import (
"context"
"testing"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestGetChaosPod(t *testing.T) {
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
tests := map[string]struct {
chaospod v1.Pod
chaospod2 v1.Pod
isErr bool
isSecondTest bool
}{
"Test Positive-1": {
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-container",
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": "wrong-job-name",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-container",
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
isErr: true,
},
"Test Negative-2": {
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-container",
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
chaospod2: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod-2",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "fake-container",
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
isSecondTest: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.KubeClient.CoreV1().Pods(experiment.Namespace).Create(context.Background(), &mock.chaospod, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create chaos pod for %v test, err: %v", name, err)
}
if mock.isSecondTest {
_, err = client.KubeClient.CoreV1().Pods(experiment.Namespace).Create(context.Background(), &mock.chaospod2, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create chaos pod 2 for %v test, err: %v", name, err)
}
}
_, err = GetChaosPod(&experiment, client)
if err != nil && !mock.isErr && !mock.isSecondTest {
t.Fatalf("%v test failed, fail to get the chaos pod, err: %v", name, err)
} else if err == nil && mock.isErr && mock.isSecondTest {
t.Fatalf("%v test failed, the err should not be nil", name)
}
})
}
}
func TestGetChaosContainerStatus(t *testing.T) {
fakeExperimentImage := "fake-experiment-image"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
tests := map[string]struct {
chaospod v1.Pod
isErr bool
}{
"Test Positive-1": {
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: experiment.JobName,
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "wrong-container-name",
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
isErr: true,
},
"Test Negative-2": {
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: experiment.JobName,
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
var ns string
if !mock.isErr {
ns = experiment.Namespace
} else {
ns = "wrong-ns"
}
_, err := client.KubeClient.CoreV1().Pods(ns).Create(context.Background(), &mock.chaospod, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create chaos pod for %v test, err: %v", name, err)
}
_, err = GetChaosContainerStatus(&experiment, client)
if err != nil && !mock.isErr {
t.Fatalf("%v test failed, fail to get the chaos pod, err: %v", name, err)
} else if err == nil && mock.isErr {
t.Fatalf("%v test failed, the err should not be nil", name)
}
})
}
}
func TestWatchChaosContainerForCompletion(t *testing.T) {
fakeExperimentImage := "fake-experiment-image"
fakeNamespace := "Fake NameSpace"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: fakeNamespace,
JobName: "fake-jobs-name-12345",
StatusCheckTimeout: 2,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: fakeNamespace,
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
chaospod v1.Pod
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{},
},
},
},
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
Experiments: []v1alpha1.ExperimentStatuses{
{
Name: experiment.Name,
},
},
},
},
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: experiment.JobName,
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
Status: v1.PodStatus{
Phase: v1.PodSucceeded,
ContainerStatuses: []v1.ContainerStatus{
{
Name: experiment.JobName,
State: v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
Reason: "Completed",
},
},
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
Experiments: []v1alpha1.ExperimentList{
{
Name: experiment.Name,
Spec: v1alpha1.ExperimentAttributes{
Components: v1alpha1.ExperimentComponents{},
},
},
},
},
},
chaospod: v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "fake-chaos-pod",
Labels: map[string]string{
"app": "myapp",
"job-name": experiment.JobName,
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: experiment.JobName,
Image: fakeExperimentImage,
Resources: v1.ResourceRequirements{},
},
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(fakeNamespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
_, err = client.KubeClient.CoreV1().Pods(fakeNamespace).Create(context.Background(), &mock.chaospod, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create chaos pod for %v test, err: %v", name, err)
}
err = engineDetails.WatchChaosContainerForCompletion(&experiment, client)
if err != nil && !mock.isErr {
t.Fatalf("%v failed, err: %v", name, err)
} else if err == nil && mock.isErr {
t.Fatalf("%v failed, the err should be nil", name)
}
})
}
}

View File

@ -1,49 +1,48 @@
package utils
import (
"context"
"fmt"
"time"
"github.com/litmuschaos/chaos-operator/pkg/apis/litmuschaos/v1alpha1"
log "github.com/sirupsen/logrus"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
"github.com/litmuschaos/litmus-go/pkg/utils/retry"
)
var err error
// checkStatusListForExp loops over all the status patched in chaosEngine, to get the one, which has to be updated
// Can go with updated the last status(status[n-1])
// But would'nt work for the pararllel execution
func checkStatusListForExp(status []v1alpha1.ExperimentStatuses, jobName string) int {
// But would'nt work for the parallel execution
func checkStatusListForExp(status []v1alpha1.ExperimentStatuses, ExperimentName string) int {
for i := range status {
if status[i].Name == jobName {
if status[i].Name == ExperimentName {
return i
}
}
return -1
}
// GetJobStatus gets status of the job
func GetJobStatus(experimentDetails *ExperimentDetails, clients ClientSets) int32 {
getJob, err := clients.KubeClient.BatchV1().Jobs(experimentDetails.Namespace).Get(experimentDetails.JobName, metav1.GetOptions{})
if err != nil {
log.Infoln("Unable to get the job : ", err)
//TODO: check for jobStatus should not return -1 directly, look for best practices.
return -1
}
//TODO:check the container of the Job, rather than going with the JobStatus.
jobStatus := getJob.Status.Active
log.Infof("Watching Job: %v, and Updating Status", experimentDetails.JobName)
return jobStatus
}
// GetChaosEngine returns chaosEngine Object
func (engineDetails EngineDetails) GetChaosEngine(clients ClientSets) (*v1alpha1.ChaosEngine, error) {
expEngine, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.AppNamespace).Get(engineDetails.Name, metav1.GetOptions{})
if err != nil {
log.Infof("Unable to get chaosEngine Name: %v, in NameSpace: %v", engineDetails.Name, engineDetails.AppNamespace)
var engine *v1alpha1.ChaosEngine
if err := retry.
Times(uint(180)).
Wait(time.Duration(2)).
Try(func(attempt uint) error {
engine, err = clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.EngineNamespace).Get(context.Background(), engineDetails.Name, metav1.GetOptions{})
if err != nil {
return errors.Errorf("unable to get ChaosEngine name: %v, in namespace: %v, error: %v", engineDetails.Name, engineDetails.EngineNamespace, err)
}
return nil
}); err != nil {
return nil, err
}
return expEngine, nil
return engine, nil
}
// PatchChaosEngineStatus updates ChaosEngine with Experiment Status
@ -53,61 +52,39 @@ func (expStatus *ExperimentStatus) PatchChaosEngineStatus(engineDetails EngineDe
if err != nil {
return err
}
jobIndex := checkStatusListForExp(expEngine.Status.Experiments, expStatus.Name)
if jobIndex == -1 {
return fmt.Errorf("Unable to find the status for JobName: %v in ChaosEngine: %v", expStatus.Name, expEngine.Name)
experimentIndex := checkStatusListForExp(expEngine.Status.Experiments, expStatus.Name)
if experimentIndex == -1 {
return errors.Errorf("unable to find the status for Experiment: %v in ChaosEngine: %v", expStatus.Name, expEngine.Name)
}
expEngine.Status.Experiments[jobIndex] = v1alpha1.ExperimentStatuses(*expStatus)
if _, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.AppNamespace).Update(expEngine); err != nil {
expEngine.Status.Experiments[experimentIndex] = v1alpha1.ExperimentStatuses(*expStatus)
if _, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(engineDetails.EngineNamespace).Update(context.Background(), expEngine, metav1.UpdateOptions{}); err != nil {
return err
}
return nil
}
// WatchJobForCompletion watches the chaosExperiment job for completions
func (engineDetails EngineDetails) WatchJobForCompletion(experiment *ExperimentDetails, clients ClientSets) error {
//TODO: use watch rather than checking for status manually.
jobStatus := GetJobStatus(experiment, clients)
if jobStatus == -1 {
return fmt.Errorf("Unable to get the chaosExperiment Job Status")
}
for jobStatus == 1 {
//checkForjobName := checkStatusListForExp(expEngine.Status.Experiments, experiment.JobName)
var expStatus ExperimentStatus
expStatus.AwaitedExperimentStatus(experiment)
if err := expStatus.PatchChaosEngineStatus(engineDetails, clients); err != nil {
log.Infof("Unable to patch ChaosEngine")
return err
}
time.Sleep(5 * time.Second)
jobStatus = GetJobStatus(experiment, clients)
}
return nil
}
// GetResultName returns the resultName using the experimentName and engine Name
func GetResultName(engineName string, experimentName string) string {
func GetResultName(engineName, experimentName, instanceID string) string {
resultName := engineName + "-" + experimentName
log.Info("ResultName : " + resultName)
if instanceID != "" {
resultName = resultName + "-" + instanceID
}
return resultName
}
// GetChaosResult returns ChaosResult object.
func (experimentDetails *ExperimentDetails) GetChaosResult(engineDetails EngineDetails, clients ClientSets) (*v1alpha1.ChaosResult, error) {
func (expDetails *ExperimentDetails) GetChaosResult(engineDetails EngineDetails, clients ClientSets) (*v1alpha1.ChaosResult, error) {
resultName := GetResultName(engineDetails.Name, experimentDetails.Name)
expResult, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosResults(engineDetails.AppNamespace).Get(resultName, metav1.GetOptions{})
resultName := GetResultName(engineDetails.Name, expDetails.Name, expDetails.InstanceID)
expResult, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosResults(engineDetails.EngineNamespace).Get(context.Background(), resultName, metav1.GetOptions{})
if err != nil {
log.Infoln("Unable to get chaosResult Resource")
log.Infoln(err)
return nil, err
return nil, errors.Errorf("unable to get ChaosResult name: %v in namespace: %v, error: %v", resultName, engineDetails.EngineNamespace, err)
}
return expResult, nil
}
// UpdateEngineWithResult will update hte resutl in chaosEngine
// UpdateEngineWithResult will update the result in chaosEngine
// And will delete job if jobCleanUpPolicy is set to "delete"
func (engineDetails EngineDetails) UpdateEngineWithResult(experiment *ExperimentDetails, clients ClientSets) error {
// Getting the Experiment Result Name
@ -117,7 +94,11 @@ func (engineDetails EngineDetails) UpdateEngineWithResult(experiment *Experiment
}
var currExpStatus ExperimentStatus
currExpStatus.CompletedExperimentStatus(chaosResult, experiment)
chaosPod, err := GetChaosPod(experiment, clients)
if err != nil {
return errors.Errorf("unable to get the chaos pod, error: %v", err)
}
currExpStatus.CompletedExperimentStatus(chaosResult, engineDetails.Name, chaosPod.Name)
if err = currExpStatus.PatchChaosEngineStatus(engineDetails, clients); err != nil {
return err
}
@ -126,24 +107,27 @@ func (engineDetails EngineDetails) UpdateEngineWithResult(experiment *Experiment
}
// DeleteJobAccordingToJobCleanUpPolicy deletes the chaosExperiment Job according to jobCleanUpPolicy
func (engineDetails EngineDetails) DeleteJobAccordingToJobCleanUpPolicy(experiment *ExperimentDetails, clients ClientSets) error {
func (engineDetails EngineDetails) DeleteJobAccordingToJobCleanUpPolicy(experiment *ExperimentDetails, clients ClientSets) (v1alpha1.CleanUpPolicy, error) {
expEngine, err := engineDetails.GetChaosEngine(clients)
if err != nil {
return err
return "", err
}
if expEngine.Spec.JobCleanUpPolicy == "delete" {
log.Infoln("Will delete the job as jobCleanPolicy is set to : " + expEngine.Spec.JobCleanUpPolicy)
switch expEngine.Spec.JobCleanUpPolicy {
case v1alpha1.CleanUpPolicyDelete:
log.Infof("deleting the job as jobCleanPolicy is set to %s", expEngine.Spec.JobCleanUpPolicy)
deletePolicy := metav1.DeletePropagationForeground
deleteJob := clients.KubeClient.BatchV1().Jobs(engineDetails.AppNamespace).Delete(experiment.JobName, &metav1.DeleteOptions{
if deleteJobErr := clients.KubeClient.BatchV1().Jobs(experiment.Namespace).Delete(context.Background(), experiment.JobName, metav1.DeleteOptions{
PropagationPolicy: &deletePolicy,
})
if deleteJob != nil {
log.Infoln(deleteJob)
return deleteJob
}); deleteJobErr != nil {
return "", errors.Errorf("unable to delete ChaosExperiment Job name: %v, in namespace: %v, error: %v", experiment.JobName, experiment.Namespace, deleteJobErr)
}
log.Infof("%v job is deleted successfully", experiment.JobName)
case v1alpha1.CleanUpPolicyRetain, "":
log.Infof("[skip]: skipping the job deletion as jobCleanUpPolicy is set to {%s}", expEngine.Spec.JobCleanUpPolicy)
default:
return expEngine.Spec.JobCleanUpPolicy, fmt.Errorf("%s jobCleanUpPolicy not supported", expEngine.Spec.JobCleanUpPolicy)
}
return nil
return expEngine.Spec.JobCleanUpPolicy, nil
}

423
pkg/utils/watchJob_test.go Normal file
View File

@ -0,0 +1,423 @@
package utils
import (
"context"
"fmt"
"testing"
batchv1 "k8s.io/api/batch/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes/fake"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
litmusFakeClientset "github.com/litmuschaos/chaos-operator/pkg/client/clientset/versioned/fake"
)
func TestPatchChaosEngineStatus(t *testing.T) {
fakeServiceAcc := "Fake Service Account"
fakeAppLabel := "Fake Label"
fakeAppKind := "Fake Kind"
expStatus := ExperimentStatus{
Name: "Fake exp Name",
Status: v1alpha1.ExperimentStatusRunning,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
Appinfo: v1alpha1.ApplicationParams{
Appns: engineDetails.EngineNamespace,
Applabel: fakeAppLabel,
AppKind: fakeAppKind,
},
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
Experiments: []v1alpha1.ExperimentStatuses{
{
Name: expStatus.Name,
},
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
Appinfo: v1alpha1.ApplicationParams{
Appns: engineDetails.EngineNamespace,
Applabel: fakeAppLabel,
AppKind: fakeAppKind,
},
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
err = expStatus.PatchChaosEngineStatus(engineDetails, client)
if !mock.isErr && err != nil {
t.Fatalf("fail to patch the engine status for %v test, err: %v", name, err)
}
if mock.isErr && err == nil {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
chaosEngine, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Get(context.Background(), engineDetails.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get chaos engine after status patch, err: %v", err)
}
if !mock.isErr {
actualResult := chaosEngine.Status.Experiments[0].Status
expectedResult := expStatus.Status
println(actualResult)
if expectedResult != actualResult {
t.Fatalf("Test %q failed: expected result is %v, got %v", name, expectedResult, actualResult)
}
}
if mock.isErr && len(chaosEngine.Status.Experiments) != 0 {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
})
}
}
func TestUpdateEngineWithResult(t *testing.T) {
fakeServiceAcc := "Fake Service Account"
fakeAppLabel := "Fake Label"
fakeAppKind := "Fake Kind"
expStatus := ExperimentStatus{
Name: "Fake-Exp-Name",
Status: v1alpha1.ExperimentStatusRunning,
}
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
chaosresult *v1alpha1.ChaosResult
chaospod v1.Pod
isErr bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
Appinfo: v1alpha1.ApplicationParams{
Appns: engineDetails.EngineNamespace,
Applabel: fakeAppLabel,
AppKind: fakeAppKind,
},
JobCleanUpPolicy: "retain",
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
Experiments: []v1alpha1.ExperimentStatuses{
{
Name: expStatus.Name,
Status: expStatus.Status,
},
},
},
},
chaospod: v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.JobName,
Namespace: experiment.Namespace,
Labels: map[string]string{
"job-name": experiment.JobName,
},
},
},
chaosresult: &v1alpha1.ChaosResult{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name + "-" + expStatus.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosResultSpec{
EngineName: engineDetails.Name,
ExperimentName: expStatus.Name,
},
Status: v1alpha1.ChaosResultStatus{
ExperimentStatus: v1alpha1.TestStatus{
Phase: "Completed",
Verdict: "Pass",
},
},
},
isErr: false,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
Appinfo: v1alpha1.ApplicationParams{
Appns: engineDetails.EngineNamespace,
Applabel: fakeAppLabel,
AppKind: fakeAppKind,
},
JobCleanUpPolicy: "retain",
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
},
},
chaospod: v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.JobName,
Namespace: experiment.Namespace,
Labels: map[string]string{
"job-name": experiment.JobName,
},
},
},
chaosresult: &v1alpha1.ChaosResult{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name + "-" + expStatus.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosResultSpec{
EngineName: engineDetails.Name,
ExperimentName: expStatus.Name,
},
Status: v1alpha1.ChaosResultStatus{
ExperimentStatus: v1alpha1.TestStatus{
Phase: "Completed",
Verdict: "Pass",
},
},
},
isErr: true,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
_, err = client.KubeClient.CoreV1().Pods(engineDetails.EngineNamespace).Create(context.Background(), &mock.chaospod, metav1.CreateOptions{})
if err != nil {
fmt.Printf("fail to create chaos pod for %v test, err: %v", name, err)
}
_, err = client.LitmusClient.LitmuschaosV1alpha1().ChaosResults(mock.chaosresult.Namespace).Create(context.Background(), mock.chaosresult, metav1.CreateOptions{})
if err != nil {
t.Fatalf("chaosresult not created for %v test, err: %v", name, err)
}
err = engineDetails.UpdateEngineWithResult(&experiment, client)
if !mock.isErr && err != nil {
t.Fatalf("fail to update chaos engine with result for %v test, err: %v", name, err)
}
if mock.isErr && err == nil {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
chaosEngine, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Get(context.Background(), engineDetails.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("fail to get chaosengine after status patch for %v test, err: %v", name, err)
}
if !mock.isErr {
actualResult := chaosEngine.Status.Experiments[0].Status
expectedResult := v1alpha1.ExperimentStatusCompleted
println(actualResult)
if expectedResult != actualResult {
t.Fatalf("Test %q failed: expected result is %v, got %v", name, expectedResult, actualResult)
}
}
if mock.isErr && len(chaosEngine.Status.Experiments) != 0 {
t.Fatalf("Test %q failed: expected error not to be nil", name)
}
})
}
}
func TestDeleteJobAccordingToJobCleanUpPolicy(t *testing.T) {
fakeServiceAcc := "Fake Service Account"
experiment := ExperimentDetails{
Name: "Fake-Exp-Name",
Namespace: "Fake NameSpace",
JobName: "fake-job-name",
StatusCheckTimeout: 10,
}
engineDetails := EngineDetails{
Name: "Fake Engine",
EngineNamespace: "Fake NameSpace",
}
tests := map[string]struct {
chaosengine *v1alpha1.ChaosEngine
expjob batchv1.Job
isErr bool
retain bool
}{
"Test Positive-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
JobCleanUpPolicy: v1alpha1.CleanUpPolicyDelete,
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
},
},
expjob: batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.JobName,
Namespace: experiment.Namespace,
Labels: map[string]string{
"job-name": experiment.JobName,
},
},
},
isErr: true,
},
"Test Positive-2": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
JobCleanUpPolicy: v1alpha1.CleanUpPolicyRetain,
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
},
},
expjob: batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.JobName,
Namespace: experiment.Namespace,
Labels: map[string]string{
"job-name": experiment.JobName,
},
},
},
isErr: true,
},
"Test Negative-1": {
chaosengine: &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: engineDetails.Name,
Namespace: engineDetails.EngineNamespace,
},
Spec: v1alpha1.ChaosEngineSpec{
ChaosServiceAccount: fakeServiceAcc,
},
Status: v1alpha1.ChaosEngineStatus{
EngineStatus: v1alpha1.EngineStatusCompleted,
},
},
expjob: batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
Name: experiment.JobName,
Namespace: experiment.Namespace,
Labels: map[string]string{
"job-name": experiment.JobName,
},
},
},
isErr: false,
},
}
for name, mock := range tests {
t.Run(name, func(t *testing.T) {
client := CreateFakeClient(t)
_, err := client.LitmusClient.LitmuschaosV1alpha1().ChaosEngines(mock.chaosengine.Namespace).Create(context.Background(), mock.chaosengine, metav1.CreateOptions{})
if err != nil {
t.Fatalf("engine not created for %v test, err: %v", name, err)
}
_, err = client.KubeClient.BatchV1().Jobs(engineDetails.EngineNamespace).Create(context.Background(), &mock.expjob, metav1.CreateOptions{})
if err != nil {
t.Fatalf("fail to create exp job pod for %v test, err: %v", name, err)
}
cleanupPolicy, err := engineDetails.DeleteJobAccordingToJobCleanUpPolicy(&experiment, client)
if err != nil {
t.Fatalf("fail to create exp job for %v test, err: %v", name, err)
}
jobList, err := client.KubeClient.BatchV1().Jobs(engineDetails.EngineNamespace).List(context.Background(), metav1.ListOptions{LabelSelector: "job-name=" + experiment.JobName})
if !mock.isErr && err != nil && len(jobList.Items) != 0 {
t.Fatalf("[%v] test failed experiment job is not deleted when the job cleanup policy is %v , err: %v", name, err, cleanupPolicy)
}
if mock.isErr && err != nil && len(jobList.Items) == 0 {
t.Fatalf("[%v] test failed experiment job is not retained when the job cleanup policy is %v , err: %v", name, err, cleanupPolicy)
}
})
}
}
func CreateFakeClient(t *testing.T) ClientSets {
clients := ClientSets{}
clients.SetFakeClient()
return clients
}
// SetFakeClient initilizes the fake required clientsets
func (clients *ClientSets) SetFakeClient() {
// Load kubernetes client set by preloading with k8s objects.
clients.KubeClient = fake.NewSimpleClientset([]runtime.Object{}...)
// Load litmus client set by preloading with litmus objects.
clients.LitmusClient = litmusFakeClientset.NewSimpleClientset([]runtime.Object{}...)
}

4
tests/README.md Normal file
View File

@ -0,0 +1,4 @@
# Steps to run this BDD
Get into the current pwd.
Command: `ginkgo -v -- -kubeconfig=/path/to/kube/config`

297
tests/runner_test.go Normal file
View File

@ -0,0 +1,297 @@
package bdd
/*
Copyright 2019 LitmusChaos Authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import (
"context"
"flag"
"fmt"
"os"
"os/exec"
"regexp"
"testing"
"time"
"github.com/litmuschaos/chaos-operator/api/litmuschaos/v1alpha1"
"github.com/litmuschaos/chaos-runner/pkg/log"
"github.com/litmuschaos/chaos-runner/pkg/utils"
"github.com/litmuschaos/chaos-runner/pkg/utils/k8s"
"github.com/litmuschaos/chaos-runner/pkg/utils/litmus"
"github.com/litmuschaos/litmus-go/pkg/utils/retry"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/pkg/errors"
appv1 "k8s.io/api/apps/v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
"k8s.io/client-go/tools/clientcmd"
)
var (
clients utils.ClientSets
kubeconfig string
)
func TestChaos(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "BDD test")
}
func init() {
flag.StringVar(&kubeconfig, "kubeconfig", os.Getenv("HOME")+"/.kube/config", "path to kubeconfig to invoke kubernetes API calls")
}
var _ = BeforeSuite(func() {
// Getting kubeconfig and generate clientSets
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
Expect(err).To(BeNil(), "failed to get config")
k8sClientSet, err := k8s.GenerateK8sClientSet(config)
Expect(err).To(BeNil(), "failed to generate k8sClientSet")
litmusClientSet, err := litmus.GenerateLitmusClientSet(config)
Expect(err).To(BeNil(), "failed to generate litmusClientSet")
clients = utils.ClientSets{}
clients.KubeClient = k8sClientSet
clients.LitmusClient = litmusClientSet
//Creating crds
By("Installing Litmus CRDs")
err = exec.Command("kubectl", "apply", "-f", "https://raw.githubusercontent.com/litmuschaos/chaos-operator/master/deploy/chaos_crds.yaml").Run()
Expect(err).To(BeNil(), "unable to create Litmus CRD's")
log.Info("CRDs created")
//Creating rbacs
By("Installing RBAC")
err = exec.Command("kubectl", "apply", "-f", "https://raw.githubusercontent.com/litmuschaos/chaos-operator/master/deploy/rbac.yaml").Run()
Expect(err).To(BeNil(), "unable to create RBAC Permissions")
log.Info("RBAC created")
//Creating Chaos-Operator
By("Installing Chaos-Operator")
err = exec.Command("kubectl", "apply", "-f", "https://raw.githubusercontent.com/litmuschaos/chaos-operator/master/deploy/operator.yaml").Run()
Expect(err).To(BeNil(), "unable to create Chaos-operator")
log.Info("Chaos-Operator created")
err = retry.
Times(uint(180 / 2)).
Wait(time.Duration(2) * time.Second).
Try(func(attempt uint) error {
podSpec, err := clients.KubeClient.CoreV1().Pods("litmus").List(context.Background(), metav1.ListOptions{LabelSelector: "name=chaos-operator"})
if err != nil || len(podSpec.Items) == 0 {
return errors.Errorf("Unable to list chaos-operator, err: %v", err)
}
for _, v := range podSpec.Items {
if v.Status.Phase != "Running" {
return errors.Errorf("chaos-operator is not in running state, phase: %v", v.Status.Phase)
}
}
return nil
})
Expect(err).To(BeNil(), "the chaos-operator is not in running state")
log.Info("Chaos-Operator is in running state")
By("Installing Pod Delete Experiment")
err = exec.Command("kubectl", "apply", "-f", "https://hub.litmuschaos.io/api/chaos/master?file=faults/kubernetes/pod-delete/fault.yaml", "-n", "litmus").Run()
Expect(err).To(BeNil(), "unable to create Pod-Delete Experiment")
log.Info("pod-delete ChaosExperiment created")
err = exec.Command("kubectl", "apply", "-f", "https://raw.githubusercontent.com/litmuschaos/chaos-operator/master/tests/manifest/pod_delete_rbac.yaml", "-n", "litmus").Run()
Expect(err).To(BeNil(), "unable to create pod-delete rbac")
log.Info("pod-delete-sa created")
})
// BDD Tests to check secondary resources
var _ = Describe("BDD on chaos-runner", func() {
// BDD TEST CASE 1
When("Create a test Deployment with nginx image", func() {
It("Should create Nginx deployment ", func() {
By("Building a nginx deployment")
//creating nginx deployment
deployment := &appv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "nginx",
Namespace: "litmus",
Labels: map[string]string{
"app": "nginx",
},
Annotations: map[string]string{
"litmuschaos.io/chaos": "true",
},
},
Spec: appv1.DeploymentSpec{
Replicas: func(i int32) *int32 { return &i }(3),
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
"app": "nginx",
},
},
Template: v1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app": "nginx",
},
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "nginx",
Image: "nginx:latest",
Ports: []v1.ContainerPort{
{
ContainerPort: 80,
},
},
},
},
},
},
},
}
By("Creating nginx deployment")
_, err := clients.KubeClient.AppsV1().Deployments("litmus").Create(context.Background(), deployment, metav1.CreateOptions{})
Expect(err).To(
BeNil(),
"while creating nginx deployment in namespace litmus",
)
log.Info("nginx deployment created")
})
})
When("Creating ChaosEngine to trigger chaos-runner", func() {
It("Should create a runnerPod and Service ", func() {
//Creating chaosEngine
By("Creating ChaosEngine")
chaosEngine := &v1alpha1.ChaosEngine{
ObjectMeta: metav1.ObjectMeta{
Name: "engine-nginx",
Namespace: "litmus",
},
Spec: v1alpha1.ChaosEngineSpec{
Appinfo: v1alpha1.ApplicationParams{
Appns: "litmus",
Applabel: "app=nginx",
AppKind: "deployment",
},
EngineState: "active",
ChaosServiceAccount: "pod-delete-sa",
Components: v1alpha1.ComponentParams{
Runner: v1alpha1.RunnerInfo{
Image: "litmuschaos/chaos-runner:ci",
Type: "go",
},
},
Experiments: []v1alpha1.ExperimentList{
{
Name: "pod-delete",
Spec: v1alpha1.ExperimentAttributes{},
},
},
},
}
By("Creating ChaosEngine Resource")
_, err := clients.LitmusClient.LitmuschaosV1alpha1().ChaosEngines("litmus").Create(context.Background(), chaosEngine, metav1.CreateOptions{})
Expect(err).To(
BeNil(),
"while building ChaosEngine engine-nginx in namespace litmus",
)
log.Info("chaos engine created")
err = retry.
Times(uint(180 / 2)).
Wait(time.Duration(2) * time.Second).
Try(func(attempt uint) error {
pod, err := clients.KubeClient.CoreV1().Pods("litmus").Get(context.Background(), "engine-nginx-runner", metav1.GetOptions{})
if err != nil {
return errors.Errorf("unable to get chaos-runner pod, err: %v", err)
}
if pod.Status.Phase != v1.PodRunning && pod.Status.Phase != v1.PodSucceeded {
return errors.Errorf("chaos runner is not in running state, phase: %v", pod.Status.Phase)
}
return nil
})
if err != nil {
log.Errorf("The chaos-runner is not in running state, err: %v", err)
}
log.Info("runner pod created")
})
})
When("Check if the Job is spawned by chaos-runner", func() {
It("Should create a Pod delete Job", func() {
err := retry.
Times(uint(180 / 2)).
Wait(time.Duration(2) * time.Second).
Try(func(attempt uint) error {
var jobName string
jobs, err := clients.KubeClient.BatchV1().Jobs("litmus").List(context.Background(), metav1.ListOptions{})
if err != nil {
return err
}
regExpr, err := regexp.Compile("pod-delete-.*")
if err != nil {
return err
}
for _, job := range jobs.Items {
matched := regExpr.MatchString(job.Name)
if matched {
jobName = job.Name
break
}
}
if jobName == "" {
return fmt.Errorf("unable to get the job, might be something wrong with chaos-runner")
}
return nil
})
Expect(err).To(
BeNil(),
"while listing experiment job in namespace litmus",
)
})
})
})
// Deleting all unused resources
var _ = AfterSuite(func() {
By("Deleting chaosengine CRD")
ceDeleteCRDs := exec.Command("kubectl", "delete", "crds", "chaosengines.litmuschaos.io").Run()
Expect(ceDeleteCRDs).To(BeNil())
By("Deleting other CRDs")
crdDeletion := exec.Command("kubectl", "delete", "crds", "chaosresults.litmuschaos.io", "chaosexperiments.litmuschaos.io").Run()
Expect(crdDeletion).To(BeNil())
By("Deleting namespace litmus")
rbacDeletion := exec.Command("kubectl", "delete", "ns", "litmus").Run()
Expect(rbacDeletion).To(BeNil())
log.Info("deleted CRD and Namespace")
})

View File

@ -1,5 +0,0 @@
*.sublime-*
.DS_Store
*.swp
*.swo
tags

View File

@ -1,12 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- 1.9.x
- "1.10.x"
- "1.11.x"
- tip

View File

@ -1,12 +0,0 @@
Copyright (c) 2012, Martin Angers
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,188 +0,0 @@
# Purell
Purell is a tiny Go library to normalize URLs. It returns a pure URL. Pure-ell. Sanitizer and all. Yeah, I know...
Based on the [wikipedia paper][wiki] and the [RFC 3986 document][rfc].
[![build status](https://travis-ci.org/PuerkitoBio/purell.svg?branch=master)](http://travis-ci.org/PuerkitoBio/purell)
## Install
`go get github.com/PuerkitoBio/purell`
## Changelog
* **v1.1.1** : Fix failing test due to Go1.12 changes (thanks to @ianlancetaylor).
* **2016-11-14 (v1.1.0)** : IDN: Conform to RFC 5895: Fold character width (thanks to @beeker1121).
* **2016-07-27 (v1.0.0)** : Normalize IDN to ASCII (thanks to @zenovich).
* **2015-02-08** : Add fix for relative paths issue ([PR #5][pr5]) and add fix for unnecessary encoding of reserved characters ([see issue #7][iss7]).
* **v0.2.0** : Add benchmarks, Attempt IDN support.
* **v0.1.0** : Initial release.
## Examples
From `example_test.go` (note that in your code, you would import "github.com/PuerkitoBio/purell", and would prefix references to its methods and constants with "purell."):
```go
package purell
import (
"fmt"
"net/url"
)
func ExampleNormalizeURLString() {
if normalized, err := NormalizeURLString("hTTp://someWEBsite.com:80/Amazing%3f/url/",
FlagLowercaseScheme|FlagLowercaseHost|FlagUppercaseEscapes); err != nil {
panic(err)
} else {
fmt.Print(normalized)
}
// Output: http://somewebsite.com:80/Amazing%3F/url/
}
func ExampleMustNormalizeURLString() {
normalized := MustNormalizeURLString("hTTpS://someWEBsite.com:443/Amazing%fa/url/",
FlagsUnsafeGreedy)
fmt.Print(normalized)
// Output: http://somewebsite.com/Amazing%FA/url
}
func ExampleNormalizeURL() {
if u, err := url.Parse("Http://SomeUrl.com:8080/a/b/.././c///g?c=3&a=1&b=9&c=0#target"); err != nil {
panic(err)
} else {
normalized := NormalizeURL(u, FlagsUsuallySafeGreedy|FlagRemoveDuplicateSlashes|FlagRemoveFragment)
fmt.Print(normalized)
}
// Output: http://someurl.com:8080/a/c/g?c=3&a=1&b=9&c=0
}
```
## API
As seen in the examples above, purell offers three methods, `NormalizeURLString(string, NormalizationFlags) (string, error)`, `MustNormalizeURLString(string, NormalizationFlags) (string)` and `NormalizeURL(*url.URL, NormalizationFlags) (string)`. They all normalize the provided URL based on the specified flags. Here are the available flags:
```go
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
```
For convenience, the set of flags `FlagsSafe`, `FlagsUsuallySafe[Greedy|NonGreedy]`, `FlagsUnsafe[Greedy|NonGreedy]` and `FlagsAll[Greedy|NonGreedy]` are provided for the similarly grouped normalizations on [wikipedia's URL normalization page][wiki]. You can add (using the bitwise OR `|` operator) or remove (using the bitwise AND NOT `&^` operator) individual flags from the sets if required, to build your own custom set.
The [full godoc reference is available on gopkgdoc][godoc].
Some things to note:
* `FlagDecodeUnnecessaryEscapes`, `FlagEncodeNecessaryEscapes`, `FlagUppercaseEscapes` and `FlagRemoveEmptyQuerySeparator` are always implicitly set, because internally, the URL string is parsed as an URL object, which automatically decodes unnecessary escapes, uppercases and encodes necessary ones, and removes empty query separators (an unnecessary `?` at the end of the url). So this operation cannot **not** be done. For this reason, `FlagRemoveEmptyQuerySeparator` (as well as the other three) has been included in the `FlagsSafe` convenience set, instead of `FlagsUnsafe`, where Wikipedia puts it.
* The `FlagDecodeUnnecessaryEscapes` decodes the following escapes (*from -> to*):
- %24 -> $
- %26 -> &
- %2B-%3B -> +,-./0123456789:;
- %3D -> =
- %40-%5A -> @ABCDEFGHIJKLMNOPQRSTUVWXYZ
- %5F -> _
- %61-%7A -> abcdefghijklmnopqrstuvwxyz
- %7E -> ~
* When the `NormalizeURL` function is used (passing an URL object), this source URL object is modified (that is, after the call, the URL object will be modified to reflect the normalization).
* The *replace IP with domain name* normalization (`http://208.77.188.166/ → http://www.example.com/`) is obviously not possible for a library without making some network requests. This is not implemented in purell.
* The *remove unused query string parameters* and *remove default query parameters* are also not implemented, since this is a very case-specific normalization, and it is quite trivial to do with an URL object.
### Safe vs Usually Safe vs Unsafe
Purell allows you to control the level of risk you take while normalizing an URL. You can aggressively normalize, play it totally safe, or anything in between.
Consider the following URL:
`HTTPS://www.RooT.com/toto/t%45%1f///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
Normalizing with the `FlagsSafe` gives:
`https://www.root.com/toto/tE%1F///a/./b/../c/?z=3&w=2&a=4&w=1#invalid`
With the `FlagsUsuallySafeGreedy`:
`https://www.root.com/toto/tE%1F///a/c?z=3&w=2&a=4&w=1#invalid`
And with `FlagsUnsafeGreedy`:
`http://root.com/toto/tE%1F/a/c?a=4&w=1&w=2&z=3`
## TODOs
* Add a class/default instance to allow specifying custom directory index names? At the moment, removing directory index removes `(^|/)((?:default|index)\.\w{1,4})$`.
## Thanks / Contributions
@rogpeppe
@jehiah
@opennota
@pchristopher1275
@zenovich
@beeker1121
## License
The [BSD 3-Clause license][bsd].
[bsd]: http://opensource.org/licenses/BSD-3-Clause
[wiki]: http://en.wikipedia.org/wiki/URL_normalization
[rfc]: http://tools.ietf.org/html/rfc3986#section-6
[godoc]: http://go.pkgdoc.org/github.com/PuerkitoBio/purell
[pr5]: https://github.com/PuerkitoBio/purell/pull/5
[iss7]: https://github.com/PuerkitoBio/purell/issues/7

View File

@ -1,379 +0,0 @@
/*
Package purell offers URL normalization as described on the wikipedia page:
http://en.wikipedia.org/wiki/URL_normalization
*/
package purell
import (
"bytes"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
"strings"
"github.com/PuerkitoBio/urlesc"
"golang.org/x/net/idna"
"golang.org/x/text/unicode/norm"
"golang.org/x/text/width"
)
// A set of normalization flags determines how a URL will
// be normalized.
type NormalizationFlags uint
const (
// Safe normalizations
FlagLowercaseScheme NormalizationFlags = 1 << iota // HTTP://host -> http://host, applied by default in Go1.1
FlagLowercaseHost // http://HOST -> http://host
FlagUppercaseEscapes // http://host/t%ef -> http://host/t%EF
FlagDecodeUnnecessaryEscapes // http://host/t%41 -> http://host/tA
FlagEncodeNecessaryEscapes // http://host/!"#$ -> http://host/%21%22#$
FlagRemoveDefaultPort // http://host:80 -> http://host
FlagRemoveEmptyQuerySeparator // http://host/path? -> http://host/path
// Usually safe normalizations
FlagRemoveTrailingSlash // http://host/path/ -> http://host/path
FlagAddTrailingSlash // http://host/path -> http://host/path/ (should choose only one of these add/remove trailing slash flags)
FlagRemoveDotSegments // http://host/path/./a/b/../c -> http://host/path/a/c
// Unsafe normalizations
FlagRemoveDirectoryIndex // http://host/path/index.html -> http://host/path/
FlagRemoveFragment // http://host/path#fragment -> http://host/path
FlagForceHTTP // https://host -> http://host
FlagRemoveDuplicateSlashes // http://host/path//a///b -> http://host/path/a/b
FlagRemoveWWW // http://www.host/ -> http://host/
FlagAddWWW // http://host/ -> http://www.host/ (should choose only one of these add/remove WWW flags)
FlagSortQuery // http://host/path?c=3&b=2&a=1&b=1 -> http://host/path?a=1&b=1&b=2&c=3
// Normalizations not in the wikipedia article, required to cover tests cases
// submitted by jehiah
FlagDecodeDWORDHost // http://1113982867 -> http://66.102.7.147
FlagDecodeOctalHost // http://0102.0146.07.0223 -> http://66.102.7.147
FlagDecodeHexHost // http://0x42660793 -> http://66.102.7.147
FlagRemoveUnnecessaryHostDots // http://.host../path -> http://host/path
FlagRemoveEmptyPortSeparator // http://host:/path -> http://host/path
// Convenience set of safe normalizations
FlagsSafe NormalizationFlags = FlagLowercaseHost | FlagLowercaseScheme | FlagUppercaseEscapes | FlagDecodeUnnecessaryEscapes | FlagEncodeNecessaryEscapes | FlagRemoveDefaultPort | FlagRemoveEmptyQuerySeparator
// For convenience sets, "greedy" uses the "remove trailing slash" and "remove www. prefix" flags,
// while "non-greedy" uses the "add (or keep) the trailing slash" and "add www. prefix".
// Convenience set of usually safe normalizations (includes FlagsSafe)
FlagsUsuallySafeGreedy NormalizationFlags = FlagsSafe | FlagRemoveTrailingSlash | FlagRemoveDotSegments
FlagsUsuallySafeNonGreedy NormalizationFlags = FlagsSafe | FlagAddTrailingSlash | FlagRemoveDotSegments
// Convenience set of unsafe normalizations (includes FlagsUsuallySafe)
FlagsUnsafeGreedy NormalizationFlags = FlagsUsuallySafeGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagRemoveWWW | FlagSortQuery
FlagsUnsafeNonGreedy NormalizationFlags = FlagsUsuallySafeNonGreedy | FlagRemoveDirectoryIndex | FlagRemoveFragment | FlagForceHTTP | FlagRemoveDuplicateSlashes | FlagAddWWW | FlagSortQuery
// Convenience set of all available flags
FlagsAllGreedy = FlagsUnsafeGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
FlagsAllNonGreedy = FlagsUnsafeNonGreedy | FlagDecodeDWORDHost | FlagDecodeOctalHost | FlagDecodeHexHost | FlagRemoveUnnecessaryHostDots | FlagRemoveEmptyPortSeparator
)
const (
defaultHttpPort = ":80"
defaultHttpsPort = ":443"
)
// Regular expressions used by the normalizations
var rxPort = regexp.MustCompile(`(:\d+)/?$`)
var rxDirIndex = regexp.MustCompile(`(^|/)((?:default|index)\.\w{1,4})$`)
var rxDupSlashes = regexp.MustCompile(`/{2,}`)
var rxDWORDHost = regexp.MustCompile(`^(\d+)((?:\.+)?(?:\:\d*)?)$`)
var rxOctalHost = regexp.MustCompile(`^(0\d*)\.(0\d*)\.(0\d*)\.(0\d*)((?:\.+)?(?:\:\d*)?)$`)
var rxHexHost = regexp.MustCompile(`^0x([0-9A-Fa-f]+)((?:\.+)?(?:\:\d*)?)$`)
var rxHostDots = regexp.MustCompile(`^(.+?)(:\d+)?$`)
var rxEmptyPort = regexp.MustCompile(`:+$`)
// Map of flags to implementation function.
// FlagDecodeUnnecessaryEscapes has no action, since it is done automatically
// by parsing the string as an URL. Same for FlagUppercaseEscapes and FlagRemoveEmptyQuerySeparator.
// Since maps have undefined traversing order, make a slice of ordered keys
var flagsOrder = []NormalizationFlags{
FlagLowercaseScheme,
FlagLowercaseHost,
FlagRemoveDefaultPort,
FlagRemoveDirectoryIndex,
FlagRemoveDotSegments,
FlagRemoveFragment,
FlagForceHTTP, // Must be after remove default port (because https=443/http=80)
FlagRemoveDuplicateSlashes,
FlagRemoveWWW,
FlagAddWWW,
FlagSortQuery,
FlagDecodeDWORDHost,
FlagDecodeOctalHost,
FlagDecodeHexHost,
FlagRemoveUnnecessaryHostDots,
FlagRemoveEmptyPortSeparator,
FlagRemoveTrailingSlash, // These two (add/remove trailing slash) must be last
FlagAddTrailingSlash,
}
// ... and then the map, where order is unimportant
var flags = map[NormalizationFlags]func(*url.URL){
FlagLowercaseScheme: lowercaseScheme,
FlagLowercaseHost: lowercaseHost,
FlagRemoveDefaultPort: removeDefaultPort,
FlagRemoveDirectoryIndex: removeDirectoryIndex,
FlagRemoveDotSegments: removeDotSegments,
FlagRemoveFragment: removeFragment,
FlagForceHTTP: forceHTTP,
FlagRemoveDuplicateSlashes: removeDuplicateSlashes,
FlagRemoveWWW: removeWWW,
FlagAddWWW: addWWW,
FlagSortQuery: sortQuery,
FlagDecodeDWORDHost: decodeDWORDHost,
FlagDecodeOctalHost: decodeOctalHost,
FlagDecodeHexHost: decodeHexHost,
FlagRemoveUnnecessaryHostDots: removeUnncessaryHostDots,
FlagRemoveEmptyPortSeparator: removeEmptyPortSeparator,
FlagRemoveTrailingSlash: removeTrailingSlash,
FlagAddTrailingSlash: addTrailingSlash,
}
// MustNormalizeURLString returns the normalized string, and panics if an error occurs.
// It takes an URL string as input, as well as the normalization flags.
func MustNormalizeURLString(u string, f NormalizationFlags) string {
result, e := NormalizeURLString(u, f)
if e != nil {
panic(e)
}
return result
}
// NormalizeURLString returns the normalized string, or an error if it can't be parsed into an URL object.
// It takes an URL string as input, as well as the normalization flags.
func NormalizeURLString(u string, f NormalizationFlags) (string, error) {
parsed, err := url.Parse(u)
if err != nil {
return "", err
}
if f&FlagLowercaseHost == FlagLowercaseHost {
parsed.Host = strings.ToLower(parsed.Host)
}
// The idna package doesn't fully conform to RFC 5895
// (https://tools.ietf.org/html/rfc5895), so we do it here.
// Taken from Go 1.8 cycle source, courtesy of bradfitz.
// TODO: Remove when (if?) idna package conforms to RFC 5895.
parsed.Host = width.Fold.String(parsed.Host)
parsed.Host = norm.NFC.String(parsed.Host)
if parsed.Host, err = idna.ToASCII(parsed.Host); err != nil {
return "", err
}
return NormalizeURL(parsed, f), nil
}
// NormalizeURL returns the normalized string.
// It takes a parsed URL object as input, as well as the normalization flags.
func NormalizeURL(u *url.URL, f NormalizationFlags) string {
for _, k := range flagsOrder {
if f&k == k {
flags[k](u)
}
}
return urlesc.Escape(u)
}
func lowercaseScheme(u *url.URL) {
if len(u.Scheme) > 0 {
u.Scheme = strings.ToLower(u.Scheme)
}
}
func lowercaseHost(u *url.URL) {
if len(u.Host) > 0 {
u.Host = strings.ToLower(u.Host)
}
}
func removeDefaultPort(u *url.URL) {
if len(u.Host) > 0 {
scheme := strings.ToLower(u.Scheme)
u.Host = rxPort.ReplaceAllStringFunc(u.Host, func(val string) string {
if (scheme == "http" && val == defaultHttpPort) || (scheme == "https" && val == defaultHttpsPort) {
return ""
}
return val
})
}
}
func removeTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if strings.HasSuffix(u.Path, "/") {
u.Path = u.Path[:l-1]
}
} else if l = len(u.Host); l > 0 {
if strings.HasSuffix(u.Host, "/") {
u.Host = u.Host[:l-1]
}
}
}
func addTrailingSlash(u *url.URL) {
if l := len(u.Path); l > 0 {
if !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
} else if l = len(u.Host); l > 0 {
if !strings.HasSuffix(u.Host, "/") {
u.Host += "/"
}
}
}
func removeDotSegments(u *url.URL) {
if len(u.Path) > 0 {
var dotFree []string
var lastIsDot bool
sections := strings.Split(u.Path, "/")
for _, s := range sections {
if s == ".." {
if len(dotFree) > 0 {
dotFree = dotFree[:len(dotFree)-1]
}
} else if s != "." {
dotFree = append(dotFree, s)
}
lastIsDot = (s == "." || s == "..")
}
// Special case if host does not end with / and new path does not begin with /
u.Path = strings.Join(dotFree, "/")
if u.Host != "" && !strings.HasSuffix(u.Host, "/") && !strings.HasPrefix(u.Path, "/") {
u.Path = "/" + u.Path
}
// Special case if the last segment was a dot, make sure the path ends with a slash
if lastIsDot && !strings.HasSuffix(u.Path, "/") {
u.Path += "/"
}
}
}
func removeDirectoryIndex(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDirIndex.ReplaceAllString(u.Path, "$1")
}
}
func removeFragment(u *url.URL) {
u.Fragment = ""
}
func forceHTTP(u *url.URL) {
if strings.ToLower(u.Scheme) == "https" {
u.Scheme = "http"
}
}
func removeDuplicateSlashes(u *url.URL) {
if len(u.Path) > 0 {
u.Path = rxDupSlashes.ReplaceAllString(u.Path, "/")
}
}
func removeWWW(u *url.URL) {
if len(u.Host) > 0 && strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = u.Host[4:]
}
}
func addWWW(u *url.URL) {
if len(u.Host) > 0 && !strings.HasPrefix(strings.ToLower(u.Host), "www.") {
u.Host = "www." + u.Host
}
}
func sortQuery(u *url.URL) {
q := u.Query()
if len(q) > 0 {
arKeys := make([]string, len(q))
i := 0
for k := range q {
arKeys[i] = k
i++
}
sort.Strings(arKeys)
buf := new(bytes.Buffer)
for _, k := range arKeys {
sort.Strings(q[k])
for _, v := range q[k] {
if buf.Len() > 0 {
buf.WriteRune('&')
}
buf.WriteString(fmt.Sprintf("%s=%s", k, urlesc.QueryEscape(v)))
}
}
// Rebuild the raw query string
u.RawQuery = buf.String()
}
}
func decodeDWORDHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxDWORDHost.FindStringSubmatch(u.Host); len(matches) > 2 {
var parts [4]int64
dword, _ := strconv.ParseInt(matches[1], 10, 0)
for i, shift := range []uint{24, 16, 8, 0} {
parts[i] = dword >> shift & 0xFF
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[2])
}
}
}
func decodeOctalHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxOctalHost.FindStringSubmatch(u.Host); len(matches) > 5 {
var parts [4]int64
for i := 1; i <= 4; i++ {
parts[i-1], _ = strconv.ParseInt(matches[i], 8, 0)
}
u.Host = fmt.Sprintf("%d.%d.%d.%d%s", parts[0], parts[1], parts[2], parts[3], matches[5])
}
}
}
func decodeHexHost(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHexHost.FindStringSubmatch(u.Host); len(matches) > 2 {
// Conversion is safe because of regex validation
parsed, _ := strconv.ParseInt(matches[1], 16, 0)
// Set host as DWORD (base 10) encoded host
u.Host = fmt.Sprintf("%d%s", parsed, matches[2])
// The rest is the same as decoding a DWORD host
decodeDWORDHost(u)
}
}
}
func removeUnncessaryHostDots(u *url.URL) {
if len(u.Host) > 0 {
if matches := rxHostDots.FindStringSubmatch(u.Host); len(matches) > 1 {
// Trim the leading and trailing dots
u.Host = strings.Trim(matches[1], ".")
if len(matches) > 2 {
u.Host += matches[2]
}
}
}
}
func removeEmptyPortSeparator(u *url.URL) {
if len(u.Host) > 0 {
u.Host = rxEmptyPort.ReplaceAllString(u.Host, "")
}
}

View File

@ -1,15 +0,0 @@
language: go
go:
- 1.4.x
- 1.5.x
- 1.6.x
- 1.7.x
- 1.8.x
- tip
install:
- go build .
script:
- go test -v

View File

@ -1,27 +0,0 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,16 +0,0 @@
urlesc [![Build Status](https://travis-ci.org/PuerkitoBio/urlesc.svg?branch=master)](https://travis-ci.org/PuerkitoBio/urlesc) [![GoDoc](http://godoc.org/github.com/PuerkitoBio/urlesc?status.svg)](http://godoc.org/github.com/PuerkitoBio/urlesc)
======
Package urlesc implements query escaping as per RFC 3986.
It contains some parts of the net/url package, modified so as to allow
some reserved characters incorrectly escaped by net/url (see [issue 5684](https://github.com/golang/go/issues/5684)).
## Install
go get github.com/PuerkitoBio/urlesc
## License
Go license (BSD-3-Clause)

View File

@ -1,180 +0,0 @@
// Copyright 2009 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 urlesc implements query escaping as per RFC 3986.
// It contains some parts of the net/url package, modified so as to allow
// some reserved characters incorrectly escaped by net/url.
// See https://github.com/golang/go/issues/5684
package urlesc
import (
"bytes"
"net/url"
"strings"
)
type encoding int
const (
encodePath encoding = 1 + iota
encodeUserPassword
encodeQueryComponent
encodeFragment
)
// Return true if the specified character should be escaped when
// appearing in a URL string, according to RFC 3986.
func shouldEscape(c byte, mode encoding) bool {
// §2.3 Unreserved characters (alphanum)
if 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' || '0' <= c && c <= '9' {
return false
}
switch c {
case '-', '.', '_', '~': // §2.3 Unreserved characters (mark)
return false
// §2.2 Reserved characters (reserved)
case ':', '/', '?', '#', '[', ']', '@', // gen-delims
'!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=': // sub-delims
// Different sections of the URL allow a few of
// the reserved characters to appear unescaped.
switch mode {
case encodePath: // §3.3
// The RFC allows sub-delims and : @.
// '/', '[' and ']' can be used to assign meaning to individual path
// segments. This package only manipulates the path as a whole,
// so we allow those as well. That leaves only ? and # to escape.
return c == '?' || c == '#'
case encodeUserPassword: // §3.2.1
// The RFC allows : and sub-delims in
// userinfo. The parsing of userinfo treats ':' as special so we must escape
// all the gen-delims.
return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'
case encodeQueryComponent: // §3.4
// The RFC allows / and ?.
return c != '/' && c != '?'
case encodeFragment: // §4.1
// The RFC text is silent but the grammar allows
// everything, so escape nothing but #
return c == '#'
}
}
// Everything else must be escaped.
return true
}
// QueryEscape escapes the string so it can be safely placed
// inside a URL query.
func QueryEscape(s string) string {
return escape(s, encodeQueryComponent)
}
func escape(s string, mode encoding) string {
spaceCount, hexCount := 0, 0
for i := 0; i < len(s); i++ {
c := s[i]
if shouldEscape(c, mode) {
if c == ' ' && mode == encodeQueryComponent {
spaceCount++
} else {
hexCount++
}
}
}
if spaceCount == 0 && hexCount == 0 {
return s
}
t := make([]byte, len(s)+2*hexCount)
j := 0
for i := 0; i < len(s); i++ {
switch c := s[i]; {
case c == ' ' && mode == encodeQueryComponent:
t[j] = '+'
j++
case shouldEscape(c, mode):
t[j] = '%'
t[j+1] = "0123456789ABCDEF"[c>>4]
t[j+2] = "0123456789ABCDEF"[c&15]
j += 3
default:
t[j] = s[i]
j++
}
}
return string(t)
}
var uiReplacer = strings.NewReplacer(
"%21", "!",
"%27", "'",
"%28", "(",
"%29", ")",
"%2A", "*",
)
// unescapeUserinfo unescapes some characters that need not to be escaped as per RFC3986.
func unescapeUserinfo(s string) string {
return uiReplacer.Replace(s)
}
// Escape reassembles the URL into a valid URL string.
// The general form of the result is one of:
//
// scheme:opaque
// scheme://userinfo@host/path?query#fragment
//
// If u.Opaque is non-empty, String uses the first form;
// otherwise it uses the second form.
//
// In the second form, the following rules apply:
// - if u.Scheme is empty, scheme: is omitted.
// - if u.User is nil, userinfo@ is omitted.
// - if u.Host is empty, host/ is omitted.
// - if u.Scheme and u.Host are empty and u.User is nil,
// the entire scheme://userinfo@host/ is omitted.
// - if u.Host is non-empty and u.Path begins with a /,
// the form host/path does not add its own /.
// - if u.RawQuery is empty, ?query is omitted.
// - if u.Fragment is empty, #fragment is omitted.
func Escape(u *url.URL) string {
var buf bytes.Buffer
if u.Scheme != "" {
buf.WriteString(u.Scheme)
buf.WriteByte(':')
}
if u.Opaque != "" {
buf.WriteString(u.Opaque)
} else {
if u.Scheme != "" || u.Host != "" || u.User != nil {
buf.WriteString("//")
if ui := u.User; ui != nil {
buf.WriteString(unescapeUserinfo(ui.String()))
buf.WriteByte('@')
}
if h := u.Host; h != "" {
buf.WriteString(h)
}
}
if u.Path != "" && u.Path[0] != '/' && u.Host != "" {
buf.WriteByte('/')
}
buf.WriteString(escape(u.Path, encodePath))
}
if u.RawQuery != "" {
buf.WriteByte('?')
buf.WriteString(u.RawQuery)
}
if u.Fragment != "" {
buf.WriteByte('#')
buf.WriteString(escape(u.Fragment, encodeFragment))
}
return buf.String()
}

View File

@ -1,15 +0,0 @@
ISC License
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,145 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is not running on Google App Engine, compiled by GopherJS, and
// "-tags safe" is not added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// Go versions prior to 1.4 are disabled because they use a different layout
// for interfaces which make the implementation of unsafeReflectValue more complex.
// +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew
import (
"reflect"
"unsafe"
)
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = false
// ptrSize is the size of a pointer on the current arch.
ptrSize = unsafe.Sizeof((*byte)(nil))
)
type flag uintptr
var (
// flagRO indicates whether the value field of a reflect.Value
// is read-only.
flagRO flag
// flagAddr indicates whether the address of the reflect.Value's
// value may be taken.
flagAddr flag
)
// flagKindMask holds the bits that make up the kind
// part of the flags field. In all the supported versions,
// it is in the lower 5 bits.
const flagKindMask = flag(0x1f)
// Different versions of Go have used different
// bit layouts for the flags type. This table
// records the known combinations.
var okFlags = []struct {
ro, addr flag
}{{
// From Go 1.4 to 1.5
ro: 1 << 5,
addr: 1 << 7,
}, {
// Up to Go tip.
ro: 1<<5 | 1<<6,
addr: 1 << 8,
}}
var flagValOffset = func() uintptr {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
return field.Offset
}()
// flagField returns a pointer to the flag field of a reflect.Value.
func flagField(v *reflect.Value) *flag {
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
}
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
// the typical safety restrictions preventing access to unaddressable and
// unexported data. It works by digging the raw pointer to the underlying
// value out of the protected value and generating a new unprotected (unsafe)
// reflect.Value to it.
//
// This allows us to check for implementations of the Stringer and error
// interfaces to be used for pretty printing ordinarily unaddressable and
// inaccessible values such as unexported struct fields.
func unsafeReflectValue(v reflect.Value) reflect.Value {
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
return v
}
flagFieldPtr := flagField(&v)
*flagFieldPtr &^= flagRO
*flagFieldPtr |= flagAddr
return v
}
// Sanity checks against future reflect package changes
// to the type or semantics of the Value.flag field.
func init() {
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
if !ok {
panic("reflect.Value has no flag field")
}
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
panic("reflect.Value flag field has changed kind")
}
type t0 int
var t struct {
A t0
// t0 will have flagEmbedRO set.
t0
// a will have flagStickyRO set
a t0
}
vA := reflect.ValueOf(t).FieldByName("A")
va := reflect.ValueOf(t).FieldByName("a")
vt0 := reflect.ValueOf(t).FieldByName("t0")
// Infer flagRO from the difference between the flags
// for the (otherwise identical) fields in t.
flagPublic := *flagField(&vA)
flagWithRO := *flagField(&va) | *flagField(&vt0)
flagRO = flagPublic ^ flagWithRO
// Infer flagAddr from the difference between a value
// taken from a pointer and not.
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
flagNoPtr := *flagField(&vA)
flagPtr := *flagField(&vPtrA)
flagAddr = flagNoPtr ^ flagPtr
// Check that the inferred flags tally with one of the known versions.
for _, f := range okFlags {
if flagRO == f.ro && flagAddr == f.addr {
return
}
}
panic("reflect.Value read-only flag has changed semantics")
}

View File

@ -1,38 +0,0 @@
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
// NOTE: Due to the following build constraints, this file will only be compiled
// when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used.
// +build js appengine safe disableunsafe !go1.4
package spew
import "reflect"
const (
// UnsafeDisabled is a build-time constant which specifies whether or
// not access to the unsafe package is available.
UnsafeDisabled = true
)
// unsafeReflectValue typically converts the passed reflect.Value into a one
// that bypasses the typical safety restrictions preventing access to
// unaddressable and unexported data. However, doing this relies on access to
// the unsafe package. This is a stub version which simply returns the passed
// reflect.Value when the unsafe package is not available.
func unsafeReflectValue(v reflect.Value) reflect.Value {
return v
}

View File

@ -1,341 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"reflect"
"sort"
"strconv"
)
// Some constants in the form of bytes to avoid string overhead. This mirrors
// the technique used in the fmt package.
var (
panicBytes = []byte("(PANIC=")
plusBytes = []byte("+")
iBytes = []byte("i")
trueBytes = []byte("true")
falseBytes = []byte("false")
interfaceBytes = []byte("(interface {})")
commaNewlineBytes = []byte(",\n")
newlineBytes = []byte("\n")
openBraceBytes = []byte("{")
openBraceNewlineBytes = []byte("{\n")
closeBraceBytes = []byte("}")
asteriskBytes = []byte("*")
colonBytes = []byte(":")
colonSpaceBytes = []byte(": ")
openParenBytes = []byte("(")
closeParenBytes = []byte(")")
spaceBytes = []byte(" ")
pointerChainBytes = []byte("->")
nilAngleBytes = []byte("<nil>")
maxNewlineBytes = []byte("<max depth reached>\n")
maxShortBytes = []byte("<max>")
circularBytes = []byte("<already shown>")
circularShortBytes = []byte("<shown>")
invalidAngleBytes = []byte("<invalid>")
openBracketBytes = []byte("[")
closeBracketBytes = []byte("]")
percentBytes = []byte("%")
precisionBytes = []byte(".")
openAngleBytes = []byte("<")
closeAngleBytes = []byte(">")
openMapBytes = []byte("map[")
closeMapBytes = []byte("]")
lenEqualsBytes = []byte("len=")
capEqualsBytes = []byte("cap=")
)
// hexDigits is used to map a decimal value to a hex digit.
var hexDigits = "0123456789abcdef"
// catchPanic handles any panics that might occur during the handleMethods
// calls.
func catchPanic(w io.Writer, v reflect.Value) {
if err := recover(); err != nil {
w.Write(panicBytes)
fmt.Fprintf(w, "%v", err)
w.Write(closeParenBytes)
}
}
// handleMethods attempts to call the Error and String methods on the underlying
// type the passed reflect.Value represents and outputes the result to Writer w.
//
// It handles panics in any called methods by catching and displaying the error
// as the formatted value.
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
// We need an interface to check if the type implements the error or
// Stringer interface. However, the reflect package won't give us an
// interface on certain things like unexported struct fields in order
// to enforce visibility rules. We use unsafe, when it's available,
// to bypass these restrictions since this package does not mutate the
// values.
if !v.CanInterface() {
if UnsafeDisabled {
return false
}
v = unsafeReflectValue(v)
}
// Choose whether or not to do error and Stringer interface lookups against
// the base type or a pointer to the base type depending on settings.
// Technically calling one of these methods with a pointer receiver can
// mutate the value, however, types which choose to satisify an error or
// Stringer interface with a pointer receiver should not be mutating their
// state inside these interface methods.
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
v = unsafeReflectValue(v)
}
if v.CanAddr() {
v = v.Addr()
}
// Is it an error or Stringer?
switch iface := v.Interface().(type) {
case error:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.Error()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.Error()))
return true
case fmt.Stringer:
defer catchPanic(w, v)
if cs.ContinueOnMethod {
w.Write(openParenBytes)
w.Write([]byte(iface.String()))
w.Write(closeParenBytes)
w.Write(spaceBytes)
return false
}
w.Write([]byte(iface.String()))
return true
}
return false
}
// printBool outputs a boolean value as true or false to Writer w.
func printBool(w io.Writer, val bool) {
if val {
w.Write(trueBytes)
} else {
w.Write(falseBytes)
}
}
// printInt outputs a signed integer value to Writer w.
func printInt(w io.Writer, val int64, base int) {
w.Write([]byte(strconv.FormatInt(val, base)))
}
// printUint outputs an unsigned integer value to Writer w.
func printUint(w io.Writer, val uint64, base int) {
w.Write([]byte(strconv.FormatUint(val, base)))
}
// printFloat outputs a floating point value using the specified precision,
// which is expected to be 32 or 64bit, to Writer w.
func printFloat(w io.Writer, val float64, precision int) {
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
}
// printComplex outputs a complex value using the specified float precision
// for the real and imaginary parts to Writer w.
func printComplex(w io.Writer, c complex128, floatPrecision int) {
r := real(c)
w.Write(openParenBytes)
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
i := imag(c)
if i >= 0 {
w.Write(plusBytes)
}
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
w.Write(iBytes)
w.Write(closeParenBytes)
}
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
// prefix to Writer w.
func printHexPtr(w io.Writer, p uintptr) {
// Null pointer.
num := uint64(p)
if num == 0 {
w.Write(nilAngleBytes)
return
}
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
buf := make([]byte, 18)
// It's simpler to construct the hex string right to left.
base := uint64(16)
i := len(buf) - 1
for num >= base {
buf[i] = hexDigits[num%base]
num /= base
i--
}
buf[i] = hexDigits[num]
// Add '0x' prefix.
i--
buf[i] = 'x'
i--
buf[i] = '0'
// Strip unused leading bytes.
buf = buf[i:]
w.Write(buf)
}
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
// elements to be sorted.
type valuesSorter struct {
values []reflect.Value
strings []string // either nil or same len and values
cs *ConfigState
}
// newValuesSorter initializes a valuesSorter instance, which holds a set of
// surrogate keys on which the data should be sorted. It uses flags in
// ConfigState to decide if and how to populate those surrogate keys.
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
vs := &valuesSorter{values: values, cs: cs}
if canSortSimply(vs.values[0].Kind()) {
return vs
}
if !cs.DisableMethods {
vs.strings = make([]string, len(values))
for i := range vs.values {
b := bytes.Buffer{}
if !handleMethods(cs, &b, vs.values[i]) {
vs.strings = nil
break
}
vs.strings[i] = b.String()
}
}
if vs.strings == nil && cs.SpewKeys {
vs.strings = make([]string, len(values))
for i := range vs.values {
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
}
}
return vs
}
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
// directly, or whether it should be considered for sorting by surrogate keys
// (if the ConfigState allows it).
func canSortSimply(kind reflect.Kind) bool {
// This switch parallels valueSortLess, except for the default case.
switch kind {
case reflect.Bool:
return true
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return true
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return true
case reflect.Float32, reflect.Float64:
return true
case reflect.String:
return true
case reflect.Uintptr:
return true
case reflect.Array:
return true
}
return false
}
// Len returns the number of values in the slice. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Len() int {
return len(s.values)
}
// Swap swaps the values at the passed indices. It is part of the
// sort.Interface implementation.
func (s *valuesSorter) Swap(i, j int) {
s.values[i], s.values[j] = s.values[j], s.values[i]
if s.strings != nil {
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
}
}
// valueSortLess returns whether the first value should sort before the second
// value. It is used by valueSorter.Less as part of the sort.Interface
// implementation.
func valueSortLess(a, b reflect.Value) bool {
switch a.Kind() {
case reflect.Bool:
return !a.Bool() && b.Bool()
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
return a.Int() < b.Int()
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
return a.Uint() < b.Uint()
case reflect.Float32, reflect.Float64:
return a.Float() < b.Float()
case reflect.String:
return a.String() < b.String()
case reflect.Uintptr:
return a.Uint() < b.Uint()
case reflect.Array:
// Compare the contents of both arrays.
l := a.Len()
for i := 0; i < l; i++ {
av := a.Index(i)
bv := b.Index(i)
if av.Interface() == bv.Interface() {
continue
}
return valueSortLess(av, bv)
}
}
return a.String() < b.String()
}
// Less returns whether the value at index i should sort before the
// value at index j. It is part of the sort.Interface implementation.
func (s *valuesSorter) Less(i, j int) bool {
if s.strings == nil {
return valueSortLess(s.values[i], s.values[j])
}
return s.strings[i] < s.strings[j]
}
// sortValues is a sort function that handles both native types and any type that
// can be converted to error or Stringer. Other inputs are sorted according to
// their Value.String() value to ensure display stability.
func sortValues(values []reflect.Value, cs *ConfigState) {
if len(values) == 0 {
return
}
sort.Sort(newValuesSorter(values, cs))
}

View File

@ -1,306 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"io"
"os"
)
// ConfigState houses the configuration options used by spew to format and
// display values. There is a global instance, Config, that is used to control
// all top-level Formatter and Dump functionality. Each ConfigState instance
// provides methods equivalent to the top-level functions.
//
// The zero value for ConfigState provides no indentation. You would typically
// want to set it to a space or a tab.
//
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
// with default settings. See the documentation of NewDefaultConfig for default
// values.
type ConfigState struct {
// Indent specifies the string to use for each indentation level. The
// global config instance that all top-level functions use set this to a
// single space by default. If you would like more indentation, you might
// set this to a tab with "\t" or perhaps two spaces with " ".
Indent string
// MaxDepth controls the maximum number of levels to descend into nested
// data structures. The default, 0, means there is no limit.
//
// NOTE: Circular data structures are properly detected, so it is not
// necessary to set this value unless you specifically want to limit deeply
// nested data structures.
MaxDepth int
// DisableMethods specifies whether or not error and Stringer interfaces are
// invoked for types that implement them.
DisableMethods bool
// DisablePointerMethods specifies whether or not to check for and invoke
// error and Stringer interfaces on types which only accept a pointer
// receiver when the current type is not a pointer.
//
// NOTE: This might be an unsafe action since calling one of these methods
// with a pointer receiver could technically mutate the value, however,
// in practice, types which choose to satisify an error or Stringer
// interface with a pointer receiver should not be mutating their state
// inside these interface methods. As a result, this option relies on
// access to the unsafe package, so it will not have any effect when
// running in environments without access to the unsafe package such as
// Google App Engine or with the "safe" build tag specified.
DisablePointerMethods bool
// DisablePointerAddresses specifies whether to disable the printing of
// pointer addresses. This is useful when diffing data structures in tests.
DisablePointerAddresses bool
// DisableCapacities specifies whether to disable the printing of capacities
// for arrays, slices, maps and channels. This is useful when diffing
// data structures in tests.
DisableCapacities bool
// ContinueOnMethod specifies whether or not recursion should continue once
// a custom error or Stringer interface is invoked. The default, false,
// means it will print the results of invoking the custom error or Stringer
// interface and return immediately instead of continuing to recurse into
// the internals of the data type.
//
// NOTE: This flag does not have any effect if method invocation is disabled
// via the DisableMethods or DisablePointerMethods options.
ContinueOnMethod bool
// SortKeys specifies map keys should be sorted before being printed. Use
// this to have a more deterministic, diffable output. Note that only
// native types (bool, int, uint, floats, uintptr and string) and types
// that support the error or Stringer interfaces (if methods are
// enabled) are supported, with other types sorted according to the
// reflect.Value.String() output which guarantees display stability.
SortKeys bool
// SpewKeys specifies that, as a last resort attempt, map keys should
// be spewed to strings and sorted by those strings. This is only
// considered if SortKeys is true.
SpewKeys bool
}
// Config is the active configuration of the top-level functions.
// The configuration can be changed by modifying the contents of spew.Config.
var Config = ConfigState{Indent: " "}
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the formatted string as a value that satisfies error. See NewFormatter
// for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, c.convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, c.convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, c.convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a Formatter interface returned by c.NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, c.convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
return fmt.Print(c.convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, c.convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
return fmt.Println(c.convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprint(a ...interface{}) string {
return fmt.Sprint(c.convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a Formatter interface returned by c.NewFormatter. It returns
// the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, c.convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a Formatter interface returned by c.NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
func (c *ConfigState) Sprintln(a ...interface{}) string {
return fmt.Sprintln(c.convertArgs(a)...)
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
c.Printf, c.Println, or c.Printf.
*/
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(c, v)
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
fdump(c, w, a...)
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func (c *ConfigState) Dump(a ...interface{}) {
fdump(c, os.Stdout, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func (c *ConfigState) Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(c, &buf, a...)
return buf.String()
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a spew Formatter interface using
// the ConfigState associated with s.
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = newFormatter(c, arg)
}
return formatters
}
// NewDefaultConfig returns a ConfigState with the following default settings.
//
// Indent: " "
// MaxDepth: 0
// DisableMethods: false
// DisablePointerMethods: false
// ContinueOnMethod: false
// SortKeys: false
func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "}
}

View File

@ -1,211 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
/*
Package spew implements a deep pretty printer for Go data structures to aid in
debugging.
A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using
Dump style)
There are two different approaches spew allows for dumping Go data structures:
* Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses
used to indirect to the final value
* A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q
along to fmt
Quick Start
This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...)
Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available
via the spew.Config global.
It is also possible to create a ConfigState instance that provides methods
equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details.
The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
* MaxDepth
Maximum number of levels to descend into nested data structures.
There is no limit by default.
* DisableMethods
Disables invocation of error and Stringer interface methods.
Method invocation is enabled by default.
* DisablePointerMethods
Disables invocation of error and Stringer interface methods on types
which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
* DisablePointerAddresses
DisablePointerAddresses specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests.
* DisableCapacities
DisableCapacities specifies whether to disable the printing of
capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
* ContinueOnMethod
Enables recursion into types after invoking error and Stringer interface
methods. Recursion after method invocation is disabled by default.
* SortKeys
Specifies map keys should be sorted before being printed. Use
this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
* SpewKeys
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
Dump Usage
Simply call spew.Dump with a list of variables you want to dump:
spew.Dump(myVar1, myVar2, ...)
You may also call spew.Fdump if you would prefer to output to an arbitrary
io.Writer. For example, to dump to standard error:
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...)
Sample Dump Output
See the Dump example for details on the setup of the types and variables being
shown here.
(main.Foo) {
unexportedField: (*main.Bar)(0xf84002e210)({
flag: (main.Flag) flagTwo,
data: (uintptr) <nil>
}),
ExportedField: (map[interface {}]interface {}) (len=1) {
(string) (len=3) "one": (bool) true
}
}
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown.
([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12|
}
Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The
formatter is useful for inline printing of smaller data types similar to the
standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
functions have syntax you are most likely already familiar with:
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Println(myVar, myVar2)
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
See the Index for the full list convenience functions.
Sample Formatter Output
Double pointer to a uint8:
%v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
See the Printf example for details on the setup of variables being shown
here.
Errors
Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information
inline with the output. Since spew is intended to provide deep pretty printing
capabilities on structures, it intentionally does not return any errors.
*/
package spew

View File

@ -1,509 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"encoding/hex"
"fmt"
"io"
"os"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
// uint8Type is a reflect.Type representing a uint8. It is used to
// convert cgo types to uint8 slices for hexdumping.
uint8Type = reflect.TypeOf(uint8(0))
// cCharRE is a regular expression that matches a cgo char.
// It is used to detect character arrays to hexdump them.
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
// char. It is used to detect unsigned character arrays to hexdump
// them.
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
// It is used to detect uint8_t arrays to hexdump them.
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
)
// dumpState contains information about the state of a dump operation.
type dumpState struct {
w io.Writer
depth int
pointers map[uintptr]int
ignoreNextType bool
ignoreNextIndent bool
cs *ConfigState
}
// indent performs indentation according to the depth level and cs.Indent
// option.
func (d *dumpState) indent() {
if d.ignoreNextIndent {
d.ignoreNextIndent = false
return
}
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
}
// unpackValue returns values inside of non-nil interfaces when possible.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface && !v.IsNil() {
v = v.Elem()
}
return v
}
// dumpPtr handles formatting of pointers by indirecting them as necessary.
func (d *dumpState) dumpPtr(v reflect.Value) {
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range d.pointers {
if depth >= d.depth {
delete(d.pointers, k)
}
}
// Keep list of all dereferenced pointers to show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by dereferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
cycleFound = true
indirects--
break
}
d.pointers[addr] = d.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type information.
d.w.Write(openParenBytes)
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
d.w.Write([]byte(ve.Type().String()))
d.w.Write(closeParenBytes)
// Display pointer information.
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
d.w.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
d.w.Write(pointerChainBytes)
}
printHexPtr(d.w, addr)
}
d.w.Write(closeParenBytes)
}
// Display dereferenced value.
d.w.Write(openParenBytes)
switch {
case nilFound:
d.w.Write(nilAngleBytes)
case cycleFound:
d.w.Write(circularBytes)
default:
d.ignoreNextType = true
d.dump(ve)
}
d.w.Write(closeParenBytes)
}
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
// reflection) arrays and slices are dumped in hexdump -C fashion.
func (d *dumpState) dumpSlice(v reflect.Value) {
// Determine whether this type should be hex dumped or not. Also,
// for types which should be hexdumped, try to use the underlying data
// first, then fall back to trying to convert them to a uint8 slice.
var buf []uint8
doConvert := false
doHexDump := false
numEntries := v.Len()
if numEntries > 0 {
vt := v.Index(0).Type()
vts := vt.String()
switch {
// C types that need to be converted.
case cCharRE.MatchString(vts):
fallthrough
case cUnsignedCharRE.MatchString(vts):
fallthrough
case cUint8tCharRE.MatchString(vts):
doConvert = true
// Try to use existing uint8 slices and fall back to converting
// and copying if that fails.
case vt.Kind() == reflect.Uint8:
// We need an addressable interface to convert the type
// to a byte slice. However, the reflect package won't
// give us an interface on certain things like
// unexported struct fields in order to enforce
// visibility rules. We use unsafe, when available, to
// bypass these restrictions since this package does not
// mutate the values.
vs := v
if !vs.CanInterface() || !vs.CanAddr() {
vs = unsafeReflectValue(vs)
}
if !UnsafeDisabled {
vs = vs.Slice(0, numEntries)
// Use the existing uint8 slice if it can be
// type asserted.
iface := vs.Interface()
if slice, ok := iface.([]uint8); ok {
buf = slice
doHexDump = true
break
}
}
// The underlying data needs to be converted if it can't
// be type asserted to a uint8 slice.
doConvert = true
}
// Copy and convert the underlying type if needed.
if doConvert && vt.ConvertibleTo(uint8Type) {
// Convert and copy each element into a uint8 byte
// slice.
buf = make([]uint8, numEntries)
for i := 0; i < numEntries; i++ {
vv := v.Index(i)
buf[i] = uint8(vv.Convert(uint8Type).Uint())
}
doHexDump = true
}
}
// Hexdump the entire slice as needed.
if doHexDump {
indent := strings.Repeat(d.cs.Indent, d.depth)
str := indent + hex.Dump(buf)
str = strings.Replace(str, "\n", "\n"+indent, -1)
str = strings.TrimRight(str, d.cs.Indent)
d.w.Write([]byte(str))
return
}
// Recursively call dump for each item.
for i := 0; i < numEntries; i++ {
d.dump(d.unpackValue(v.Index(i)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
// dump is the main workhorse for dumping a value. It uses the passed reflect
// value to figure out what kind of object we are dealing with and formats it
// appropriately. It is a recursive function, however circular data structures
// are detected and handled properly.
func (d *dumpState) dump(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
d.w.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
d.indent()
d.dumpPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !d.ignoreNextType {
d.indent()
d.w.Write(openParenBytes)
d.w.Write([]byte(v.Type().String()))
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
d.ignoreNextType = false
// Display length and capacity if the built-in len and cap functions
// work with the value's kind and the len/cap itself is non-zero.
valueLen, valueCap := 0, 0
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Chan:
valueLen, valueCap = v.Len(), v.Cap()
case reflect.Map, reflect.String:
valueLen = v.Len()
}
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
d.w.Write(openParenBytes)
if valueLen != 0 {
d.w.Write(lenEqualsBytes)
printInt(d.w, int64(valueLen), 10)
}
if !d.cs.DisableCapacities && valueCap != 0 {
if valueLen != 0 {
d.w.Write(spaceBytes)
}
d.w.Write(capEqualsBytes)
printInt(d.w, int64(valueCap), 10)
}
d.w.Write(closeParenBytes)
d.w.Write(spaceBytes)
}
// Call Stringer/error interfaces if they exist and the handle methods flag
// is enabled
if !d.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(d.cs, d.w, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(d.w, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(d.w, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(d.w, v.Uint(), 10)
case reflect.Float32:
printFloat(d.w, v.Float(), 32)
case reflect.Float64:
printFloat(d.w, v.Float(), 64)
case reflect.Complex64:
printComplex(d.w, v.Complex(), 32)
case reflect.Complex128:
printComplex(d.w, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
d.dumpSlice(v)
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.String:
d.w.Write([]byte(strconv.Quote(v.String())))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
d.w.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
d.w.Write(nilAngleBytes)
break
}
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
numEntries := v.Len()
keys := v.MapKeys()
if d.cs.SortKeys {
sortValues(keys, d.cs)
}
for i, key := range keys {
d.dump(d.unpackValue(key))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.MapIndex(key)))
if i < (numEntries - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Struct:
d.w.Write(openBraceNewlineBytes)
d.depth++
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
d.indent()
d.w.Write(maxNewlineBytes)
} else {
vt := v.Type()
numFields := v.NumField()
for i := 0; i < numFields; i++ {
d.indent()
vtf := vt.Field(i)
d.w.Write([]byte(vtf.Name))
d.w.Write(colonSpaceBytes)
d.ignoreNextIndent = true
d.dump(d.unpackValue(v.Field(i)))
if i < (numFields - 1) {
d.w.Write(commaNewlineBytes)
} else {
d.w.Write(newlineBytes)
}
}
}
d.depth--
d.indent()
d.w.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(d.w, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(d.w, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it in case any new
// types are added.
default:
if v.CanInterface() {
fmt.Fprintf(d.w, "%v", v.Interface())
} else {
fmt.Fprintf(d.w, "%v", v.String())
}
}
}
// fdump is a helper function to consolidate the logic from the various public
// methods which take varying writers and config states.
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
for _, arg := range a {
if arg == nil {
w.Write(interfaceBytes)
w.Write(spaceBytes)
w.Write(nilAngleBytes)
w.Write(newlineBytes)
continue
}
d := dumpState{w: w, cs: cs}
d.pointers = make(map[uintptr]int)
d.dump(reflect.ValueOf(arg))
d.w.Write(newlineBytes)
}
}
// Fdump formats and displays the passed arguments to io.Writer w. It formats
// exactly the same as Dump.
func Fdump(w io.Writer, a ...interface{}) {
fdump(&Config, w, a...)
}
// Sdump returns a string with the passed arguments formatted exactly the same
// as Dump.
func Sdump(a ...interface{}) string {
var buf bytes.Buffer
fdump(&Config, &buf, a...)
return buf.String()
}
/*
Dump displays the passed parameters to standard out with newlines, customizable
indentation, and additional debug information such as complete types and all
pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt
package:
* Pointers are dereferenced and followed
* Circular data structures are detected and handled properly
* Custom Stringer/error interfaces are optionally invoked, including
on unexported types
* Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer
variables
* Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation.
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
get the formatted result as a string.
*/
func Dump(a ...interface{}) {
fdump(&Config, os.Stdout, a...)
}

View File

@ -1,419 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"bytes"
"fmt"
"reflect"
"strconv"
"strings"
)
// supportedFlags is a list of all the character flags supported by fmt package.
const supportedFlags = "0-+# "
// formatState implements the fmt.Formatter interface and contains information
// about the state of a formatting operation. The NewFormatter function can
// be used to get a new Formatter which can be used directly as arguments
// in standard fmt package printing calls.
type formatState struct {
value interface{}
fs fmt.State
depth int
pointers map[uintptr]int
ignoreNextType bool
cs *ConfigState
}
// buildDefaultFormat recreates the original format string without precision
// and width information to pass in to fmt.Sprintf in the case of an
// unrecognized type. Unless new types are added to the language, this
// function won't ever be called.
func (f *formatState) buildDefaultFormat() (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
buf.WriteRune('v')
format = buf.String()
return format
}
// constructOrigFormat recreates the original format string including precision
// and width information to pass along to the standard fmt package. This allows
// automatic deferral of all format strings this package doesn't support.
func (f *formatState) constructOrigFormat(verb rune) (format string) {
buf := bytes.NewBuffer(percentBytes)
for _, flag := range supportedFlags {
if f.fs.Flag(int(flag)) {
buf.WriteRune(flag)
}
}
if width, ok := f.fs.Width(); ok {
buf.WriteString(strconv.Itoa(width))
}
if precision, ok := f.fs.Precision(); ok {
buf.Write(precisionBytes)
buf.WriteString(strconv.Itoa(precision))
}
buf.WriteRune(verb)
format = buf.String()
return format
}
// unpackValue returns values inside of non-nil interfaces when possible and
// ensures that types for values which have been unpacked from an interface
// are displayed when the show types flag is also set.
// This is useful for data types like structs, arrays, slices, and maps which
// can contain varying types packed inside an interface.
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
f.ignoreNextType = false
if !v.IsNil() {
v = v.Elem()
}
}
return v
}
// formatPtr handles formatting of pointers by indirecting them as necessary.
func (f *formatState) formatPtr(v reflect.Value) {
// Display nil if top level pointer is nil.
showTypes := f.fs.Flag('#')
if v.IsNil() && (!showTypes || f.ignoreNextType) {
f.fs.Write(nilAngleBytes)
return
}
// Remove pointers at or below the current depth from map used to detect
// circular refs.
for k, depth := range f.pointers {
if depth >= f.depth {
delete(f.pointers, k)
}
}
// Keep list of all dereferenced pointers to possibly show later.
pointerChain := make([]uintptr, 0)
// Figure out how many levels of indirection there are by derferencing
// pointers and unpacking interfaces down the chain while detecting circular
// references.
nilFound := false
cycleFound := false
indirects := 0
ve := v
for ve.Kind() == reflect.Ptr {
if ve.IsNil() {
nilFound = true
break
}
indirects++
addr := ve.Pointer()
pointerChain = append(pointerChain, addr)
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
cycleFound = true
indirects--
break
}
f.pointers[addr] = f.depth
ve = ve.Elem()
if ve.Kind() == reflect.Interface {
if ve.IsNil() {
nilFound = true
break
}
ve = ve.Elem()
}
}
// Display type or indirection level depending on flags.
if showTypes && !f.ignoreNextType {
f.fs.Write(openParenBytes)
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
f.fs.Write([]byte(ve.Type().String()))
f.fs.Write(closeParenBytes)
} else {
if nilFound || cycleFound {
indirects += strings.Count(ve.Type().String(), "*")
}
f.fs.Write(openAngleBytes)
f.fs.Write([]byte(strings.Repeat("*", indirects)))
f.fs.Write(closeAngleBytes)
}
// Display pointer information depending on flags.
if f.fs.Flag('+') && (len(pointerChain) > 0) {
f.fs.Write(openParenBytes)
for i, addr := range pointerChain {
if i > 0 {
f.fs.Write(pointerChainBytes)
}
printHexPtr(f.fs, addr)
}
f.fs.Write(closeParenBytes)
}
// Display dereferenced value.
switch {
case nilFound:
f.fs.Write(nilAngleBytes)
case cycleFound:
f.fs.Write(circularShortBytes)
default:
f.ignoreNextType = true
f.format(ve)
}
}
// format is the main workhorse for providing the Formatter interface. It
// uses the passed reflect value to figure out what kind of object we are
// dealing with and formats it appropriately. It is a recursive function,
// however circular data structures are detected and handled properly.
func (f *formatState) format(v reflect.Value) {
// Handle invalid reflect values immediately.
kind := v.Kind()
if kind == reflect.Invalid {
f.fs.Write(invalidAngleBytes)
return
}
// Handle pointers specially.
if kind == reflect.Ptr {
f.formatPtr(v)
return
}
// Print type information unless already handled elsewhere.
if !f.ignoreNextType && f.fs.Flag('#') {
f.fs.Write(openParenBytes)
f.fs.Write([]byte(v.Type().String()))
f.fs.Write(closeParenBytes)
}
f.ignoreNextType = false
// Call Stringer/error interfaces if they exist and the handle methods
// flag is enabled.
if !f.cs.DisableMethods {
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
if handled := handleMethods(f.cs, f.fs, v); handled {
return
}
}
}
switch kind {
case reflect.Invalid:
// Do nothing. We should never get here since invalid has already
// been handled above.
case reflect.Bool:
printBool(f.fs, v.Bool())
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
printInt(f.fs, v.Int(), 10)
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
printUint(f.fs, v.Uint(), 10)
case reflect.Float32:
printFloat(f.fs, v.Float(), 32)
case reflect.Float64:
printFloat(f.fs, v.Float(), 64)
case reflect.Complex64:
printComplex(f.fs, v.Complex(), 32)
case reflect.Complex128:
printComplex(f.fs, v.Complex(), 64)
case reflect.Slice:
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
fallthrough
case reflect.Array:
f.fs.Write(openBracketBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
numEntries := v.Len()
for i := 0; i < numEntries; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(v.Index(i)))
}
}
f.depth--
f.fs.Write(closeBracketBytes)
case reflect.String:
f.fs.Write([]byte(v.String()))
case reflect.Interface:
// The only time we should get here is for nil interfaces due to
// unpackValue calls.
if v.IsNil() {
f.fs.Write(nilAngleBytes)
}
case reflect.Ptr:
// Do nothing. We should never get here since pointers have already
// been handled above.
case reflect.Map:
// nil maps should be indicated as different than empty maps
if v.IsNil() {
f.fs.Write(nilAngleBytes)
break
}
f.fs.Write(openMapBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
keys := v.MapKeys()
if f.cs.SortKeys {
sortValues(keys, f.cs)
}
for i, key := range keys {
if i > 0 {
f.fs.Write(spaceBytes)
}
f.ignoreNextType = true
f.format(f.unpackValue(key))
f.fs.Write(colonBytes)
f.ignoreNextType = true
f.format(f.unpackValue(v.MapIndex(key)))
}
}
f.depth--
f.fs.Write(closeMapBytes)
case reflect.Struct:
numFields := v.NumField()
f.fs.Write(openBraceBytes)
f.depth++
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
f.fs.Write(maxShortBytes)
} else {
vt := v.Type()
for i := 0; i < numFields; i++ {
if i > 0 {
f.fs.Write(spaceBytes)
}
vtf := vt.Field(i)
if f.fs.Flag('+') || f.fs.Flag('#') {
f.fs.Write([]byte(vtf.Name))
f.fs.Write(colonBytes)
}
f.format(f.unpackValue(v.Field(i)))
}
}
f.depth--
f.fs.Write(closeBraceBytes)
case reflect.Uintptr:
printHexPtr(f.fs, uintptr(v.Uint()))
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
printHexPtr(f.fs, v.Pointer())
// There were not any other types at the time this code was written, but
// fall back to letting the default fmt package handle it if any get added.
default:
format := f.buildDefaultFormat()
if v.CanInterface() {
fmt.Fprintf(f.fs, format, v.Interface())
} else {
fmt.Fprintf(f.fs, format, v.String())
}
}
}
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
// details.
func (f *formatState) Format(fs fmt.State, verb rune) {
f.fs = fs
// Use standard formatting for verbs that are not v.
if verb != 'v' {
format := f.constructOrigFormat(verb)
fmt.Fprintf(fs, format, f.value)
return
}
if f.value == nil {
if fs.Flag('#') {
fs.Write(interfaceBytes)
}
fs.Write(nilAngleBytes)
return
}
f.format(reflect.ValueOf(f.value))
}
// newFormatter is a helper function to consolidate the logic from the various
// public methods which take varying config states.
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
fs := &formatState{value: v, cs: cs}
fs.pointers = make(map[uintptr]int)
return fs
}
/*
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
interface. As a result, it integrates cleanly with standard fmt package
printing functions. The formatter is useful for inline printing of smaller data
types similar to the standard %v format specifier.
The custom formatter only responds to the %v (most compact), %+v (adds pointer
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
combinations. Any other verbs such as %x and %q will be sent to the the
standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter).
Typically this function shouldn't be called directly. It is much easier to make
use of the custom formatter by calling one of the convenience functions such as
Printf, Println, or Fprintf.
*/
func NewFormatter(v interface{}) fmt.Formatter {
return newFormatter(&Config, v)
}

View File

@ -1,148 +0,0 @@
/*
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package spew
import (
"fmt"
"io"
)
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the formatted string as a value that satisfies error. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Errorf(format string, a ...interface{}) (err error) {
return fmt.Errorf(format, convertArgs(a)...)
}
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprint(w, convertArgs(a)...)
}
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
return fmt.Fprintf(w, format, convertArgs(a)...)
}
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
// passed with a default Formatter interface returned by NewFormatter. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
return fmt.Fprintln(w, convertArgs(a)...)
}
// Print is a wrapper for fmt.Print that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
func Print(a ...interface{}) (n int, err error) {
return fmt.Print(convertArgs(a)...)
}
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Printf(format string, a ...interface{}) (n int, err error) {
return fmt.Printf(format, convertArgs(a)...)
}
// Println is a wrapper for fmt.Println that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the number of bytes written and any write error encountered. See
// NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
func Println(a ...interface{}) (n int, err error) {
return fmt.Println(convertArgs(a)...)
}
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprint(a ...interface{}) string {
return fmt.Sprint(convertArgs(a)...)
}
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
// passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintf(format string, a ...interface{}) string {
return fmt.Sprintf(format, convertArgs(a)...)
}
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
// were passed with a default Formatter interface returned by NewFormatter. It
// returns the resulting string. See NewFormatter for formatting details.
//
// This function is shorthand for the following syntax:
//
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
func Sprintln(a ...interface{}) string {
return fmt.Sprintln(convertArgs(a)...)
}
// convertArgs accepts a slice of arguments and returns a slice of the same
// length with each argument converted to a default spew Formatter interface.
func convertArgs(args []interface{}) (formatters []interface{}) {
formatters = make([]interface{}, len(args))
for index, arg := range args {
formatters[index] = NewFormatter(arg)
}
return formatters
}

View File

@ -1,71 +0,0 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
restful.html
*.out
tmp.prof
go-restful.test
examples/restful-basic-authentication
examples/restful-encoding-filter
examples/restful-filters
examples/restful-hello-world
examples/restful-resource-functions
examples/restful-serve-static
examples/restful-user-service
*.DS_Store
examples/restful-user-resource
examples/restful-multi-containers
examples/restful-form-handling
examples/restful-CORS-filter
examples/restful-options-filter
examples/restful-curly-router
examples/restful-cpuprofiler-service
examples/restful-pre-post-filters
curly.prof
examples/restful-NCSA-logging
examples/restful-html-template
s.html
restful-path-tail
.idea

View File

@ -1,6 +0,0 @@
language: go
go:
- 1.x
script: go test -v

View File

@ -1,286 +0,0 @@
# Change history of go-restful
v2.11.1
- fix WriteError return value (#415)
v2.11.0
- allow prefix and suffix in path variable expression (#414)
v2.9.6
- support google custome verb (#413)
v2.9.5
- fix panic in Response.WriteError if err == nil
v2.9.4
- fix issue #400 , parsing mime type quality
- Route Builder added option for contentEncodingEnabled (#398)
v2.9.3
- Avoid return of 415 Unsupported Media Type when request body is empty (#396)
v2.9.2
- Reduce allocations in per-request methods to improve performance (#395)
v2.9.1
- Fix issue with default responses and invalid status code 0. (#393)
v2.9.0
- add per Route content encoding setting (overrides container setting)
v2.8.0
- add Request.QueryParameters()
- add json-iterator (via build tag)
- disable vgo module (until log is moved)
v2.7.1
- add vgo module
v2.6.1
- add JSONNewDecoderFunc to allow custom JSON Decoder usage (go 1.10+)
v2.6.0
- Make JSR 311 routing and path param processing consistent
- Adding description to RouteBuilder.Reads()
- Update example for Swagger12 and OpenAPI
2017-09-13
- added route condition functions using `.If(func)` in route building.
2017-02-16
- solved issue #304, make operation names unique
2017-01-30
[IMPORTANT] For swagger users, change your import statement to:
swagger "github.com/emicklei/go-restful-swagger12"
- moved swagger 1.2 code to go-restful-swagger12
- created TAG 2.0.0
2017-01-27
- remove defer request body close
- expose Dispatch for testing filters and Routefunctions
- swagger response model cannot be array
- created TAG 1.0.0
2016-12-22
- (API change) Remove code related to caching request content. Removes SetCacheReadEntity(doCache bool)
2016-11-26
- Default change! now use CurlyRouter (was RouterJSR311)
- Default change! no more caching of request content
- Default change! do not recover from panics
2016-09-22
- fix the DefaultRequestContentType feature
2016-02-14
- take the qualify factor of the Accept header mediatype into account when deciding the contentype of the response
- add constructors for custom entity accessors for xml and json
2015-09-27
- rename new WriteStatusAnd... to WriteHeaderAnd... for consistency
2015-09-25
- fixed problem with changing Header after WriteHeader (issue 235)
2015-09-14
- changed behavior of WriteHeader (immediate write) and WriteEntity (no status write)
- added support for custom EntityReaderWriters.
2015-08-06
- add support for reading entities from compressed request content
- use sync.Pool for compressors of http response and request body
- add Description to Parameter for documentation in Swagger UI
2015-03-20
- add configurable logging
2015-03-18
- if not specified, the Operation is derived from the Route function
2015-03-17
- expose Parameter creation functions
- make trace logger an interface
- fix OPTIONSFilter
- customize rendering of ServiceError
- JSR311 router now handles wildcards
- add Notes to Route
2014-11-27
- (api add) PrettyPrint per response. (as proposed in #167)
2014-11-12
- (api add) ApiVersion(.) for documentation in Swagger UI
2014-11-10
- (api change) struct fields tagged with "description" show up in Swagger UI
2014-10-31
- (api change) ReturnsError -> Returns
- (api add) RouteBuilder.Do(aBuilder) for DRY use of RouteBuilder
- fix swagger nested structs
- sort Swagger response messages by code
2014-10-23
- (api add) ReturnsError allows you to document Http codes in swagger
- fixed problem with greedy CurlyRouter
- (api add) Access-Control-Max-Age in CORS
- add tracing functionality (injectable) for debugging purposes
- support JSON parse 64bit int
- fix empty parameters for swagger
- WebServicesUrl is now optional for swagger
- fixed duplicate AccessControlAllowOrigin in CORS
- (api change) expose ServeMux in container
- (api add) added AllowedDomains in CORS
- (api add) ParameterNamed for detailed documentation
2014-04-16
- (api add) expose constructor of Request for testing.
2014-06-27
- (api add) ParameterNamed gives access to a Parameter definition and its data (for further specification).
- (api add) SetCacheReadEntity allow scontrol over whether or not the request body is being cached (default true for compatibility reasons).
2014-07-03
- (api add) CORS can be configured with a list of allowed domains
2014-03-12
- (api add) Route path parameters can use wildcard or regular expressions. (requires CurlyRouter)
2014-02-26
- (api add) Request now provides information about the matched Route, see method SelectedRoutePath
2014-02-17
- (api change) renamed parameter constants (go-lint checks)
2014-01-10
- (api add) support for CloseNotify, see http://golang.org/pkg/net/http/#CloseNotifier
2014-01-07
- (api change) Write* methods in Response now return the error or nil.
- added example of serving HTML from a Go template.
- fixed comparing Allowed headers in CORS (is now case-insensitive)
2013-11-13
- (api add) Response knows how many bytes are written to the response body.
2013-10-29
- (api add) RecoverHandler(handler RecoverHandleFunction) to change how panic recovery is handled. Default behavior is to log and return a stacktrace. This may be a security issue as it exposes sourcecode information.
2013-10-04
- (api add) Response knows what HTTP status has been written
- (api add) Request can have attributes (map of string->interface, also called request-scoped variables
2013-09-12
- (api change) Router interface simplified
- Implemented CurlyRouter, a Router that does not use|allow regular expressions in paths
2013-08-05
- add OPTIONS support
- add CORS support
2013-08-27
- fixed some reported issues (see github)
- (api change) deprecated use of WriteError; use WriteErrorString instead
2014-04-15
- (fix) v1.0.1 tag: fix Issue 111: WriteErrorString
2013-08-08
- (api add) Added implementation Container: a WebServices collection with its own http.ServeMux allowing multiple endpoints per program. Existing uses of go-restful will register their services to the DefaultContainer.
- (api add) the swagger package has be extended to have a UI per container.
- if panic is detected then a small stack trace is printed (thanks to runner-mei)
- (api add) WriteErrorString to Response
Important API changes:
- (api remove) package variable DoNotRecover no longer works ; use restful.DefaultContainer.DoNotRecover(true) instead.
- (api remove) package variable EnableContentEncoding no longer works ; use restful.DefaultContainer.EnableContentEncoding(true) instead.
2013-07-06
- (api add) Added support for response encoding (gzip and deflate(zlib)). This feature is disabled on default (for backwards compatibility). Use restful.EnableContentEncoding = true in your initialization to enable this feature.
2013-06-19
- (improve) DoNotRecover option, moved request body closer, improved ReadEntity
2013-06-03
- (api change) removed Dispatcher interface, hide PathExpression
- changed receiver names of type functions to be more idiomatic Go
2013-06-02
- (optimize) Cache the RegExp compilation of Paths.
2013-05-22
- (api add) Added support for request/response filter functions
2013-05-18
- (api add) Added feature to change the default Http Request Dispatch function (travis cline)
- (api change) Moved Swagger Webservice to swagger package (see example restful-user)
[2012-11-14 .. 2013-05-18>
- See https://github.com/emicklei/go-restful/commits
2012-11-14
- Initial commit

View File

@ -1,22 +0,0 @@
Copyright (c) 2012,2013 Ernest Micklei
MIT License
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.

View File

@ -1,7 +0,0 @@
all: test
test:
go test -v .
ex:
cd examples && ls *.go | xargs go build -o /tmp/ignore

View File

@ -1,88 +0,0 @@
go-restful
==========
package for building REST-style Web Services using Google Go
[![Build Status](https://travis-ci.org/emicklei/go-restful.png)](https://travis-ci.org/emicklei/go-restful)
[![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/go-restful)](https://goreportcard.com/report/github.com/emicklei/go-restful)
[![GoDoc](https://godoc.org/github.com/emicklei/go-restful?status.svg)](https://godoc.org/github.com/emicklei/go-restful)
- [Code examples](https://github.com/emicklei/go-restful/tree/master/examples)
REST asks developers to use HTTP methods explicitly and in a way that's consistent with the protocol definition. This basic REST design principle establishes a one-to-one mapping between create, read, update, and delete (CRUD) operations and HTTP methods. According to this mapping:
- GET = Retrieve a representation of a resource
- POST = Create if you are sending content to the server to create a subordinate of the specified resource collection, using some server-side algorithm.
- PUT = Create if you are sending the full content of the specified resource (URI).
- PUT = Update if you are updating the full content of the specified resource.
- DELETE = Delete if you are requesting the server to delete the resource
- PATCH = Update partial content of a resource
- OPTIONS = Get information about the communication options for the request URI
### Example
```Go
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML)
ws.Route(ws.GET("/{user-id}").To(u.findUser).
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Writes(User{}))
...
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
...
}
```
[Full API of a UserResource](https://github.com/emicklei/go-restful/tree/master/examples/restful-user-resource.go)
### Features
- Routes for request &#8594; function mapping with path parameter (e.g. {id} but also prefix_{var} and {var}_suffix) support
- Configurable router:
- (default) Fast routing algorithm that allows static elements, [google custom method](https://cloud.google.com/apis/design/custom_methods), regular expressions and dynamic parameters in the URL path (e.g. /resource/name:customVerb, /meetings/{id} or /static/{subpath:*})
- Routing algorithm after [JSR311](http://jsr311.java.net/nonav/releases/1.1/spec/spec.html) that is implemented using (but does **not** accept) regular expressions
- Request API for reading structs from JSON/XML and accesing parameters (path,query,header)
- Response API for writing structs to JSON/XML and setting headers
- Customizable encoding using EntityReaderWriter registration
- Filters for intercepting the request &#8594; response flow on Service or Route level
- Request-scoped variables using attributes
- Containers for WebServices on different HTTP endpoints
- Content encoding (gzip,deflate) of request and response payloads
- Automatic responses on OPTIONS (using a filter)
- Automatic CORS request handling (using a filter)
- API declaration for Swagger UI ([go-restful-openapi](https://github.com/emicklei/go-restful-openapi), see [go-restful-swagger12](https://github.com/emicklei/go-restful-swagger12))
- Panic recovery to produce HTTP 500, customizable using RecoverHandler(...)
- Route errors produce HTTP 404/405/406/415 errors, customizable using ServiceErrorHandler(...)
- Configurable (trace) logging
- Customizable gzip/deflate readers and writers using CompressorProvider registration
## How to customize
There are several hooks to customize the behavior of the go-restful package.
- Router algorithm
- Panic recovery
- JSON decoder
- Trace logging
- Compression
- Encoders for other serializers
- Use [jsoniter](https://github.com/json-iterator/go) by build this package using a tag, e.g. `go build -tags=jsoniter .`
TODO: write examples of these.
## Resources
- [Example posted on blog](http://ernestmicklei.com/2012/11/go-restful-first-working-example/)
- [Design explained on blog](http://ernestmicklei.com/2012/11/go-restful-api-design/)
- [sourcegraph](https://sourcegraph.com/github.com/emicklei/go-restful)
- [showcase: Zazkia - tcp proxy for testing resiliency](https://github.com/emicklei/zazkia)
- [showcase: Mora - MongoDB REST Api server](https://github.com/emicklei/mora)
Type ```git shortlog -s``` for a full list of contributors.
© 2012 - 2018, http://ernestmicklei.com. MIT License. Contributions are welcome.

View File

@ -1 +0,0 @@
{"SkipDirs": ["examples"]}

View File

@ -1,10 +0,0 @@
#go test -run=none -file bench_test.go -test.bench . -cpuprofile=bench_test.out
go test -c
./go-restful.test -test.run=none -test.cpuprofile=tmp.prof -test.bench=BenchmarkMany
./go-restful.test -test.run=none -test.cpuprofile=curly.prof -test.bench=BenchmarkManyCurly
#go tool pprof go-restful.test tmp.prof
go tool pprof go-restful.test curly.prof

View File

@ -1,123 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"bufio"
"compress/gzip"
"compress/zlib"
"errors"
"io"
"net"
"net/http"
"strings"
)
// OBSOLETE : use restful.DefaultContainer.EnableContentEncoding(true) to change this setting.
var EnableContentEncoding = false
// CompressingResponseWriter is a http.ResponseWriter that can perform content encoding (gzip and zlib)
type CompressingResponseWriter struct {
writer http.ResponseWriter
compressor io.WriteCloser
encoding string
}
// Header is part of http.ResponseWriter interface
func (c *CompressingResponseWriter) Header() http.Header {
return c.writer.Header()
}
// WriteHeader is part of http.ResponseWriter interface
func (c *CompressingResponseWriter) WriteHeader(status int) {
c.writer.WriteHeader(status)
}
// Write is part of http.ResponseWriter interface
// It is passed through the compressor
func (c *CompressingResponseWriter) Write(bytes []byte) (int, error) {
if c.isCompressorClosed() {
return -1, errors.New("Compressing error: tried to write data using closed compressor")
}
return c.compressor.Write(bytes)
}
// CloseNotify is part of http.CloseNotifier interface
func (c *CompressingResponseWriter) CloseNotify() <-chan bool {
return c.writer.(http.CloseNotifier).CloseNotify()
}
// Close the underlying compressor
func (c *CompressingResponseWriter) Close() error {
if c.isCompressorClosed() {
return errors.New("Compressing error: tried to close already closed compressor")
}
c.compressor.Close()
if ENCODING_GZIP == c.encoding {
currentCompressorProvider.ReleaseGzipWriter(c.compressor.(*gzip.Writer))
}
if ENCODING_DEFLATE == c.encoding {
currentCompressorProvider.ReleaseZlibWriter(c.compressor.(*zlib.Writer))
}
// gc hint needed?
c.compressor = nil
return nil
}
func (c *CompressingResponseWriter) isCompressorClosed() bool {
return nil == c.compressor
}
// Hijack implements the Hijacker interface
// This is especially useful when combining Container.EnabledContentEncoding
// in combination with websockets (for instance gorilla/websocket)
func (c *CompressingResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
hijacker, ok := c.writer.(http.Hijacker)
if !ok {
return nil, nil, errors.New("ResponseWriter doesn't support Hijacker interface")
}
return hijacker.Hijack()
}
// WantsCompressedResponse reads the Accept-Encoding header to see if and which encoding is requested.
func wantsCompressedResponse(httpRequest *http.Request) (bool, string) {
header := httpRequest.Header.Get(HEADER_AcceptEncoding)
gi := strings.Index(header, ENCODING_GZIP)
zi := strings.Index(header, ENCODING_DEFLATE)
// use in order of appearance
if gi == -1 {
return zi != -1, ENCODING_DEFLATE
} else if zi == -1 {
return gi != -1, ENCODING_GZIP
} else {
if gi < zi {
return true, ENCODING_GZIP
}
return true, ENCODING_DEFLATE
}
}
// NewCompressingResponseWriter create a CompressingResponseWriter for a known encoding = {gzip,deflate}
func NewCompressingResponseWriter(httpWriter http.ResponseWriter, encoding string) (*CompressingResponseWriter, error) {
httpWriter.Header().Set(HEADER_ContentEncoding, encoding)
c := new(CompressingResponseWriter)
c.writer = httpWriter
var err error
if ENCODING_GZIP == encoding {
w := currentCompressorProvider.AcquireGzipWriter()
w.Reset(httpWriter)
c.compressor = w
c.encoding = ENCODING_GZIP
} else if ENCODING_DEFLATE == encoding {
w := currentCompressorProvider.AcquireZlibWriter()
w.Reset(httpWriter)
c.compressor = w
c.encoding = ENCODING_DEFLATE
} else {
return nil, errors.New("Unknown encoding:" + encoding)
}
return c, err
}

View File

@ -1,103 +0,0 @@
package restful
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"compress/gzip"
"compress/zlib"
)
// BoundedCachedCompressors is a CompressorProvider that uses a cache with a fixed amount
// of writers and readers (resources).
// If a new resource is acquired and all are in use, it will return a new unmanaged resource.
type BoundedCachedCompressors struct {
gzipWriters chan *gzip.Writer
gzipReaders chan *gzip.Reader
zlibWriters chan *zlib.Writer
writersCapacity int
readersCapacity int
}
// NewBoundedCachedCompressors returns a new, with filled cache, BoundedCachedCompressors.
func NewBoundedCachedCompressors(writersCapacity, readersCapacity int) *BoundedCachedCompressors {
b := &BoundedCachedCompressors{
gzipWriters: make(chan *gzip.Writer, writersCapacity),
gzipReaders: make(chan *gzip.Reader, readersCapacity),
zlibWriters: make(chan *zlib.Writer, writersCapacity),
writersCapacity: writersCapacity,
readersCapacity: readersCapacity,
}
for ix := 0; ix < writersCapacity; ix++ {
b.gzipWriters <- newGzipWriter()
b.zlibWriters <- newZlibWriter()
}
for ix := 0; ix < readersCapacity; ix++ {
b.gzipReaders <- newGzipReader()
}
return b
}
// AcquireGzipWriter returns an resettable *gzip.Writer. Needs to be released.
func (b *BoundedCachedCompressors) AcquireGzipWriter() *gzip.Writer {
var writer *gzip.Writer
select {
case writer, _ = <-b.gzipWriters:
default:
// return a new unmanaged one
writer = newGzipWriter()
}
return writer
}
// ReleaseGzipWriter accepts a writer (does not have to be one that was cached)
// only when the cache has room for it. It will ignore it otherwise.
func (b *BoundedCachedCompressors) ReleaseGzipWriter(w *gzip.Writer) {
// forget the unmanaged ones
if len(b.gzipWriters) < b.writersCapacity {
b.gzipWriters <- w
}
}
// AcquireGzipReader returns a *gzip.Reader. Needs to be released.
func (b *BoundedCachedCompressors) AcquireGzipReader() *gzip.Reader {
var reader *gzip.Reader
select {
case reader, _ = <-b.gzipReaders:
default:
// return a new unmanaged one
reader = newGzipReader()
}
return reader
}
// ReleaseGzipReader accepts a reader (does not have to be one that was cached)
// only when the cache has room for it. It will ignore it otherwise.
func (b *BoundedCachedCompressors) ReleaseGzipReader(r *gzip.Reader) {
// forget the unmanaged ones
if len(b.gzipReaders) < b.readersCapacity {
b.gzipReaders <- r
}
}
// AcquireZlibWriter returns an resettable *zlib.Writer. Needs to be released.
func (b *BoundedCachedCompressors) AcquireZlibWriter() *zlib.Writer {
var writer *zlib.Writer
select {
case writer, _ = <-b.zlibWriters:
default:
// return a new unmanaged one
writer = newZlibWriter()
}
return writer
}
// ReleaseZlibWriter accepts a writer (does not have to be one that was cached)
// only when the cache has room for it. It will ignore it otherwise.
func (b *BoundedCachedCompressors) ReleaseZlibWriter(w *zlib.Writer) {
// forget the unmanaged ones
if len(b.zlibWriters) < b.writersCapacity {
b.zlibWriters <- w
}
}

View File

@ -1,91 +0,0 @@
package restful
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"bytes"
"compress/gzip"
"compress/zlib"
"sync"
)
// SyncPoolCompessors is a CompressorProvider that use the standard sync.Pool.
type SyncPoolCompessors struct {
GzipWriterPool *sync.Pool
GzipReaderPool *sync.Pool
ZlibWriterPool *sync.Pool
}
// NewSyncPoolCompessors returns a new ("empty") SyncPoolCompessors.
func NewSyncPoolCompessors() *SyncPoolCompessors {
return &SyncPoolCompessors{
GzipWriterPool: &sync.Pool{
New: func() interface{} { return newGzipWriter() },
},
GzipReaderPool: &sync.Pool{
New: func() interface{} { return newGzipReader() },
},
ZlibWriterPool: &sync.Pool{
New: func() interface{} { return newZlibWriter() },
},
}
}
func (s *SyncPoolCompessors) AcquireGzipWriter() *gzip.Writer {
return s.GzipWriterPool.Get().(*gzip.Writer)
}
func (s *SyncPoolCompessors) ReleaseGzipWriter(w *gzip.Writer) {
s.GzipWriterPool.Put(w)
}
func (s *SyncPoolCompessors) AcquireGzipReader() *gzip.Reader {
return s.GzipReaderPool.Get().(*gzip.Reader)
}
func (s *SyncPoolCompessors) ReleaseGzipReader(r *gzip.Reader) {
s.GzipReaderPool.Put(r)
}
func (s *SyncPoolCompessors) AcquireZlibWriter() *zlib.Writer {
return s.ZlibWriterPool.Get().(*zlib.Writer)
}
func (s *SyncPoolCompessors) ReleaseZlibWriter(w *zlib.Writer) {
s.ZlibWriterPool.Put(w)
}
func newGzipWriter() *gzip.Writer {
// create with an empty bytes writer; it will be replaced before using the gzipWriter
writer, err := gzip.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
if err != nil {
panic(err.Error())
}
return writer
}
func newGzipReader() *gzip.Reader {
// create with an empty reader (but with GZIP header); it will be replaced before using the gzipReader
// we can safely use currentCompressProvider because it is set on package initialization.
w := currentCompressorProvider.AcquireGzipWriter()
defer currentCompressorProvider.ReleaseGzipWriter(w)
b := new(bytes.Buffer)
w.Reset(b)
w.Flush()
w.Close()
reader, err := gzip.NewReader(bytes.NewReader(b.Bytes()))
if err != nil {
panic(err.Error())
}
return reader
}
func newZlibWriter() *zlib.Writer {
writer, err := zlib.NewWriterLevel(new(bytes.Buffer), gzip.BestSpeed)
if err != nil {
panic(err.Error())
}
return writer
}

View File

@ -1,54 +0,0 @@
package restful
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"compress/gzip"
"compress/zlib"
)
// CompressorProvider describes a component that can provider compressors for the std methods.
type CompressorProvider interface {
// Returns a *gzip.Writer which needs to be released later.
// Before using it, call Reset().
AcquireGzipWriter() *gzip.Writer
// Releases an acquired *gzip.Writer.
ReleaseGzipWriter(w *gzip.Writer)
// Returns a *gzip.Reader which needs to be released later.
AcquireGzipReader() *gzip.Reader
// Releases an acquired *gzip.Reader.
ReleaseGzipReader(w *gzip.Reader)
// Returns a *zlib.Writer which needs to be released later.
// Before using it, call Reset().
AcquireZlibWriter() *zlib.Writer
// Releases an acquired *zlib.Writer.
ReleaseZlibWriter(w *zlib.Writer)
}
// DefaultCompressorProvider is the actual provider of compressors (zlib or gzip).
var currentCompressorProvider CompressorProvider
func init() {
currentCompressorProvider = NewSyncPoolCompessors()
}
// CurrentCompressorProvider returns the current CompressorProvider.
// It is initialized using a SyncPoolCompessors.
func CurrentCompressorProvider() CompressorProvider {
return currentCompressorProvider
}
// SetCompressorProvider sets the actual provider of compressors (zlib or gzip).
func SetCompressorProvider(p CompressorProvider) {
if p == nil {
panic("cannot set compressor provider to nil")
}
currentCompressorProvider = p
}

View File

@ -1,30 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
const (
MIME_XML = "application/xml" // Accept or Content-Type used in Consumes() and/or Produces()
MIME_JSON = "application/json" // Accept or Content-Type used in Consumes() and/or Produces()
MIME_OCTET = "application/octet-stream" // If Content-Type is not present in request, use the default
HEADER_Allow = "Allow"
HEADER_Accept = "Accept"
HEADER_Origin = "Origin"
HEADER_ContentType = "Content-Type"
HEADER_LastModified = "Last-Modified"
HEADER_AcceptEncoding = "Accept-Encoding"
HEADER_ContentEncoding = "Content-Encoding"
HEADER_AccessControlExposeHeaders = "Access-Control-Expose-Headers"
HEADER_AccessControlRequestMethod = "Access-Control-Request-Method"
HEADER_AccessControlRequestHeaders = "Access-Control-Request-Headers"
HEADER_AccessControlAllowMethods = "Access-Control-Allow-Methods"
HEADER_AccessControlAllowOrigin = "Access-Control-Allow-Origin"
HEADER_AccessControlAllowCredentials = "Access-Control-Allow-Credentials"
HEADER_AccessControlAllowHeaders = "Access-Control-Allow-Headers"
HEADER_AccessControlMaxAge = "Access-Control-Max-Age"
ENCODING_GZIP = "gzip"
ENCODING_DEFLATE = "deflate"
)

View File

@ -1,374 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"bytes"
"errors"
"fmt"
"net/http"
"os"
"runtime"
"strings"
"sync"
"github.com/emicklei/go-restful/log"
)
// Container holds a collection of WebServices and a http.ServeMux to dispatch http requests.
// The requests are further dispatched to routes of WebServices using a RouteSelector
type Container struct {
webServicesLock sync.RWMutex
webServices []*WebService
ServeMux *http.ServeMux
isRegisteredOnRoot bool
containerFilters []FilterFunction
doNotRecover bool // default is true
recoverHandleFunc RecoverHandleFunction
serviceErrorHandleFunc ServiceErrorHandleFunction
router RouteSelector // default is a CurlyRouter (RouterJSR311 is a slower alternative)
contentEncodingEnabled bool // default is false
}
// NewContainer creates a new Container using a new ServeMux and default router (CurlyRouter)
func NewContainer() *Container {
return &Container{
webServices: []*WebService{},
ServeMux: http.NewServeMux(),
isRegisteredOnRoot: false,
containerFilters: []FilterFunction{},
doNotRecover: true,
recoverHandleFunc: logStackOnRecover,
serviceErrorHandleFunc: writeServiceError,
router: CurlyRouter{},
contentEncodingEnabled: false}
}
// RecoverHandleFunction declares functions that can be used to handle a panic situation.
// The first argument is what recover() returns. The second must be used to communicate an error response.
type RecoverHandleFunction func(interface{}, http.ResponseWriter)
// RecoverHandler changes the default function (logStackOnRecover) to be called
// when a panic is detected. DoNotRecover must be have its default value (=false).
func (c *Container) RecoverHandler(handler RecoverHandleFunction) {
c.recoverHandleFunc = handler
}
// ServiceErrorHandleFunction declares functions that can be used to handle a service error situation.
// The first argument is the service error, the second is the request that resulted in the error and
// the third must be used to communicate an error response.
type ServiceErrorHandleFunction func(ServiceError, *Request, *Response)
// ServiceErrorHandler changes the default function (writeServiceError) to be called
// when a ServiceError is detected.
func (c *Container) ServiceErrorHandler(handler ServiceErrorHandleFunction) {
c.serviceErrorHandleFunc = handler
}
// DoNotRecover controls whether panics will be caught to return HTTP 500.
// If set to true, Route functions are responsible for handling any error situation.
// Default value is true.
func (c *Container) DoNotRecover(doNot bool) {
c.doNotRecover = doNot
}
// Router changes the default Router (currently CurlyRouter)
func (c *Container) Router(aRouter RouteSelector) {
c.router = aRouter
}
// EnableContentEncoding (default=false) allows for GZIP or DEFLATE encoding of responses.
func (c *Container) EnableContentEncoding(enabled bool) {
c.contentEncodingEnabled = enabled
}
// Add a WebService to the Container. It will detect duplicate root paths and exit in that case.
func (c *Container) Add(service *WebService) *Container {
c.webServicesLock.Lock()
defer c.webServicesLock.Unlock()
// if rootPath was not set then lazy initialize it
if len(service.rootPath) == 0 {
service.Path("/")
}
// cannot have duplicate root paths
for _, each := range c.webServices {
if each.RootPath() == service.RootPath() {
log.Printf("WebService with duplicate root path detected:['%v']", each)
os.Exit(1)
}
}
// If not registered on root then add specific mapping
if !c.isRegisteredOnRoot {
c.isRegisteredOnRoot = c.addHandler(service, c.ServeMux)
}
c.webServices = append(c.webServices, service)
return c
}
// addHandler may set a new HandleFunc for the serveMux
// this function must run inside the critical region protected by the webServicesLock.
// returns true if the function was registered on root ("/")
func (c *Container) addHandler(service *WebService, serveMux *http.ServeMux) bool {
pattern := fixedPrefixPath(service.RootPath())
// check if root path registration is needed
if "/" == pattern || "" == pattern {
serveMux.HandleFunc("/", c.dispatch)
return true
}
// detect if registration already exists
alreadyMapped := false
for _, each := range c.webServices {
if each.RootPath() == service.RootPath() {
alreadyMapped = true
break
}
}
if !alreadyMapped {
serveMux.HandleFunc(pattern, c.dispatch)
if !strings.HasSuffix(pattern, "/") {
serveMux.HandleFunc(pattern+"/", c.dispatch)
}
}
return false
}
func (c *Container) Remove(ws *WebService) error {
if c.ServeMux == http.DefaultServeMux {
errMsg := fmt.Sprintf("cannot remove a WebService from a Container using the DefaultServeMux: ['%v']", ws)
log.Print(errMsg)
return errors.New(errMsg)
}
c.webServicesLock.Lock()
defer c.webServicesLock.Unlock()
// build a new ServeMux and re-register all WebServices
newServeMux := http.NewServeMux()
newServices := []*WebService{}
newIsRegisteredOnRoot := false
for _, each := range c.webServices {
if each.rootPath != ws.rootPath {
// If not registered on root then add specific mapping
if !newIsRegisteredOnRoot {
newIsRegisteredOnRoot = c.addHandler(each, newServeMux)
}
newServices = append(newServices, each)
}
}
c.webServices, c.ServeMux, c.isRegisteredOnRoot = newServices, newServeMux, newIsRegisteredOnRoot
return nil
}
// logStackOnRecover is the default RecoverHandleFunction and is called
// when DoNotRecover is false and the recoverHandleFunc is not set for the container.
// Default implementation logs the stacktrace and writes the stacktrace on the response.
// This may be a security issue as it exposes sourcecode information.
func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) {
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason))
for i := 2; ; i += 1 {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line))
}
log.Print(buffer.String())
httpWriter.WriteHeader(http.StatusInternalServerError)
httpWriter.Write(buffer.Bytes())
}
// writeServiceError is the default ServiceErrorHandleFunction and is called
// when a ServiceError is returned during route selection. Default implementation
// calls resp.WriteErrorString(err.Code, err.Message)
func writeServiceError(err ServiceError, req *Request, resp *Response) {
resp.WriteErrorString(err.Code, err.Message)
}
// Dispatch the incoming Http Request to a matching WebService.
func (c *Container) Dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
if httpWriter == nil {
panic("httpWriter cannot be nil")
}
if httpRequest == nil {
panic("httpRequest cannot be nil")
}
c.dispatch(httpWriter, httpRequest)
}
// Dispatch the incoming Http Request to a matching WebService.
func (c *Container) dispatch(httpWriter http.ResponseWriter, httpRequest *http.Request) {
writer := httpWriter
// CompressingResponseWriter should be closed after all operations are done
defer func() {
if compressWriter, ok := writer.(*CompressingResponseWriter); ok {
compressWriter.Close()
}
}()
// Instal panic recovery unless told otherwise
if !c.doNotRecover { // catch all for 500 response
defer func() {
if r := recover(); r != nil {
c.recoverHandleFunc(r, writer)
return
}
}()
}
// Find best match Route ; err is non nil if no match was found
var webService *WebService
var route *Route
var err error
func() {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
webService, route, err = c.router.SelectRoute(
c.webServices,
httpRequest)
}()
// Detect if compression is needed
// assume without compression, test for override
contentEncodingEnabled := c.contentEncodingEnabled
if route != nil && route.contentEncodingEnabled != nil {
contentEncodingEnabled = *route.contentEncodingEnabled
}
if contentEncodingEnabled {
doCompress, encoding := wantsCompressedResponse(httpRequest)
if doCompress {
var err error
writer, err = NewCompressingResponseWriter(httpWriter, encoding)
if err != nil {
log.Print("unable to install compressor: ", err)
httpWriter.WriteHeader(http.StatusInternalServerError)
return
}
}
}
if err != nil {
// a non-200 response has already been written
// run container filters anyway ; they should not touch the response...
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
switch err.(type) {
case ServiceError:
ser := err.(ServiceError)
c.serviceErrorHandleFunc(ser, req, resp)
}
// TODO
}}
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(writer))
return
}
pathProcessor, routerProcessesPath := c.router.(PathProcessor)
if !routerProcessesPath {
pathProcessor = defaultPathProcessor{}
}
pathParams := pathProcessor.ExtractParameters(route, webService, httpRequest.URL.Path)
wrappedRequest, wrappedResponse := route.wrapRequestResponse(writer, httpRequest, pathParams)
// pass through filters (if any)
if size := len(c.containerFilters) + len(webService.filters) + len(route.Filters); size > 0 {
// compose filter chain
allFilters := make([]FilterFunction, 0, size)
allFilters = append(allFilters, c.containerFilters...)
allFilters = append(allFilters, webService.filters...)
allFilters = append(allFilters, route.Filters...)
chain := FilterChain{Filters: allFilters, Target: route.Function}
chain.ProcessFilter(wrappedRequest, wrappedResponse)
} else {
// no filters, handle request by route
route.Function(wrappedRequest, wrappedResponse)
}
}
// fixedPrefixPath returns the fixed part of the partspec ; it may include template vars {}
func fixedPrefixPath(pathspec string) string {
varBegin := strings.Index(pathspec, "{")
if -1 == varBegin {
return pathspec
}
return pathspec[:varBegin]
}
// ServeHTTP implements net/http.Handler therefore a Container can be a Handler in a http.Server
func (c *Container) ServeHTTP(httpwriter http.ResponseWriter, httpRequest *http.Request) {
c.ServeMux.ServeHTTP(httpwriter, httpRequest)
}
// Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
func (c *Container) Handle(pattern string, handler http.Handler) {
c.ServeMux.Handle(pattern, handler)
}
// HandleWithFilter registers the handler for the given pattern.
// Container's filter chain is applied for handler.
// If a handler already exists for pattern, HandleWithFilter panics.
func (c *Container) HandleWithFilter(pattern string, handler http.Handler) {
f := func(httpResponse http.ResponseWriter, httpRequest *http.Request) {
if len(c.containerFilters) == 0 {
handler.ServeHTTP(httpResponse, httpRequest)
return
}
chain := FilterChain{Filters: c.containerFilters, Target: func(req *Request, resp *Response) {
handler.ServeHTTP(httpResponse, httpRequest)
}}
chain.ProcessFilter(NewRequest(httpRequest), NewResponse(httpResponse))
}
c.Handle(pattern, http.HandlerFunc(f))
}
// Filter appends a container FilterFunction. These are called before dispatching
// a http.Request to a WebService from the container
func (c *Container) Filter(filter FilterFunction) {
c.containerFilters = append(c.containerFilters, filter)
}
// RegisteredWebServices returns the collections of added WebServices
func (c *Container) RegisteredWebServices() []*WebService {
c.webServicesLock.RLock()
defer c.webServicesLock.RUnlock()
result := make([]*WebService, len(c.webServices))
for ix := range c.webServices {
result[ix] = c.webServices[ix]
}
return result
}
// computeAllowedMethods returns a list of HTTP methods that are valid for a Request
func (c *Container) computeAllowedMethods(req *Request) []string {
// Go through all RegisteredWebServices() and all its Routes to collect the options
methods := []string{}
requestPath := req.Request.URL.Path
for _, ws := range c.RegisteredWebServices() {
matches := ws.pathExpr.Matcher.FindStringSubmatch(requestPath)
if matches != nil {
finalMatch := matches[len(matches)-1]
for _, rt := range ws.Routes() {
matches := rt.pathExpr.Matcher.FindStringSubmatch(finalMatch)
if matches != nil {
lastMatch := matches[len(matches)-1]
if lastMatch == "" || lastMatch == "/" { // do not include if value is neither empty nor /.
methods = append(methods, rt.Method)
}
}
}
}
}
// methods = append(methods, "OPTIONS") not sure about this
return methods
}
// newBasicRequestResponse creates a pair of Request,Response from its http versions.
// It is basic because no parameter or (produces) content-type information is given.
func newBasicRequestResponse(httpWriter http.ResponseWriter, httpRequest *http.Request) (*Request, *Response) {
resp := NewResponse(httpWriter)
resp.requestAccept = httpRequest.Header.Get(HEADER_Accept)
return NewRequest(httpRequest), resp
}

View File

@ -1,202 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"regexp"
"strconv"
"strings"
)
// CrossOriginResourceSharing is used to create a Container Filter that implements CORS.
// Cross-origin resource sharing (CORS) is a mechanism that allows JavaScript on a web page
// to make XMLHttpRequests to another domain, not the domain the JavaScript originated from.
//
// http://en.wikipedia.org/wiki/Cross-origin_resource_sharing
// http://enable-cors.org/server.html
// http://www.html5rocks.com/en/tutorials/cors/#toc-handling-a-not-so-simple-request
type CrossOriginResourceSharing struct {
ExposeHeaders []string // list of Header names
AllowedHeaders []string // list of Header names
AllowedDomains []string // list of allowed values for Http Origin. An allowed value can be a regular expression to support subdomain matching. If empty all are allowed.
AllowedMethods []string
MaxAge int // number of seconds before requiring new Options request
CookiesAllowed bool
Container *Container
allowedOriginPatterns []*regexp.Regexp // internal field for origin regexp check.
}
// Filter is a filter function that implements the CORS flow as documented on http://enable-cors.org/server.html
// and http://www.html5rocks.com/static/images/cors_server_flowchart.png
func (c CrossOriginResourceSharing) Filter(req *Request, resp *Response, chain *FilterChain) {
origin := req.Request.Header.Get(HEADER_Origin)
if len(origin) == 0 {
if trace {
traceLogger.Print("no Http header Origin set")
}
chain.ProcessFilter(req, resp)
return
}
if !c.isOriginAllowed(origin) { // check whether this origin is allowed
if trace {
traceLogger.Printf("HTTP Origin:%s is not part of %v, neither matches any part of %v", origin, c.AllowedDomains, c.allowedOriginPatterns)
}
chain.ProcessFilter(req, resp)
return
}
if req.Request.Method != "OPTIONS" {
c.doActualRequest(req, resp)
chain.ProcessFilter(req, resp)
return
}
if acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod); acrm != "" {
c.doPreflightRequest(req, resp)
} else {
c.doActualRequest(req, resp)
chain.ProcessFilter(req, resp)
return
}
}
func (c CrossOriginResourceSharing) doActualRequest(req *Request, resp *Response) {
c.setOptionsHeaders(req, resp)
// continue processing the response
}
func (c *CrossOriginResourceSharing) doPreflightRequest(req *Request, resp *Response) {
if len(c.AllowedMethods) == 0 {
if c.Container == nil {
c.AllowedMethods = DefaultContainer.computeAllowedMethods(req)
} else {
c.AllowedMethods = c.Container.computeAllowedMethods(req)
}
}
acrm := req.Request.Header.Get(HEADER_AccessControlRequestMethod)
if !c.isValidAccessControlRequestMethod(acrm, c.AllowedMethods) {
if trace {
traceLogger.Printf("Http header %s:%s is not in %v",
HEADER_AccessControlRequestMethod,
acrm,
c.AllowedMethods)
}
return
}
acrhs := req.Request.Header.Get(HEADER_AccessControlRequestHeaders)
if len(acrhs) > 0 {
for _, each := range strings.Split(acrhs, ",") {
if !c.isValidAccessControlRequestHeader(strings.Trim(each, " ")) {
if trace {
traceLogger.Printf("Http header %s:%s is not in %v",
HEADER_AccessControlRequestHeaders,
acrhs,
c.AllowedHeaders)
}
return
}
}
}
resp.AddHeader(HEADER_AccessControlAllowMethods, strings.Join(c.AllowedMethods, ","))
resp.AddHeader(HEADER_AccessControlAllowHeaders, acrhs)
c.setOptionsHeaders(req, resp)
// return http 200 response, no body
}
func (c CrossOriginResourceSharing) setOptionsHeaders(req *Request, resp *Response) {
c.checkAndSetExposeHeaders(resp)
c.setAllowOriginHeader(req, resp)
c.checkAndSetAllowCredentials(resp)
if c.MaxAge > 0 {
resp.AddHeader(HEADER_AccessControlMaxAge, strconv.Itoa(c.MaxAge))
}
}
func (c CrossOriginResourceSharing) isOriginAllowed(origin string) bool {
if len(origin) == 0 {
return false
}
if len(c.AllowedDomains) == 0 {
return true
}
allowed := false
for _, domain := range c.AllowedDomains {
if domain == origin {
allowed = true
break
}
}
if !allowed {
if len(c.allowedOriginPatterns) == 0 {
// compile allowed domains to allowed origin patterns
allowedOriginRegexps, err := compileRegexps(c.AllowedDomains)
if err != nil {
return false
}
c.allowedOriginPatterns = allowedOriginRegexps
}
for _, pattern := range c.allowedOriginPatterns {
if allowed = pattern.MatchString(origin); allowed {
break
}
}
}
return allowed
}
func (c CrossOriginResourceSharing) setAllowOriginHeader(req *Request, resp *Response) {
origin := req.Request.Header.Get(HEADER_Origin)
if c.isOriginAllowed(origin) {
resp.AddHeader(HEADER_AccessControlAllowOrigin, origin)
}
}
func (c CrossOriginResourceSharing) checkAndSetExposeHeaders(resp *Response) {
if len(c.ExposeHeaders) > 0 {
resp.AddHeader(HEADER_AccessControlExposeHeaders, strings.Join(c.ExposeHeaders, ","))
}
}
func (c CrossOriginResourceSharing) checkAndSetAllowCredentials(resp *Response) {
if c.CookiesAllowed {
resp.AddHeader(HEADER_AccessControlAllowCredentials, "true")
}
}
func (c CrossOriginResourceSharing) isValidAccessControlRequestMethod(method string, allowedMethods []string) bool {
for _, each := range allowedMethods {
if each == method {
return true
}
}
return false
}
func (c CrossOriginResourceSharing) isValidAccessControlRequestHeader(header string) bool {
for _, each := range c.AllowedHeaders {
if strings.ToLower(each) == strings.ToLower(header) {
return true
}
}
return false
}
// Take a list of strings and compile them into a list of regular expressions.
func compileRegexps(regexpStrings []string) ([]*regexp.Regexp, error) {
regexps := []*regexp.Regexp{}
for _, regexpStr := range regexpStrings {
r, err := regexp.Compile(regexpStr)
if err != nil {
return regexps, err
}
regexps = append(regexps, r)
}
return regexps, nil
}

View File

@ -1,2 +0,0 @@
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

View File

@ -1,173 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"net/http"
"regexp"
"sort"
"strings"
)
// CurlyRouter expects Routes with paths that contain zero or more parameters in curly brackets.
type CurlyRouter struct{}
// SelectRoute is part of the Router interface and returns the best match
// for the WebService and its Route for the given Request.
func (c CurlyRouter) SelectRoute(
webServices []*WebService,
httpRequest *http.Request) (selectedService *WebService, selected *Route, err error) {
requestTokens := tokenizePath(httpRequest.URL.Path)
detectedService := c.detectWebService(requestTokens, webServices)
if detectedService == nil {
if trace {
traceLogger.Printf("no WebService was found to match URL path:%s\n", httpRequest.URL.Path)
}
return nil, nil, NewError(http.StatusNotFound, "404: Page Not Found")
}
candidateRoutes := c.selectRoutes(detectedService, requestTokens)
if len(candidateRoutes) == 0 {
if trace {
traceLogger.Printf("no Route in WebService with path %s was found to match URL path:%s\n", detectedService.rootPath, httpRequest.URL.Path)
}
return detectedService, nil, NewError(http.StatusNotFound, "404: Page Not Found")
}
selectedRoute, err := c.detectRoute(candidateRoutes, httpRequest)
if selectedRoute == nil {
return detectedService, nil, err
}
return detectedService, selectedRoute, nil
}
// selectRoutes return a collection of Route from a WebService that matches the path tokens from the request.
func (c CurlyRouter) selectRoutes(ws *WebService, requestTokens []string) sortableCurlyRoutes {
candidates := make(sortableCurlyRoutes, 0, 8)
for _, each := range ws.routes {
matches, paramCount, staticCount := c.matchesRouteByPathTokens(each.pathParts, requestTokens, each.hasCustomVerb)
if matches {
candidates.add(curlyRoute{each, paramCount, staticCount}) // TODO make sure Routes() return pointers?
}
}
sort.Sort(candidates)
return candidates
}
// matchesRouteByPathTokens computes whether it matches, howmany parameters do match and what the number of static path elements are.
func (c CurlyRouter) matchesRouteByPathTokens(routeTokens, requestTokens []string, routeHasCustomVerb bool) (matches bool, paramCount int, staticCount int) {
if len(routeTokens) < len(requestTokens) {
// proceed in matching only if last routeToken is wildcard
count := len(routeTokens)
if count == 0 || !strings.HasSuffix(routeTokens[count-1], "*}") {
return false, 0, 0
}
// proceed
}
for i, routeToken := range routeTokens {
if i == len(requestTokens) {
// reached end of request path
return false, 0, 0
}
requestToken := requestTokens[i]
if routeHasCustomVerb && hasCustomVerb(routeToken){
if !isMatchCustomVerb(routeToken, requestToken) {
return false, 0, 0
}
staticCount++
requestToken = removeCustomVerb(requestToken)
routeToken = removeCustomVerb(routeToken)
}
if strings.HasPrefix(routeToken, "{") {
paramCount++
if colon := strings.Index(routeToken, ":"); colon != -1 {
// match by regex
matchesToken, matchesRemainder := c.regularMatchesPathToken(routeToken, colon, requestToken)
if !matchesToken {
return false, 0, 0
}
if matchesRemainder {
break
}
}
} else { // no { prefix
if requestToken != routeToken {
return false, 0, 0
}
staticCount++
}
}
return true, paramCount, staticCount
}
// regularMatchesPathToken tests whether the regular expression part of routeToken matches the requestToken or all remaining tokens
// format routeToken is {someVar:someExpression}, e.g. {zipcode:[\d][\d][\d][\d][A-Z][A-Z]}
func (c CurlyRouter) regularMatchesPathToken(routeToken string, colon int, requestToken string) (matchesToken bool, matchesRemainder bool) {
regPart := routeToken[colon+1 : len(routeToken)-1]
if regPart == "*" {
if trace {
traceLogger.Printf("wildcard parameter detected in route token %s that matches %s\n", routeToken, requestToken)
}
return true, true
}
matched, err := regexp.MatchString(regPart, requestToken)
return (matched && err == nil), false
}
var jsr311Router = RouterJSR311{}
// detectRoute selectes from a list of Route the first match by inspecting both the Accept and Content-Type
// headers of the Request. See also RouterJSR311 in jsr311.go
func (c CurlyRouter) detectRoute(candidateRoutes sortableCurlyRoutes, httpRequest *http.Request) (*Route, error) {
// tracing is done inside detectRoute
return jsr311Router.detectRoute(candidateRoutes.routes(), httpRequest)
}
// detectWebService returns the best matching webService given the list of path tokens.
// see also computeWebserviceScore
func (c CurlyRouter) detectWebService(requestTokens []string, webServices []*WebService) *WebService {
var best *WebService
score := -1
for _, each := range webServices {
matches, eachScore := c.computeWebserviceScore(requestTokens, each.pathExpr.tokens)
if matches && (eachScore > score) {
best = each
score = eachScore
}
}
return best
}
// computeWebserviceScore returns whether tokens match and
// the weighted score of the longest matching consecutive tokens from the beginning.
func (c CurlyRouter) computeWebserviceScore(requestTokens []string, tokens []string) (bool, int) {
if len(tokens) > len(requestTokens) {
return false, 0
}
score := 0
for i := 0; i < len(tokens); i++ {
each := requestTokens[i]
other := tokens[i]
if len(each) == 0 && len(other) == 0 {
score++
continue
}
if len(other) > 0 && strings.HasPrefix(other, "{") {
// no empty match
if len(each) == 0 {
return false, score
}
score += 1
} else {
// not a parameter
if each != other {
return false, score
}
score += (len(tokens) - i) * 10 //fuzzy
}
}
return true, score
}

View File

@ -1,54 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
// curlyRoute exits for sorting Routes by the CurlyRouter based on number of parameters and number of static path elements.
type curlyRoute struct {
route Route
paramCount int
staticCount int
}
// sortableCurlyRoutes orders by most parameters and path elements first.
type sortableCurlyRoutes []curlyRoute
func (s *sortableCurlyRoutes) add(route curlyRoute) {
*s = append(*s, route)
}
func (s sortableCurlyRoutes) routes() (routes []Route) {
routes = make([]Route, 0, len(s))
for _, each := range s {
routes = append(routes, each.route) // TODO change return type
}
return routes
}
func (s sortableCurlyRoutes) Len() int {
return len(s)
}
func (s sortableCurlyRoutes) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortableCurlyRoutes) Less(i, j int) bool {
a := s[j]
b := s[i]
// primary key
if a.staticCount < b.staticCount {
return true
}
if a.staticCount > b.staticCount {
return false
}
// secundary key
if a.paramCount < b.paramCount {
return true
}
if a.paramCount > b.paramCount {
return false
}
return a.route.Path < b.route.Path
}

View File

@ -1,29 +0,0 @@
package restful
import (
"fmt"
"regexp"
)
var (
customVerbReg = regexp.MustCompile(":([A-Za-z]+)$")
)
func hasCustomVerb(routeToken string) bool {
return customVerbReg.MatchString(routeToken)
}
func isMatchCustomVerb(routeToken string, pathToken string) bool {
rs := customVerbReg.FindStringSubmatch(routeToken)
if len(rs) < 2 {
return false
}
customVerb := rs[1]
specificVerbReg := regexp.MustCompile(fmt.Sprintf(":%s$", customVerb))
return specificVerbReg.MatchString(pathToken)
}
func removeCustomVerb(str string) string {
return customVerbReg.ReplaceAllString(str, "")
}

View File

@ -1,185 +0,0 @@
/*
Package restful , a lean package for creating REST-style WebServices without magic.
WebServices and Routes
A WebService has a collection of Route objects that dispatch incoming Http Requests to a function calls.
Typically, a WebService has a root path (e.g. /users) and defines common MIME types for its routes.
WebServices must be added to a container (see below) in order to handler Http requests from a server.
A Route is defined by a HTTP method, an URL path and (optionally) the MIME types it consumes (Content-Type) and produces (Accept).
This package has the logic to find the best matching Route and if found, call its Function.
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_JSON, restful.MIME_XML).
Produces(restful.MIME_JSON, restful.MIME_XML)
ws.Route(ws.GET("/{user-id}").To(u.findUser)) // u is a UserResource
...
// GET http://localhost:8080/users/1
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
...
}
The (*Request, *Response) arguments provide functions for reading information from the request and writing information back to the response.
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-user-resource.go with a full implementation.
Regular expression matching Routes
A Route parameter can be specified using the format "uri/{var[:regexp]}" or the special version "uri/{var:*}" for matching the tail of the path.
For example, /persons/{name:[A-Z][A-Z]} can be used to restrict values for the parameter "name" to only contain capital alphabetic characters.
Regular expressions must use the standard Go syntax as described in the regexp package. (https://code.google.com/p/re2/wiki/Syntax)
This feature requires the use of a CurlyRouter.
Containers
A Container holds a collection of WebServices, Filters and a http.ServeMux for multiplexing http requests.
Using the statements "restful.Add(...) and restful.Filter(...)" will register WebServices and Filters to the Default Container.
The Default container of go-restful uses the http.DefaultServeMux.
You can create your own Container and create a new http.Server for that particular container.
container := restful.NewContainer()
server := &http.Server{Addr: ":8081", Handler: container}
Filters
A filter dynamically intercepts requests and responses to transform or use the information contained in the requests or responses.
You can use filters to perform generic logging, measurement, authentication, redirect, set response headers etc.
In the restful package there are three hooks into the request,response flow where filters can be added.
Each filter must define a FilterFunction:
func (req *restful.Request, resp *restful.Response, chain *restful.FilterChain)
Use the following statement to pass the request,response pair to the next filter or RouteFunction
chain.ProcessFilter(req, resp)
Container Filters
These are processed before any registered WebService.
// install a (global) filter for the default container (processed before any webservice)
restful.Filter(globalLogging)
WebService Filters
These are processed before any Route of a WebService.
// install a webservice filter (processed before any route)
ws.Filter(webserviceLogging).Filter(measureTime)
Route Filters
These are processed before calling the function associated with the Route.
// install 2 chained route filters (processed before calling findUser)
ws.Route(ws.GET("/{user-id}").Filter(routeLogging).Filter(NewCountFilter().routeCounter).To(findUser))
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-filters.go with full implementations.
Response Encoding
Two encodings are supported: gzip and deflate. To enable this for all responses:
restful.DefaultContainer.EnableContentEncoding(true)
If a Http request includes the Accept-Encoding header then the response content will be compressed using the specified encoding.
Alternatively, you can create a Filter that performs the encoding and install it per WebService or Route.
See the example https://github.com/emicklei/go-restful/blob/master/examples/restful-encoding-filter.go
OPTIONS support
By installing a pre-defined container filter, your Webservice(s) can respond to the OPTIONS Http request.
Filter(OPTIONSFilter())
CORS
By installing the filter of a CrossOriginResourceSharing (CORS), your WebService(s) can handle CORS requests.
cors := CrossOriginResourceSharing{ExposeHeaders: []string{"X-My-Header"}, CookiesAllowed: false, Container: DefaultContainer}
Filter(cors.Filter)
Error Handling
Unexpected things happen. If a request cannot be processed because of a failure, your service needs to tell via the response what happened and why.
For this reason HTTP status codes exist and it is important to use the correct code in every exceptional situation.
400: Bad Request
If path or query parameters are not valid (content or type) then use http.StatusBadRequest.
404: Not Found
Despite a valid URI, the resource requested may not be available
500: Internal Server Error
If the application logic could not process the request (or write the response) then use http.StatusInternalServerError.
405: Method Not Allowed
The request has a valid URL but the method (GET,PUT,POST,...) is not allowed.
406: Not Acceptable
The request does not have or has an unknown Accept Header set for this operation.
415: Unsupported Media Type
The request does not have or has an unknown Content-Type Header set for this operation.
ServiceError
In addition to setting the correct (error) Http status code, you can choose to write a ServiceError message on the response.
Performance options
This package has several options that affect the performance of your service. It is important to understand them and how you can change it.
restful.DefaultContainer.DoNotRecover(false)
DoNotRecover controls whether panics will be caught to return HTTP 500.
If set to false, the container will recover from panics.
Default value is true
restful.SetCompressorProvider(NewBoundedCachedCompressors(20, 20))
If content encoding is enabled then the default strategy for getting new gzip/zlib writers and readers is to use a sync.Pool.
Because writers are expensive structures, performance is even more improved when using a preloaded cache. You can also inject your own implementation.
Trouble shooting
This package has the means to produce detail logging of the complete Http request matching process and filter invocation.
Enabling this feature requires you to set an implementation of restful.StdLogger (e.g. log.Logger) instance such as:
restful.TraceLogger(log.New(os.Stdout, "[restful] ", log.LstdFlags|log.Lshortfile))
Logging
The restful.SetLogger() method allows you to override the logger used by the package. By default restful
uses the standard library `log` package and logs to stdout. Different logging packages are supported as
long as they conform to `StdLogger` interface defined in the `log` sub-package, writing an adapter for your
preferred package is simple.
Resources
[project]: https://github.com/emicklei/go-restful
[examples]: https://github.com/emicklei/go-restful/blob/master/examples
[design]: http://ernestmicklei.com/2012/11/11/go-restful-api-design/
[showcases]: https://github.com/emicklei/mora, https://github.com/emicklei/landskape
(c) 2012-2015, http://ernestmicklei.com. MIT License
*/
package restful

View File

@ -1,162 +0,0 @@
package restful
// Copyright 2015 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
import (
"encoding/xml"
"strings"
"sync"
)
// EntityReaderWriter can read and write values using an encoding such as JSON,XML.
type EntityReaderWriter interface {
// Read a serialized version of the value from the request.
// The Request may have a decompressing reader. Depends on Content-Encoding.
Read(req *Request, v interface{}) error
// Write a serialized version of the value on the response.
// The Response may have a compressing writer. Depends on Accept-Encoding.
// status should be a valid Http Status code
Write(resp *Response, status int, v interface{}) error
}
// entityAccessRegistry is a singleton
var entityAccessRegistry = &entityReaderWriters{
protection: new(sync.RWMutex),
accessors: map[string]EntityReaderWriter{},
}
// entityReaderWriters associates MIME to an EntityReaderWriter
type entityReaderWriters struct {
protection *sync.RWMutex
accessors map[string]EntityReaderWriter
}
func init() {
RegisterEntityAccessor(MIME_JSON, NewEntityAccessorJSON(MIME_JSON))
RegisterEntityAccessor(MIME_XML, NewEntityAccessorXML(MIME_XML))
}
// RegisterEntityAccessor add/overrides the ReaderWriter for encoding content with this MIME type.
func RegisterEntityAccessor(mime string, erw EntityReaderWriter) {
entityAccessRegistry.protection.Lock()
defer entityAccessRegistry.protection.Unlock()
entityAccessRegistry.accessors[mime] = erw
}
// NewEntityAccessorJSON returns a new EntityReaderWriter for accessing JSON content.
// This package is already initialized with such an accessor using the MIME_JSON contentType.
func NewEntityAccessorJSON(contentType string) EntityReaderWriter {
return entityJSONAccess{ContentType: contentType}
}
// NewEntityAccessorXML returns a new EntityReaderWriter for accessing XML content.
// This package is already initialized with such an accessor using the MIME_XML contentType.
func NewEntityAccessorXML(contentType string) EntityReaderWriter {
return entityXMLAccess{ContentType: contentType}
}
// accessorAt returns the registered ReaderWriter for this MIME type.
func (r *entityReaderWriters) accessorAt(mime string) (EntityReaderWriter, bool) {
r.protection.RLock()
defer r.protection.RUnlock()
er, ok := r.accessors[mime]
if !ok {
// retry with reverse lookup
// more expensive but we are in an exceptional situation anyway
for k, v := range r.accessors {
if strings.Contains(mime, k) {
return v, true
}
}
}
return er, ok
}
// entityXMLAccess is a EntityReaderWriter for XML encoding
type entityXMLAccess struct {
// This is used for setting the Content-Type header when writing
ContentType string
}
// Read unmarshalls the value from XML
func (e entityXMLAccess) Read(req *Request, v interface{}) error {
return xml.NewDecoder(req.Request.Body).Decode(v)
}
// Write marshalls the value to JSON and set the Content-Type Header.
func (e entityXMLAccess) Write(resp *Response, status int, v interface{}) error {
return writeXML(resp, status, e.ContentType, v)
}
// writeXML marshalls the value to JSON and set the Content-Type Header.
func writeXML(resp *Response, status int, contentType string, v interface{}) error {
if v == nil {
resp.WriteHeader(status)
// do not write a nil representation
return nil
}
if resp.prettyPrint {
// pretty output must be created and written explicitly
output, err := xml.MarshalIndent(v, " ", " ")
if err != nil {
return err
}
resp.Header().Set(HEADER_ContentType, contentType)
resp.WriteHeader(status)
_, err = resp.Write([]byte(xml.Header))
if err != nil {
return err
}
_, err = resp.Write(output)
return err
}
// not-so-pretty
resp.Header().Set(HEADER_ContentType, contentType)
resp.WriteHeader(status)
return xml.NewEncoder(resp).Encode(v)
}
// entityJSONAccess is a EntityReaderWriter for JSON encoding
type entityJSONAccess struct {
// This is used for setting the Content-Type header when writing
ContentType string
}
// Read unmarshalls the value from JSON
func (e entityJSONAccess) Read(req *Request, v interface{}) error {
decoder := NewDecoder(req.Request.Body)
decoder.UseNumber()
return decoder.Decode(v)
}
// Write marshalls the value to JSON and set the Content-Type Header.
func (e entityJSONAccess) Write(resp *Response, status int, v interface{}) error {
return writeJSON(resp, status, e.ContentType, v)
}
// write marshalls the value to JSON and set the Content-Type Header.
func writeJSON(resp *Response, status int, contentType string, v interface{}) error {
if v == nil {
resp.WriteHeader(status)
// do not write a nil representation
return nil
}
if resp.prettyPrint {
// pretty output must be created and written explicitly
output, err := MarshalIndent(v, "", " ")
if err != nil {
return err
}
resp.Header().Set(HEADER_ContentType, contentType)
resp.WriteHeader(status)
_, err = resp.Write(output)
return err
}
// not-so-pretty
resp.Header().Set(HEADER_ContentType, contentType)
resp.WriteHeader(status)
return NewEncoder(resp).Encode(v)
}

View File

@ -1,35 +0,0 @@
package restful
// Copyright 2013 Ernest Micklei. All rights reserved.
// Use of this source code is governed by a license
// that can be found in the LICENSE file.
// FilterChain is a request scoped object to process one or more filters before calling the target RouteFunction.
type FilterChain struct {
Filters []FilterFunction // ordered list of FilterFunction
Index int // index into filters that is currently in progress
Target RouteFunction // function to call after passing all filters
}
// ProcessFilter passes the request,response pair through the next of Filters.
// Each filter can decide to proceed to the next Filter or handle the Response itself.
func (f *FilterChain) ProcessFilter(request *Request, response *Response) {
if f.Index < len(f.Filters) {
f.Index++
f.Filters[f.Index-1](request, response, f)
} else {
f.Target(request, response)
}
}
// FilterFunction definitions must call ProcessFilter on the FilterChain to pass on the control and eventually call the RouteFunction
type FilterFunction func(*Request, *Response, *FilterChain)
// NoBrowserCacheFilter is a filter function to set HTTP headers that disable browser caching
// See examples/restful-no-cache-filter.go for usage
func NoBrowserCacheFilter(req *Request, resp *Response, chain *FilterChain) {
resp.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") // HTTP 1.1.
resp.Header().Set("Pragma", "no-cache") // HTTP 1.0.
resp.Header().Set("Expires", "0") // Proxies.
chain.ProcessFilter(req, resp)
}

Some files were not shown because too many files have changed in this diff Show More