diff --git a/Gopkg.lock b/Gopkg.lock index 09f4a4084..1bd32310d 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -10,7 +10,7 @@ version = "v0.37.0" [[projects]] - digest = "1:2be791e7b333ff7c06f8fb3dc18a7d70580e9399dbdffd352621d067ff260b6e" + digest = "1:26b14a6dc72ace253599e969997d5ecf2143c63833c015179786bc756c76eaa4" name = "github.com/Microsoft/go-winio" packages = ["."] pruneopts = "NUT" @@ -43,7 +43,7 @@ [[projects]] branch = "master" - digest = "1:2795ce42ac79b6a0733d76c5c2de94f458fa0892949c1768351e2b5e41b5abc6" + digest = "1:1e31f7a894b511019f6d5ce4a9462cb60151440645bb25dd48b5940ff2bbbdf1" name = "github.com/aead/chacha20" packages = [ ".", @@ -60,6 +60,14 @@ revision = "7c0e3b262f30165a8ec3d0b4c6059fd92703bfb2" version = "1.0.0" +[[projects]] + digest = "1:5a23cd3a5496a0b2da7e3b348d14e77b11a210738c398200d7d6f04ea8cf3bd8" + name = "github.com/asaskevich/govalidator" + packages = ["."] + pruneopts = "NUT" + revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f" + version = "v9" + [[projects]] branch = "master" digest = "1:707ebe952a8b3d00b343c01536c79c73771d100f63ec6babeaed5c79e2b8a8dd" @@ -69,7 +77,7 @@ revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] - digest = "1:92aa7f157d0b4d08199dd6d9697652bc65c7d2179c15918141a357bf486559e0" + digest = "1:4d43362859489b0672b9a1544d5ae02231202a43172c7eda7a55d44799f97f53" name = "github.com/bifurcation/mint" packages = [ ".", @@ -112,7 +120,7 @@ [[projects]] branch = "master" - digest = "1:4c7d169280debf9f36b84a0f682094889cccc5dc0db8657f9cffc93b21975a57" + digest = "1:5b5643142a4d48d55f7a2ad09da4565be2b0ecc554c903a25d18b18159d39b9f" name = "github.com/docker/distribution" packages = [ "digestset", @@ -122,7 +130,7 @@ revision = "6d62eb1d4a3515399431b713fde3ce5a9b40e8d5" [[projects]] - digest = "1:b75e9ec256dfcb9208b8e65baf9f5aecfd86ab7d93d5424565aa3d38832ea9c6" + digest = "1:addad6aa992efe1cd74f5bec0b069a68b35db1da8b042581002215d59b3ac54d" name = "github.com/docker/docker" packages = [ "api", @@ -152,7 +160,7 @@ [[projects]] branch = "master" - digest = "1:42cfcc9461365aacb806b9cfe929c926f0d3fb0f692bfc10c375872f2881b2f2" + digest = "1:0c6d0df813996e4b7bfd40eb2f0671755159576e0b38263babeef1ee7bbfe4d0" name = "github.com/docker/go-connections" packages = [ "nat", @@ -180,7 +188,7 @@ [[projects]] branch = "master" - digest = "1:eb8f1b1913bffd6e788deee9fe4ba3a4d83267aff6045d3be33105e35ece290b" + digest = "1:8d9247519c7cce3720ec913acc3a8d593ba04690ffd83b3b3a4d08f662c2d6b3" name = "github.com/docker/spdystream" packages = [ ".", @@ -198,7 +206,7 @@ version = "v1.0.0" [[projects]] - digest = "1:aab9e95808c688b75757e520e59c48eb954991693dfc5441f0e87d1f9cb8b7bb" + digest = "1:3267a1e17bf240decf62c364e861d79e68b52b5804d63b510b0baed70b9a6aee" name = "github.com/emicklei/go-restful" packages = [ ".", @@ -217,7 +225,7 @@ version = "v1.0.0" [[projects]] - digest = "1:b498b36dbb2b306d1c5205ee5236c9e60352be8f9eea9bf08186723a9f75b4f3" + digest = "1:85b82e47d3bacb3125fbcb321b828f6c1c1e9afc307166dae6b6342b66aa427c" name = "github.com/emirpasic/gods" packages = [ "containers", @@ -239,6 +247,14 @@ revision = "72bf35d0ff611848c1dc9df0f976c81192392fa5" version = "v4.1.0" +[[projects]] + digest = "1:aa3ed0a71c4e66e4ae6486bf97a3f4cab28edc78df2e50c5ad01dc7d91604b88" + name = "github.com/fatih/structs" + packages = ["."] + pruneopts = "NUT" + revision = "4966fc68f5b7593aafa6cbbba2d65ec6e1416047" + version = "v1.1.0" + [[projects]] branch = "master" digest = "1:1ccd7321e62f680a988bba496f0f5a9c80410b8104d55b0f6b8ecf84ad328476" @@ -312,7 +328,7 @@ version = "v0.18.0" [[projects]] - digest = "1:513691cf6809d4fa2482469adb88aba530f96a5c4a9c68a09a0786519b07c24a" + digest = "1:589fc282fd63dd84be3c91d87feaaa67a969612ba1ca33b911755fd95f460358" name = "github.com/go-redis/redis" packages = [ ".", @@ -344,7 +360,17 @@ version = "v1.6.15" [[projects]] - digest = "1:a1b2a5e38f79688ee8250942d5fa960525fceb1024c855c7bc76fa77b0f3cca2" + digest = "1:3b878cddad697b48929214a1bbd46d1cd411e79855fa502f0d33f7a30c2f882c" + name = "github.com/gocraft/dbr" + packages = [ + ".", + "dialect", + ] + pruneopts = "NUT" + revision = "a0fd650918f6287ffe111d1c7b66bb755ff3be4a" + +[[projects]] + digest = "1:f1631663db3b95aec6a1610560b33ede5e70011b0e8cad87b110016a6fbfc2db" name = "github.com/gogo/protobuf" packages = [ "proto", @@ -354,6 +380,14 @@ revision = "ba06b47c162d49f2af050fb4c75bcbc86a159d5c" version = "v1.2.1" +[[projects]] + branch = "master" + digest = "1:4cd45b97f006fcd976470886b5fe97f59494e0f7b8e626059500cb8ace0e4e73" + name = "github.com/golang/example" + packages = ["stringutil"] + pruneopts = "NUT" + revision = "46695d81d1fae905a270fb7db8a4d11a334562fe" + [[projects]] branch = "master" digest = "1:e2b86e41f3d669fc36b50d31d32d22c8ac656c75aa5ea89717ce7177e134ff2a" @@ -371,7 +405,7 @@ revision = "5b532d6fd5efaf7fa130d4e859a2fde0fc3a9e1b" [[projects]] - digest = "1:2d0636a8c490d2272dd725db26f74a537111b99b9dbdda0d8b98febe63702aa4" + digest = "1:a8b59d8995b50db3b206d9160817e00aace183e456cb60abf5157de16d12e3c9" name = "github.com/golang/protobuf" packages = [ "proto", @@ -392,6 +426,14 @@ pruneopts = "NUT" revision = "4030bb1f1f0c35b30ca7009e9ebd06849dd45306" +[[projects]] + digest = "1:a63cff6b5d8b95638bfe300385d93b2a6d9d687734b863da8e09dc834510a690" + name = "github.com/google/go-querystring" + packages = ["query"] + pruneopts = "NUT" + revision = "44c6ddd0a2342c386950e880b658017258da92fc" + version = "v1.0.0" + [[projects]] branch = "master" digest = "1:52c5834e2bebac9030c97cc0798ac11c3aa8a39f098aeb419f142533da6cd3cc" @@ -409,7 +451,7 @@ version = "v1.1.1" [[projects]] - digest = "1:06a7dadb7b760767341ffb6c8d377238d68a1226f2b21b5d497d2e3f6ecf6b4e" + digest = "1:add738701bd5b2b985c0c37011092c57218bdc46caf1e682a73dc210ad36b03f" name = "github.com/googleapis/gnostic" packages = [ "OpenAPIv2", @@ -438,7 +480,7 @@ [[projects]] branch = "master" - digest = "1:a86d65bc23eea505cd9139178e4d889733928fe165c7a008f41eaab039edf9df" + digest = "1:790debb569e0ca4a39c168cae515a5f6b353229ca351c0b4208ef6964934aaed" name = "github.com/gregjones/httpcache" packages = [ ".", @@ -475,7 +517,7 @@ version = "v0.5.1" [[projects]] - digest = "1:41933d387bfa3eaa6a82647914ed7044f7b8355764c24fb920892bc8c03ef0c3" + digest = "1:e4d4b786065b1879481dcfa5da9886f40fde00d6ab7dadea53d6d7dc943c4792" name = "github.com/hpcloud/tail" packages = [ ".", @@ -562,7 +604,7 @@ [[projects]] branch = "kubesphere" - digest = "1:d73bdba7430fe5491bdcb463166389a7467f1cab2ac902c8d12f93890278a5b6" + digest = "1:7bcdcd306f2dccd6d8e0e25d861de0ce94b93b0971604c879afeaad8f5439a11" name = "github.com/kiali/kiali" packages = [ "business", @@ -601,7 +643,7 @@ [[projects]] branch = "master" - digest = "1:c9258bacbb5c016d8eb7154c656cdc3cbec688ce74d57d5d2c35d3b33f5c7cb0" + digest = "1:da3896234af34d707626fa39b1402cd480908966354a792c503486c4f5ef4de7" name = "github.com/knative/pkg" packages = [ "apis/istio", @@ -635,7 +677,7 @@ [[projects]] branch = "master" - digest = "1:68a59bc3410e2fd7b5341057d54ec3aedb5c4aff7d0c59b72ced7d5fe6b1b92c" + digest = "1:11728f1747eea824b5d8194f85b5a7c0af7be4487be66645604e8302967e54b1" name = "github.com/kubernetes-sigs/application" packages = [ "pkg/apis/app/v1beta1", @@ -650,7 +692,7 @@ revision = "4ead7f1b87048b7717b3e474a21fdc07e6bce636" [[projects]] - digest = "1:26e91e5a7f6be65b1ed0ae488ef156c35a69e3435fd5c4ccd7b4e9cd847997c5" + digest = "1:f97290db0238bf4d6394b527daa18391a80b5592f3db8d991f2013f70f64bd46" name = "github.com/kubesphere/s2ioperator" packages = [ "pkg/apis/devops/v1alpha1", @@ -667,6 +709,14 @@ revision = "dc48c60d3ba316d8533688b556555567f9c78d51" version = "v0.0.8" +[[projects]] + digest = "1:a0c81030b65e467a7128668f24707beeca9812a9b1ed38c6d5db5b967da4e3b8" + name = "github.com/kubesphere/sonargo" + packages = ["sonar"] + pruneopts = "NUT" + revision = "313ae64d680ed14ef12e806ab0dd3777240e908d" + version = "v0.0.2" + [[projects]] branch = "master" digest = "1:2d137c17dacc803b85c06b7a0cc9e9a1d68e3104e567caf27fea2fb067ef424e" @@ -676,7 +726,7 @@ revision = "cd47fb39b79f867c6e4e5cd39cf7abd799f71670" [[projects]] - digest = "1:7e8a4473ed7155b5a3e4b0c692c0407d8d242e878d067d4adb9530b077f6f4e5" + digest = "1:3a12648ef8bab4b7310adf715fe4d74fc1d52eb81f1df6e9fcb168770ffdbd6e" name = "github.com/lucas-clemente/quic-go" packages = [ ".", @@ -705,7 +755,7 @@ [[projects]] branch = "master" - digest = "1:df495c9184b4e6cbb9d55652236dbcbe72c65a1c8b6469da50722628cea474e7" + digest = "1:0b14bf0c424137a702e793fe3570f41320cc3b8495d124e279a4c362c57f214b" name = "github.com/mailru/easyjson" packages = [ "buffer", @@ -732,7 +782,7 @@ version = "v1.0.1" [[projects]] - digest = "1:1499e521c77de0395b3b4136c9b65712f8cc3e12bcd1c4c397cdd0f73d458bdc" + digest = "1:0a36dc212d1b91a38b435accb11764fe6506c503cc6b75a014b4fd0bd9db44df" name = "github.com/mholt/caddy" packages = [ ".", @@ -827,7 +877,7 @@ version = "v0.1.0" [[projects]] - digest = "1:26b3fcffe42d2ef2076af3a8321c8c39db44ff1f46aac3be20a9b64a46328928" + digest = "1:9cd0478666845c9fb37ce8458c8b455d73c0a74d1e4c7603be298d9e28df83a1" name = "github.com/naoina/toml" packages = [ ".", @@ -838,7 +888,7 @@ version = "v0.1.1" [[projects]] - digest = "1:3c46171ee5eee66086897e1efca67b84bf552b1f80039d421068c90684868194" + digest = "1:a84acfd5bc2d90ec8d9e0c2f2728aa8f2a33512f1247a099be4330ef9b926094" name = "github.com/onsi/ginkgo" packages = [ ".", @@ -865,7 +915,7 @@ version = "v1.8.0" [[projects]] - digest = "1:01797bb0a7bd98751c65dff3461b9f948e3be09c443d179468781720326b09fe" + digest = "1:ad29fb22a545681b5b5d0e07bc9fe20cbdaf3f954ffcad361017f3e34ac6b4ce" name = "github.com/onsi/gomega" packages = [ ".", @@ -896,7 +946,7 @@ version = "v1.0.0-rc1" [[projects]] - digest = "1:cf7a1da29bb5ad81a30d8f13a97de4b45c7c78f1f7a0f5651ce35bf3bf88ff2b" + digest = "1:290add25d7ce226bce0d6880f38c4fbb7129346827a71f37293143cf9dade289" name = "github.com/openshift/api" packages = [ "apps/v1", @@ -955,7 +1005,7 @@ version = "v1.0.0" [[projects]] - digest = "1:e411054f54a332ecadd5fe5a4a68d092f36583e554037587da86da298cffc72d" + digest = "1:a6b0f01bc76800f17cb666c82e1dde9f219fa35cd6989a351a9f0ef8a5a7cbd6" name = "github.com/prometheus/client_golang" packages = [ "api", @@ -970,14 +1020,14 @@ [[projects]] branch = "master" - digest = "1:2d5cd61daa5565187e1d96bae64dbbc6080dacf741448e9629c64fd93203b0d4" + digest = "1:0f37e09b3e92aaeda5991581311f8dbf38944b36a3edec61cc2d1991f527554a" name = "github.com/prometheus/client_model" packages = ["go"] pruneopts = "NUT" revision = "fd36f4220a901265f90734c3183c5f0c91daa0b8" [[projects]] - digest = "1:4e776079b966091d3e6e12ed2aaf728bea5cd1175ef88bb654e03adbf5d4f5d3" + digest = "1:718e954e70b5f876b6bf756173ddc35603be265423e8d80661b1882cb1bd9a91" name = "github.com/prometheus/common" packages = [ "expfmt", @@ -990,7 +1040,7 @@ [[projects]] branch = "master" - digest = "1:612e0e6b31740c56ca06dcc7a58ef57379190bcc9eb7f7ec7698ef480fddf1da" + digest = "1:2aa721eb10a93c6216e8b864b42e78c20f787aa5b11010f8cfba5f7a8891f13f" name = "github.com/prometheus/procfs" packages = [ ".", @@ -1003,7 +1053,7 @@ revision = "e56f2e22fc761e82a34aca553f6725e2aff4fe6c" [[projects]] - digest = "1:e09ada96a5a41deda4748b1659cc8953961799e798aea557257b56baee4ecaf3" + digest = "1:9e5d599747d9210d7dee1d10efe147f344f99f9521bb3c6d557ab65f4c2cf4ef" name = "github.com/rogpeppe/go-internal" packages = [ "modfile", @@ -1031,7 +1081,23 @@ version = "v1.0.0" [[projects]] - digest = "1:90cf76d709ce9b057e7d75bd245bf7c1242d21ba4f908fb22c7a2a96d1dcc0ca" + branch = "master" + digest = "1:7d62418b0a0e438a72444d7bc4bd7f5c066acfbdf20dfa9e75edc64fe8540621" + name = "github.com/sony/sonyflake" + packages = ["."] + pruneopts = "NUT" + revision = "6d5bd61810093eae37d7d605d0cbdd845969b5b2" + +[[projects]] + digest = "1:7aa2c655b92b09fc47c7c69b629dcacdca2a7a3bf1837d69d04f96b5c3ff8454" + name = "github.com/speps/go-hashids" + packages = ["."] + pruneopts = "NUT" + revision = "ad304c6ede07ca1f915c30a5510c45860aa165a7" + version = "v2.0.0" + +[[projects]] + digest = "1:d0af1fc7035b6d64dfbe4af440d8c70e593ea44e60284d98b74787db004a7452" name = "github.com/spf13/afero" packages = [ ".", @@ -1042,7 +1108,7 @@ version = "v1.2.1" [[projects]] - digest = "1:343d44e06621142ab09ae0c76c1799104cdfddd3ffb445d78b1adf8dc3ffaf3d" + digest = "1:2c3b60fc961b7ddca4336bb7bb39146cb73ea2ad73d4afc6b4ffea05571e712a" name = "github.com/spf13/cobra" packages = ["."] pruneopts = "NUT" @@ -1058,7 +1124,7 @@ version = "v1.0.3" [[projects]] - digest = "1:89fd77d603a74a6540d60067debad9397865bf040955d907362c95d364baeba6" + digest = "1:8c262fc0329de35cbb1f1208ea1990446e0ea265171aafd523189ab9c587433b" name = "github.com/src-d/gcfg" packages = [ ".", @@ -1079,7 +1145,7 @@ version = "v0.1.1" [[projects]] - digest = "1:4fec2fe9ce617252d634de83ca8dd5f9c7c391637540896542c80ab63f071f51" + digest = "1:5f1187480cbc19150e63c774ed8007e7e64c1cb919a5e9ff7b0a0e5e1900775e" name = "github.com/stretchr/testify" packages = [ "assert", @@ -1098,7 +1164,7 @@ version = "v0.2.1" [[projects]] - digest = "1:4ec126f1327fe6b348ccf2d4974c38ccf2628b10ad30be7b3be53f483db85149" + digest = "1:6ae48b637ad9c325f1eeb116f1780cc310fe93d524bc8a2eb78a350f21a35dff" name = "github.com/xenolf/lego" packages = [ "acme", @@ -1139,7 +1205,7 @@ version = "v1.1.0" [[projects]] - digest = "1:85674ac609b704fd4e9f463553b6ffc3a3527a993ae0ba550eb56beaabdfe094" + digest = "1:244145848a183fe658a95c682d379142595c79733d854a7d4076fa72fb1b9eb8" name = "go.uber.org/zap" packages = [ ".", @@ -1155,7 +1221,7 @@ [[projects]] branch = "master" - digest = "1:a364cbf471c6403d39967b18ce1c11fb0d1e9a05a4aa66d5656cd45383f95ebe" + digest = "1:5cbcf748cfe80e3837b99eb98c37498c0eb4183b35c28659696731ead7a4dd5f" name = "golang.org/x/crypto" packages = [ "cast5", @@ -1184,7 +1250,7 @@ [[projects]] branch = "master" - digest = "1:3b0a30883b4833e6d256fac81b74cb287e97d8fb2ddc4e60974a990d5632401a" + digest = "1:4ef584461a8ab7d5e5883daca745b525450fe886a6839e583fbe24b65af527f1" name = "golang.org/x/net" packages = [ "bpf", @@ -1209,7 +1275,7 @@ [[projects]] branch = "master" - digest = "1:17ee74a4d9b6078611784b873cdbfe91892d2c73052c430724e66fcc015b6c7b" + digest = "1:cd20b2392f53dd5a6b3aff0ac551d02fae60274c673bf2b885e9a3ddadff5c4f" name = "golang.org/x/oauth2" packages = [ ".", @@ -1223,7 +1289,7 @@ [[projects]] branch = "master" - digest = "1:a64cdf903b3f6c7496d32d8eee29604facb0e20fc022e3e69d6950d0917cafa5" + digest = "1:a243573e9643276548ac3219e2243ccb7b3abce72d3fe442170a0dbf64a997a2" name = "golang.org/x/sys" packages = [ "cpu", @@ -1234,7 +1300,7 @@ revision = "fead79001313d15903fb4605b4a1b781532cd93e" [[projects]] - digest = "1:2c4e3b27329379d7b5f889038b48086140ea60708fb92e4b19d6a775926ea30b" + digest = "1:0ebfea43f83a5127e01acb49438dffeb32416312c3b73fa87c7fa6e39f7e03bc" name = "golang.org/x/text" packages = [ "collate", @@ -1279,7 +1345,7 @@ [[projects]] branch = "master" - digest = "1:e46d8e20161401a9cf8765dfa428494a3492a0b56fe114156b7da792bf41ba78" + digest = "1:8f1c06104804a7f5dda99effae51188e8d7bf28900a4e8ef3e4b198cc3406f7e" name = "golang.org/x/tools" packages = [ "go/ast/astutil", @@ -1299,7 +1365,7 @@ revision = "8b67d361bba210f5fbb3c1a0fc121e0847b10b57" [[projects]] - digest = "1:55c735e000d1eef22b3c2f3226447489bafa33f0159dfece150279d2f9e25f7c" + digest = "1:faa5e95fa65f513292bea81c0baa5b8697e3a4c04b77a968327d9b73bfa9e8e0" name = "google.golang.org/appengine" packages = [ ".", @@ -1360,7 +1426,7 @@ version = "v2.1" [[projects]] - digest = "1:d5547d77e1c9ca9850f3d868d29eed275742611eeae2b99bcd8a1f18f368b6e8" + digest = "1:1d8471ac1b112254af183f50adb0f41309cb1f83926befb09b7238357e90041c" name = "gopkg.in/square/go-jose.v2" packages = [ ".", @@ -1373,7 +1439,7 @@ version = "v2.3.0" [[projects]] - digest = "1:1cf1388ec8c73b7ecc711d9f279ab631ea0a6964d1ccc32809a6be90c33fa2a0" + digest = "1:3a27ea3585e1517381a8a9f26dbbc1b02f646ffcf1f58db4c4713c3ad7ae7117" name = "gopkg.in/src-d/go-billy.v4" packages = [ ".", @@ -1387,7 +1453,7 @@ version = "v4.3.0" [[projects]] - digest = "1:bcee186946d33b2e3befed808ee6ffe73387345807187e51790e3e148b5344c2" + digest = "1:388a60e173d00bf4256826ac6773a55ba384033e06b6593a89e4e4e402d208ec" name = "gopkg.in/src-d/go-git.v4" packages = [ ".", @@ -1461,7 +1527,7 @@ version = "v2.2.2" [[projects]] - digest = "1:0ae5137dc94a40cd72fe4fcdf9db0c119d2d63df04af5958cfd514a2a21b3988" + digest = "1:54a81502271a4e19f45dabc7ac58b6ba06513c0fff6f1ce9c555bde0e5061113" name = "k8s.io/api" packages = [ "admission/v1beta1", @@ -1503,7 +1569,7 @@ version = "kubernetes-1.13.1" [[projects]] - digest = "1:09949d3a2ceff9874863e6dd454f553696fdfac4a8d35b931cc7fd8556f7b5ca" + digest = "1:62a3fc47167844f281a867f4a23a0d11aa3b944005d72271f2f3f560f98a28ca" name = "k8s.io/apiextensions-apiserver" packages = [ "pkg/apis/apiextensions", @@ -1518,7 +1584,7 @@ version = "kubernetes-1.13.1" [[projects]] - digest = "1:0278d2edffad91a4feb86b4255a57c7b13eeddc2fc892c8053436f90cfa6e9bd" + digest = "1:7040b3db74b73ed260ed93b5cd74c40d5476887f97f8b916014e2949410b3128" name = "k8s.io/apimachinery" packages = [ "pkg/api/equality", @@ -1579,7 +1645,7 @@ version = "kubernetes-1.13.1" [[projects]] - digest = "1:60980a58a47ddc5616bef02dbe42855e63a718fb6de108cd8fc108afa7d0af84" + digest = "1:6df68d1649c3ddb4d47c6ad78f260582d5e55a4a9653c6e30fbd35822fff0699" name = "k8s.io/apiserver" packages = [ "pkg/apis/audit", @@ -1596,7 +1662,7 @@ version = "kubernetes-1.13.1" [[projects]] - digest = "1:174302b5262613baa367ba2fca9f07870b24b41b6ab9e0cdf22798febfa7ee80" + digest = "1:356ad4c66929ad8d8abcf058b68a2c05a55912a53dfad819bc2b6d141a2d618e" name = "k8s.io/client-go" packages = [ "discovery", @@ -1753,7 +1819,7 @@ version = "kubernetes-1.13.1" [[projects]] - digest = "1:dc1ae99dcab96913d81ae970b1f7a7411a54199b14bfb17a7e86f9a56979c720" + digest = "1:8f29fd4c578cae70e2e1673656e19e9bae4896c72b458f42b824dfe03d7ea3b2" name = "k8s.io/code-generator" packages = [ "cmd/client-gen", @@ -1772,7 +1838,7 @@ [[projects]] branch = "master" - digest = "1:462e6076bc1fde8da364901873fde3a9e66160d853d766b797a657d66e4b2cc4" + digest = "1:8279bfdd72fb8eb68d03b5a43acbff4109a79725a2717d98e90f15915eefdbd0" name = "k8s.io/gengo" packages = [ "args", @@ -1788,12 +1854,12 @@ revision = "b90029ef6cd877cb3f422d75b3a07707e3aac6b7" [[projects]] - digest = "1:c263611800c3a97991dbcf9d3bc4de390f6224aaa8ca0a7226a9d734f65a416a" + digest = "1:9cc257b3c9ff6a0158c9c661ab6eebda1fe8a4a4453cd5c4044dc9a2ebfb992b" name = "k8s.io/klog" packages = ["."] pruneopts = "NUT" - revision = "71442cd4037d612096940ceb0f3fec3f7fff66e0" - version = "v0.2.0" + revision = "a5bc97fbc634d635061f3146511332c7e313a55a" + version = "v0.1.0" [[projects]] branch = "master" @@ -1804,7 +1870,7 @@ revision = "15615b16d372105f0c69ff47dfe7402926a65aaa" [[projects]] - digest = "1:71c5cb5b157c506a5e0e94d186a03cddc007bae59c9ad5f460400e487aa28696" + digest = "1:ccabade367cf9af9bd03efe88d4c091b33e227bc4fb256ceff1b5e15211d4cea" name = "k8s.io/kubernetes" packages = [ "pkg/api/legacyscheme", @@ -1834,7 +1900,6 @@ "pkg/util/metrics", "pkg/util/net/sets", "pkg/util/parsers", - "pkg/util/slice", "pkg/util/taints", ] pruneopts = "NUT" @@ -1843,7 +1908,7 @@ [[projects]] branch = "master" - digest = "1:5b4cb4a89696f012fc162d3fcb16aecfecf979ce794cf29c44822c9aa9f4319e" + digest = "1:68a6a10a10ee5f93fc5e0c63c8857165b472a0c45b6e482342d8d25b17837bd5" name = "k8s.io/utils" packages = ["pointer"] pruneopts = "NUT" @@ -1858,7 +1923,7 @@ revision = "4ead7f1b87048b7717b3e474a21fdc07e6bce636" [[projects]] - digest = "1:2615e1a9c8353287333b45fd7334173bc6c8dac525ae5710dc2e3c80b7b3e6ba" + digest = "1:41e0826a5968b00d321ba7981be205017ca2bea433a8f85c01e2b3f4c62445eb" name = "sigs.k8s.io/controller-runtime" packages = [ "pkg/cache", @@ -1903,7 +1968,7 @@ version = "v0.1.10" [[projects]] - digest = "1:992675a6714d511089a0b7ffb7063d36e5423089cda610642de7a0cfbbf673ab" + digest = "1:170bb3fef81543de085fe9bcdfc97c8677563151b5badba8f6e8ebeabb73a310" name = "sigs.k8s.io/controller-tools" packages = [ "cmd/controller-gen", @@ -1922,7 +1987,7 @@ version = "v0.1.9" [[projects]] - digest = "1:237ad09ac561f3e3a2a2e9f3fee85fea98c0f41f381ac80562b05bdaa9fe89c6" + digest = "1:a9ab998a89bcd0ee9a5cf293943b79e2f889aa6a587b4f2ca3de113340e20418" name = "sigs.k8s.io/testing_frameworks" packages = [ "integration", @@ -1945,15 +2010,20 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ + "github.com/asaskevich/govalidator", "github.com/dgrijalva/jwt-go", "github.com/docker/docker/api/types", "github.com/docker/docker/client", "github.com/emicklei/go-restful", "github.com/emicklei/go-restful-openapi", + "github.com/fatih/structs", "github.com/go-ldap/ldap", "github.com/go-openapi/spec", "github.com/go-redis/redis", "github.com/go-sql-driver/mysql", + "github.com/gocraft/dbr", + "github.com/gocraft/dbr/dialect", + "github.com/golang/example/stringutil", "github.com/golang/glog", "github.com/google/uuid", "github.com/jinzhu/gorm", @@ -1969,14 +2039,19 @@ "github.com/kubesphere/s2ioperator/pkg/apis/devops/v1alpha1", "github.com/kubesphere/s2ioperator/pkg/client/clientset/versioned", "github.com/kubesphere/s2ioperator/pkg/client/informers/externalversions", + "github.com/kubesphere/sonargo/sonar", "github.com/mholt/caddy", "github.com/mholt/caddy/caddy/caddymain", "github.com/mholt/caddy/caddyhttp/httpserver", "github.com/onsi/ginkgo", "github.com/onsi/gomega", + "github.com/sony/sonyflake", + "github.com/speps/go-hashids", "github.com/spf13/cobra", "github.com/spf13/pflag", + "github.com/stretchr/testify/assert", "golang.org/x/net/context", + "golang.org/x/net/html", "gopkg.in/igm/sockjs-go.v2/sockjs", "gopkg.in/src-d/go-git.v4", "gopkg.in/src-d/go-git.v4/config", @@ -2004,6 +2079,7 @@ "k8s.io/apimachinery/pkg/runtime/serializer", "k8s.io/apimachinery/pkg/types", "k8s.io/apimachinery/pkg/util/json", + "k8s.io/apimachinery/pkg/util/net", "k8s.io/apimachinery/pkg/util/runtime", "k8s.io/apimachinery/pkg/util/sets", "k8s.io/apimachinery/pkg/util/wait", @@ -2037,7 +2113,6 @@ "k8s.io/kubernetes/pkg/apis/core", "k8s.io/kubernetes/pkg/controller", "k8s.io/kubernetes/pkg/util/metrics", - "k8s.io/kubernetes/pkg/util/slice", "sigs.k8s.io/application/pkg/controller/application", "sigs.k8s.io/controller-runtime/pkg/client", "sigs.k8s.io/controller-runtime/pkg/client/config", diff --git a/Gopkg.toml b/Gopkg.toml index 45009199b..6f837f0eb 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -121,3 +121,11 @@ required = [ [[constraint]] name = "gopkg.in/igm/sockjs-go.v2" version = "2.0.0" + +[[constraint]] + name = "github.com/gocraft/dbr" + revision = "a0fd650918f6287ffe111d1c7b66bb755ff3be4a" + +[[constraint]] + name = "github.com/kubesphere/sonargo" + version = "0.0.2" diff --git a/cmd/ks-apiserver/app/server.go b/cmd/ks-apiserver/app/server.go index 8cef8836a..95a324f79 100644 --- a/cmd/ks-apiserver/app/server.go +++ b/cmd/ks-apiserver/app/server.go @@ -30,10 +30,10 @@ import ( "kubesphere.io/kubesphere/pkg/apiserver/servicemesh/tracing" "kubesphere.io/kubesphere/pkg/filter" "kubesphere.io/kubesphere/pkg/informers" - "kubesphere.io/kubesphere/pkg/models" logging "kubesphere.io/kubesphere/pkg/models/log" "kubesphere.io/kubesphere/pkg/signals" - "kubesphere.io/kubesphere/pkg/simple/client/mysql" + "kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins" + "kubesphere.io/kubesphere/pkg/simple/client/devops_mysql" "log" "net/http" ) @@ -79,13 +79,10 @@ func Run(s *options.ServerRunOptions) error { } } + initializeAdminJenkins() + initializeDevOpsDatabase() initializeESClientConfig() initializeServicemeshConfig(s) - err = initializeDatabase() - - if err != nil { - return err - } if s.GenericServerRunOptions.InsecurePort != 0 { log.Printf("Server listening on %d.", s.GenericServerRunOptions.InsecurePort) @@ -100,14 +97,12 @@ func Run(s *options.ServerRunOptions) error { return err } -func initializeDatabase() error { - db := mysql.Client() - if !db.HasTable(&models.WorkspaceDPBinding{}) { - if err := db.CreateTable(&models.WorkspaceDPBinding{}).Error; err != nil { - return err - } - } - return nil +func initializeAdminJenkins() { + admin_jenkins.Client() +} + +func initializeDevOpsDatabase() { + devops_mysql.OpenDatabase() } func initializeServicemeshConfig(s *options.ServerRunOptions) { diff --git a/hack/docker_build.sh b/hack/docker_build.sh index 4ae937ef5..4ce3c4fa4 100755 --- a/hack/docker_build.sh +++ b/hack/docker_build.sh @@ -5,3 +5,5 @@ docker build -f build/ks-iam/Dockerfile -t kubespheredev/ks-account:latest . docker build -f build/controller-manager/Dockerfile -t kubespheredev/ks-controller-manager:latest . + + docker build -f ./pkg/db/Dockerfile -t kubespheredev/ks-devops:flyway ./pkg/db/ diff --git a/hack/docker_push.sh b/hack/docker_push.sh index 9367f279d..605816b6c 100755 --- a/hack/docker_push.sh +++ b/hack/docker_push.sh @@ -7,3 +7,4 @@ docker push kubespheredev/ks-apigateway:latest docker push kubespheredev/ks-apiserver:latest docker push kubespheredev/ks-account:latest docker push kubespheredev/ks-controller-manager:latest +docker push kubespheredev/ks-devops:flyway diff --git a/pkg/apiserver/tenant/tenant.go b/pkg/apiserver/tenant/tenant.go index 050cc57e4..837b5eebc 100644 --- a/pkg/apiserver/tenant/tenant.go +++ b/pkg/apiserver/tenant/tenant.go @@ -28,15 +28,15 @@ import ( "kubesphere.io/kubesphere/pkg/apiserver/logging" "kubesphere.io/kubesphere/pkg/constants" "kubesphere.io/kubesphere/pkg/errors" - "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/devops" "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/models/metrics" "kubesphere.io/kubesphere/pkg/models/resources" "kubesphere.io/kubesphere/pkg/models/tenant" "kubesphere.io/kubesphere/pkg/models/workspaces" "kubesphere.io/kubesphere/pkg/params" + "kubesphere.io/kubesphere/pkg/simple/client/elasticsearch" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" "kubesphere.io/kubesphere/pkg/utils/sliceutil" "net/http" "strings" @@ -242,17 +242,10 @@ func DeleteDevopsProject(req *restful.Request, resp *restful.Response) { return } - err = kubesphere.Client().DeleteDevopsProject(username, devops) + err, code := tenant.DeleteDevOpsProject(devops, username) if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) - return - } - - err = workspaces.UnBindDevopsProject(workspaceName, devops) - - if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + resp.WriteHeaderAndEntity(code, errors.Wrap(err)) return } @@ -264,7 +257,7 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) { workspaceName := req.PathParameter("workspace") username := req.HeaderParameter(constants.UserNameHeader) - var devops models.DevopsProject + var devops devops.DevOpsProject err := req.ReadEntity(&devops) @@ -274,10 +267,10 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) { } glog.Infoln("create workspace", username, workspaceName, devops) - project, err := workspaces.CreateDevopsProject(username, workspaceName, &devops) + project, err, code := tenant.CreateDevopsProject(username, workspaceName, &devops) if err != nil { - resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err)) + resp.WriteHeaderAndEntity(code, errors.Wrap(err)) return } diff --git a/pkg/db/Dockerfile b/pkg/db/Dockerfile new file mode 100644 index 000000000..92942ec6b --- /dev/null +++ b/pkg/db/Dockerfile @@ -0,0 +1,11 @@ +# Copyright 2017 The OpenPitrix Authors. All rights reserved. +# Use of this source code is governed by a Apache license +# that can be found in the LICENSE file. + +FROM dhoer/flyway:5.1.4-mysql-8.0.11-alpine + +RUN apk add --no-cache mysql-client + +COPY ./schema /flyway/sql +COPY ./ddl /flyway/sql/ddl +COPY ./scripts /flyway/sql/ddl diff --git a/pkg/db/condition.go b/pkg/db/condition.go new file mode 100644 index 000000000..7386106dc --- /dev/null +++ b/pkg/db/condition.go @@ -0,0 +1,109 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "strings" + + "github.com/gocraft/dbr" +) + +const ( + placeholder = "?" +) + +type EqCondition struct { + dbr.Builder + Column string + Value interface{} +} + +// Copy From vendor/github.com/gocraft/dbr/condition.go:36 +func buildCmp(d dbr.Dialect, buf dbr.Buffer, pred string, column string, value interface{}) error { + buf.WriteString(d.QuoteIdent(column)) + buf.WriteString(" ") + buf.WriteString(pred) + buf.WriteString(" ") + buf.WriteString(placeholder) + + buf.WriteValue(value) + return nil +} + +// And creates AND from a list of conditions +func And(cond ...dbr.Builder) dbr.Builder { + return dbr.And(cond...) +} + +// Or creates OR from a list of conditions +func Or(cond ...dbr.Builder) dbr.Builder { + return dbr.Or(cond...) +} + +func escape(str string) string { + return strings.Map(func(r rune) rune { + switch r { + case '%', '\'', '^', '[', ']', '!', '_': + return ' ' + } + return r + }, str) +} + +func Like(column string, value string) dbr.Builder { + value = "%" + strings.TrimSpace(escape(value)) + "%" + return dbr.BuildFunc(func(d dbr.Dialect, buf dbr.Buffer) error { + return buildCmp(d, buf, "LIKE", column, value) + }) +} + +// Eq is `=`. +// When value is nil, it will be translated to `IS NULL`. +// When value is a slice, it will be translated to `IN`. +// Otherwise it will be translated to `=`. +func Eq(column string, value interface{}) dbr.Builder { + return &EqCondition{ + Builder: dbr.Eq(column, value), + Column: column, + Value: value, + } +} + +// Neq is `!=`. +// When value is nil, it will be translated to `IS NOT NULL`. +// When value is a slice, it will be translated to `NOT IN`. +// Otherwise it will be translated to `!=`. +func Neq(column string, value interface{}) dbr.Builder { + return dbr.Neq(column, value) +} + +// Gt is `>`. +func Gt(column string, value interface{}) dbr.Builder { + return dbr.Gt(column, value) +} + +// Gte is '>='. +func Gte(column string, value interface{}) dbr.Builder { + return dbr.Gte(column, value) +} + +// Lt is '<'. +func Lt(column string, value interface{}) dbr.Builder { + return dbr.Lt(column, value) +} + +// Lte is `<=`. +func Lte(column string, value interface{}) dbr.Builder { + return dbr.Lte(column, value) +} diff --git a/pkg/db/condition_test.go b/pkg/db/condition_test.go new file mode 100644 index 000000000..dadeb5bc8 --- /dev/null +++ b/pkg/db/condition_test.go @@ -0,0 +1,88 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "testing" + + "github.com/gocraft/dbr" + "github.com/gocraft/dbr/dialect" + "github.com/stretchr/testify/assert" +) + +// Ref: https://github.com/gocraft/dbr/blob/5d59a8b3aa915660960329efb3af5513e7a0db07/condition_test.go +func TestCondition(t *testing.T) { + for _, test := range []struct { + cond dbr.Builder + query string + value []interface{} + }{ + { + cond: Eq("col", 1), + query: "`col` = ?", + value: []interface{}{1}, + }, + { + cond: Eq("col", nil), + query: "`col` IS NULL", + value: nil, + }, + { + cond: Eq("col", []int{}), + query: "0", + value: nil, + }, + { + cond: Neq("col", 1), + query: "`col` != ?", + value: []interface{}{1}, + }, + { + cond: Neq("col", nil), + query: "`col` IS NOT NULL", + value: nil, + }, + { + cond: Gt("col", 1), + query: "`col` > ?", + value: []interface{}{1}, + }, + { + cond: Gte("col", 1), + query: "`col` >= ?", + value: []interface{}{1}, + }, + { + cond: Lt("col", 1), + query: "`col` < ?", + value: []interface{}{1}, + }, + { + cond: Lte("col", 1), + query: "`col` <= ?", + value: []interface{}{1}, + }, + { + cond: And(Lt("a", 1), Or(Gt("b", 2), Neq("c", 3))), + query: "(`a` < ?) AND ((`b` > ?) OR (`c` != ?))", + value: []interface{}{1, 2, 3}, + }, + } { + buf := dbr.NewBuffer() + err := test.cond.Build(dialect.MySQL, buf) + assert.NoError(t, err) + assert.Equal(t, test.query, buf.String()) + assert.Equal(t, test.value, buf.Value()) + } +} diff --git a/pkg/db/db.go b/pkg/db/db.go new file mode 100644 index 000000000..ee2822d56 --- /dev/null +++ b/pkg/db/db.go @@ -0,0 +1,283 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "database/sql" + "fmt" + "strings" + + _ "github.com/go-sql-driver/mysql" + "github.com/gocraft/dbr" +) + +const ( + DefaultSelectLimit = 200 +) + +func GetLimit(n uint64) uint64 { + if n < 0 { + n = 0 + } + if n > DefaultSelectLimit { + n = DefaultSelectLimit + } + return n +} + +func GetOffset(n uint64) uint64 { + if n < 0 { + n = 0 + } + return n +} + +type InsertHook func(query *InsertQuery) +type UpdateHook func(query *UpdateQuery) +type DeleteHook func(query *DeleteQuery) + +type Database struct { + *dbr.Session + InsertHook InsertHook + UpdateHook UpdateHook + DeleteHook DeleteHook +} + +type SelectQuery struct { + *dbr.SelectBuilder + JoinCount int // for join filter +} + +type InsertQuery struct { + *dbr.InsertBuilder + Hook InsertHook +} + +type DeleteQuery struct { + *dbr.DeleteBuilder + Hook DeleteHook +} + +type UpdateQuery struct { + *dbr.UpdateBuilder + Hook UpdateHook +} + +type UpsertQuery struct { + table string + *dbr.Session + whereConds map[string]string + upsertValues map[string]interface{} +} + +// SelectQuery +// Example: Select().From().Where().Limit().Offset().OrderDir().Load() +// Select().From().Where().Limit().Offset().OrderDir().LoadOne() +// Select().From().Where().Count() +// SelectAll().From().Where().Limit().Offset().OrderDir().Load() +// SelectAll().From().Where().Limit().Offset().OrderDir().LoadOne() +// SelectAll().From().Where().Count() + +func (db *Database) Select(columns ...string) *SelectQuery { + return &SelectQuery{db.Session.Select(columns...), 0} +} + +func (db *Database) SelectBySql(query string, value ...interface{}) *SelectQuery { + return &SelectQuery{db.Session.SelectBySql(query, value...), 0} +} + +func (db *Database) SelectAll(columns ...string) *SelectQuery { + return &SelectQuery{db.Session.Select("*"), 0} +} + +func (b *SelectQuery) Join(table, on interface{}) *SelectQuery { + b.SelectBuilder.Join(table, on) + return b +} + +func (b *SelectQuery) JoinAs(table string, alias string, on interface{}) *SelectQuery { + b.SelectBuilder.Join(dbr.I(table).As(alias), on) + return b +} + +func (b *SelectQuery) From(table string) *SelectQuery { + b.SelectBuilder.From(table) + return b +} + +func (b *SelectQuery) Where(query interface{}, value ...interface{}) *SelectQuery { + b.SelectBuilder.Where(query, value...) + return b +} + +func (b *SelectQuery) GroupBy(col ...string) *SelectQuery { + b.SelectBuilder.GroupBy(col...) + return b +} + +func (b *SelectQuery) Distinct() *SelectQuery { + b.SelectBuilder.Distinct() + return b +} + +func (b *SelectQuery) Limit(n uint64) *SelectQuery { + n = GetLimit(n) + b.SelectBuilder.Limit(n) + return b +} + +func (b *SelectQuery) Offset(n uint64) *SelectQuery { + n = GetLimit(n) + b.SelectBuilder.Offset(n) + return b +} + +func (b *SelectQuery) OrderDir(col string, isAsc bool) *SelectQuery { + b.SelectBuilder.OrderDir(col, isAsc) + return b +} + +func (b *SelectQuery) Load(value interface{}) (int, error) { + return b.SelectBuilder.Load(value) +} + +func (b *SelectQuery) LoadOne(value interface{}) error { + return b.SelectBuilder.LoadOne(value) +} + +func getColumns(dbrColumns []interface{}) string { + var columns []string + for _, column := range dbrColumns { + if c, ok := column.(string); ok { + columns = append(columns, c) + } + } + return strings.Join(columns, ", ") +} + +func (b *SelectQuery) Count() (count uint32, err error) { + // cache SelectStmt + selectStmt := b.SelectStmt + + limit := selectStmt.LimitCount + offset := selectStmt.OffsetCount + column := selectStmt.Column + isDistinct := selectStmt.IsDistinct + order := selectStmt.Order + + b.SelectStmt.LimitCount = -1 + b.SelectStmt.OffsetCount = -1 + b.SelectStmt.Column = []interface{}{"COUNT(*)"} + b.SelectStmt.Order = []dbr.Builder{} + + if isDistinct { + b.SelectStmt.Column = []interface{}{fmt.Sprintf("COUNT(DISTINCT %s)", getColumns(column))} + b.SelectStmt.IsDistinct = false + } + + err = b.LoadOne(&count) + // fallback SelectStmt + selectStmt.LimitCount = limit + selectStmt.OffsetCount = offset + selectStmt.Column = column + selectStmt.IsDistinct = isDistinct + selectStmt.Order = order + b.SelectStmt = selectStmt + return +} + +// InsertQuery +// Example: InsertInto().Columns().Record().Exec() + +func (db *Database) InsertInto(table string) *InsertQuery { + return &InsertQuery{db.Session.InsertInto(table), db.InsertHook} +} + +func (b *InsertQuery) Exec() (sql.Result, error) { + result, err := b.InsertBuilder.Exec() + if b.Hook != nil && err == nil { + defer b.Hook(b) + } + return result, err +} + +func (b *InsertQuery) Columns(columns ...string) *InsertQuery { + b.InsertBuilder.Columns(columns...) + return b +} + +func (b *InsertQuery) Record(structValue interface{}) *InsertQuery { + b.InsertBuilder.Record(structValue) + return b +} + +// DeleteQuery +// Example: DeleteFrom().Where().Limit().Exec() + +func (db *Database) DeleteFrom(table string) *DeleteQuery { + return &DeleteQuery{db.Session.DeleteFrom(table), db.DeleteHook} +} + +func (b *DeleteQuery) Where(query interface{}, value ...interface{}) *DeleteQuery { + b.DeleteBuilder.Where(query, value...) + return b +} + +func (b *DeleteQuery) Limit(n uint64) *DeleteQuery { + b.DeleteBuilder.Limit(n) + return b +} + +func (b *DeleteQuery) Exec() (sql.Result, error) { + result, err := b.DeleteBuilder.Exec() + if b.Hook != nil && err == nil { + defer b.Hook(b) + } + return result, err +} + +// UpdateQuery +// Example: Update().Set().Where().Exec() + +func (db *Database) Update(table string) *UpdateQuery { + return &UpdateQuery{db.Session.Update(table), db.UpdateHook} +} + +func (b *UpdateQuery) Exec() (sql.Result, error) { + result, err := b.UpdateBuilder.Exec() + if b.Hook != nil && err == nil { + defer b.Hook(b) + } + return result, err +} + +func (b *UpdateQuery) Set(column string, value interface{}) *UpdateQuery { + b.UpdateBuilder.Set(column, value) + return b +} + +func (b *UpdateQuery) SetMap(m map[string]interface{}) *UpdateQuery { + b.UpdateBuilder.SetMap(m) + return b +} + +func (b *UpdateQuery) Where(query interface{}, value ...interface{}) *UpdateQuery { + b.UpdateBuilder.Where(query, value...) + return b +} + +func (b *UpdateQuery) Limit(n uint64) *UpdateQuery { + b.UpdateBuilder.Limit(n) + return b +} diff --git a/pkg/db/ddl/devops.sql b/pkg/db/ddl/devops.sql new file mode 100644 index 000000000..06a5b89be --- /dev/null +++ b/pkg/db/ddl/devops.sql @@ -0,0 +1,2 @@ +CREATE DATABASE IF NOT EXISTS devops DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; +CREATE DATABASE IF NOT EXISTS kubesphere DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; diff --git a/pkg/db/error.go b/pkg/db/error.go new file mode 100644 index 000000000..2e3580650 --- /dev/null +++ b/pkg/db/error.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "github.com/gocraft/dbr" +) + +// package errors +var ( + ErrNotFound = dbr.ErrNotFound + ErrNotSupported = dbr.ErrNotSupported + ErrTableNotSpecified = dbr.ErrTableNotSpecified + ErrColumnNotSpecified = dbr.ErrColumnNotSpecified + ErrInvalidPointer = dbr.ErrInvalidPointer + ErrPlaceholderCount = dbr.ErrPlaceholderCount + ErrInvalidSliceLength = dbr.ErrInvalidSliceLength + ErrCantConvertToTime = dbr.ErrCantConvertToTime + ErrInvalidTimestring = dbr.ErrInvalidTimestring +) diff --git a/pkg/db/event.go b/pkg/db/event.go new file mode 100644 index 000000000..b63debe8e --- /dev/null +++ b/pkg/db/event.go @@ -0,0 +1,55 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package db + +import ( + "github.com/golang/glog" +) + +// EventReceiver is a sentinel EventReceiver; use it if the caller doesn't supply one +type EventReceiver struct{} + +// Event receives a simple notification when various events occur +func (n *EventReceiver) Event(eventName string) { + +} + +// EventKv receives a notification when various events occur along with +// optional key/value data +func (n *EventReceiver) EventKv(eventName string, kvs map[string]string) { +} + +// EventErr receives a notification of an error if one occurs +func (n *EventReceiver) EventErr(eventName string, err error) error { + return err +} + +// EventErrKv receives a notification of an error if one occurs along with +// optional key/value data +func (n *EventReceiver) EventErrKv(eventName string, err error, kvs map[string]string) error { + glog.Errorf("%+v", err) + glog.Errorf("%s: %+v", eventName, kvs) + return err +} + +// Timing receives the time an event took to happen +func (n *EventReceiver) Timing(eventName string, nanoseconds int64) { + +} + +// TimingKv receives the time an event took to happen along with optional key/value data +func (n *EventReceiver) TimingKv(eventName string, nanoseconds int64, kvs map[string]string) { + // TODO: Change logger level to debug + glog.Infof("%s spend %.2fms: %+v", eventName, float32(nanoseconds)/1000000, kvs) +} diff --git a/pkg/db/schema/devops/V0_1__init.sql b/pkg/db/schema/devops/V0_1__init.sql new file mode 100644 index 000000000..1c8ce547d --- /dev/null +++ b/pkg/db/schema/devops/V0_1__init.sql @@ -0,0 +1,23 @@ +CREATE TABLE project ( + `project_id` VARCHAR(50) NOT NULL, + `name` VARCHAR(50) NOT NULL, + `description` TEXT NOT NULL, + `creator` VARCHAR(50) NOT NULL, + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + `status` VARCHAR(50) NOT NULL, + `visibility` VARCHAR(50) NOT NULL, + `extra` TEXT NOT NULL, + PRIMARY KEY (`project_id`) +); + + + +CREATE TABLE `project_membership` ( + `username` VARCHAR(50) NOT NULL, + `project_id` VARCHAR(50) NOT NULL, + `role` VARCHAR(50) NOT NULL, + `status` VARCHAR(50) NOT NULL, + `grant_by` VARCHAR(50) NOT NULL, + PRIMARY KEY (`username`, `project_id`) +); + diff --git a/pkg/db/schema/devops/V0_2__credential.sql b/pkg/db/schema/devops/V0_2__credential.sql new file mode 100644 index 000000000..713744964 --- /dev/null +++ b/pkg/db/schema/devops/V0_2__credential.sql @@ -0,0 +1,8 @@ +CREATE TABLE `project_credential` ( + `project_id` VARCHAR(50) NOT NULL, + `credential_id` VARCHAR(255) NOT NULL, + `domain` VARCHAR(255) NOT NULL, + `creator` VARCHAR(50) NOT NULL, + `create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`project_id`, `credential_id`, `domain`) +); diff --git a/pkg/db/schema/devops/V0_3__workspace.sql b/pkg/db/schema/devops/V0_3__workspace.sql new file mode 100644 index 000000000..9275b69bd --- /dev/null +++ b/pkg/db/schema/devops/V0_3__workspace.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS `kubesphere.workspace_dp_bindings` ( + `workspace` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, + `dev_ops_project` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL, + PRIMARY KEY (`workspace`,`dev_ops_project`) +) ENGINE=InnoDB + +ALTER TABLE kubesphere.workspace_dp_bindings +CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +ALTER TABLE devops.project +ADD COLUMN workspace VARCHAR(255) NOT NULL DEFAULT ''; + +UPDATE devops.project t1 +INNER JOIN kubesphere.workspace_dp_bindings t2 ON t1.project_id= t2.dev_ops_project +SET t1.workspace=t2.workspace; diff --git a/pkg/db/scripts/ddl_init.sh b/pkg/db/scripts/ddl_init.sh new file mode 100755 index 000000000..ab5d9cb19 --- /dev/null +++ b/pkg/db/scripts/ddl_init.sh @@ -0,0 +1,17 @@ +#!/bin/sh + +cd /flyway/sql/ddl + +[ -n "$PASSWORD" ] && OPT="-p$(echo "$PASSWORD" | tr -d '\n')" + +for F in $(ls *.sql) +do + echo "Start process $F" + mysql "$@" "$OPT" < "$F" + if [ $? -ne 0 ]; then + echo "Process $F failed" + return 1 + else + echo "Process $F successful" + fi +done diff --git a/pkg/gojenkins/README.md b/pkg/gojenkins/README.md new file mode 100644 index 000000000..210b10562 --- /dev/null +++ b/pkg/gojenkins/README.md @@ -0,0 +1,24 @@ +# Jenkins API Client for Go + + +## About + +Jenkins is the most popular Open Source Continuous Integration system. This Library will help you interact with Jenkins in a more developer-friendly way. + +Fork From https://github.com/bndr/gojenkins + +These are some of the features that are currently implemented: + +* Get information on test-results of completed/failed build +* Ability to query Nodes, and manipulate them. Start, Stop, set Offline. +* Ability to query Jobs, and manipulate them. +* Get Plugins, Builds, Artifacts, Fingerprints +* Validate Fingerprints of Artifacts +* Get Current Queue, Cancel Tasks +* etc. For all methods go to GoDoc Reference. + +Add some features: + +* Credentials Management +* Pipeline Model Converter +* RBAC control diff --git a/pkg/gojenkins/_tests/build_history.txt b/pkg/gojenkins/_tests/build_history.txt new file mode 100644 index 000000000..b9c777d37 --- /dev/null +++ b/pkg/gojenkins/_tests/build_history.txt @@ -0,0 +1,4 @@ + + + +
\ No newline at end of file diff --git a/pkg/gojenkins/_tests/job.xml b/pkg/gojenkins/_tests/job.xml new file mode 100644 index 000000000..76a65ec25 --- /dev/null +++ b/pkg/gojenkins/_tests/job.xml @@ -0,0 +1,27 @@ + + + + Some Job Description + false + + + + + params1 + description + defaultVal + + + + + + true + false + false + false + + false + + + + diff --git a/pkg/gojenkins/artifact.go b/pkg/gojenkins/artifact.go new file mode 100644 index 000000000..364940656 --- /dev/null +++ b/pkg/gojenkins/artifact.go @@ -0,0 +1,118 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "crypto/md5" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "path" +) + +// Represents an Artifact +type Artifact struct { + Jenkins *Jenkins + Build *Build + FileName string + Path string +} + +// Get raw byte data of Artifact +func (a Artifact) GetData() ([]byte, error) { + var data string + response, err := a.Jenkins.Requester.Get(a.Path, &data, nil) + + if err != nil { + return nil, err + } + + code := response.StatusCode + if code != 200 { + Error.Printf("Jenkins responded with StatusCode: %d", code) + return nil, errors.New("Could not get File Contents") + } + return []byte(data), nil +} + +// Save artifact to a specific path, using your own filename. +func (a Artifact) Save(path string) (bool, error) { + data, err := a.GetData() + + if err != nil { + return false, errors.New("No Data received, not saving file.") + } + + if _, err = os.Stat(path); err == nil { + Warning.Println("Local Copy already exists, Overwriting...") + } + + err = ioutil.WriteFile(path, data, 0644) + a.validateDownload(path) + + if err != nil { + return false, err + } + return true, nil +} + +// Save Artifact to directory using Artifact filename. +func (a Artifact) SaveToDir(dir string) (bool, error) { + if _, err := os.Stat(dir); err != nil { + Error.Printf("can't save artifact: directory %s does not exist", dir) + return false, fmt.Errorf("can't save artifact: directory %s does not exist", dir) + } + saved, err := a.Save(path.Join(dir, a.FileName)) + if err != nil { + return saved, nil + } + return saved, nil +} + +// Compare Remote and local MD5 +func (a Artifact) validateDownload(path string) (bool, error) { + localHash := a.getMD5local(path) + + fp := FingerPrint{Jenkins: a.Jenkins, Base: "/fingerprint/", Id: localHash, Raw: new(FingerPrintResponse)} + + valid, err := fp.ValidateForBuild(a.FileName, a.Build) + + if err != nil { + return false, err + } + if !valid { + return false, errors.New("FingerPrint of the downloaded artifact could not be verified") + } + return true, nil +} + +// Get Local MD5 +func (a Artifact) getMD5local(path string) string { + h := md5.New() + localFile, err := os.Open(path) + if err != nil { + return "" + } + buffer := make([]byte, 2^20) + n, err := localFile.Read(buffer) + defer localFile.Close() + for err == nil { + io.WriteString(h, string(buffer[0:n])) + n, err = localFile.Read(buffer) + } + return fmt.Sprintf("%x", h.Sum(nil)) +} diff --git a/pkg/gojenkins/build.go b/pkg/gojenkins/build.go new file mode 100644 index 000000000..69aea39bc --- /dev/null +++ b/pkg/gojenkins/build.go @@ -0,0 +1,480 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "bytes" + "errors" + "net/http" + "net/url" + "regexp" + "strconv" + "time" +) + +type Build struct { + Raw *BuildResponse + Job *Job + Jenkins *Jenkins + Base string + Depth int +} + +type parameter struct { + Name string + Value string +} + +type branch struct { + SHA1 string `json:",omitempty"` + Name string `json:",omitempty"` +} + +type BuildRevision struct { + SHA1 string `json:"SHA1,omitempty"` + Branch []branch `json:"branch,omitempty"` +} + +type Builds struct { + BuildNumber int64 `json:"buildNumber"` + BuildResult interface{} `json:"buildResult"` + Marked BuildRevision `json:"marked"` + Revision BuildRevision `json:"revision"` +} + +type Culprit struct { + AbsoluteUrl string + FullName string +} + +type GeneralObj struct { + Parameters []parameter `json:"parameters,omitempty"` + Causes []map[string]interface{} `json:"causes,omitempty"` + BuildsByBranchName map[string]Builds `json:"buildsByBranchName,omitempty"` + LastBuiltRevision *BuildRevision `json:"lastBuiltRevision,omitempty"` + RemoteUrls []string `json:"remoteUrls,omitempty"` + ScmName string `json:"scmName,omitempty"` + MercurialNodeName string `json:"mercurialNodeName,omitempty"` + MercurialRevisionNumber string `json:"mercurialRevisionNumber,omitempty"` + Subdir interface{} `json:"subdir,omitempty"` + ClassName string `json:"_class,omitempty"` + SonarTaskId string `json:"ceTaskId,omitempty"` + SonarServerUrl string `json:"serverUrl,omitempty"` + SonarDashboardUrl string `json:"sonarqubeDashboardUrl,omitempty"` + TotalCount int64 `json:",omitempty"` + UrlName string `json:",omitempty"` +} + +type TestResult struct { + Duration int64 `json:"duration"` + Empty bool `json:"empty"` + FailCount int64 `json:"failCount"` + PassCount int64 `json:"passCount"` + SkipCount int64 `json:"skipCount"` + Suites []struct { + Cases []struct { + Age int64 `json:"age"` + ClassName string `json:"className"` + Duration int64 `json:"duration"` + ErrorDetails interface{} `json:"errorDetails"` + ErrorStackTrace interface{} `json:"errorStackTrace"` + FailedSince int64 `json:"failedSince"` + Name string `json:"name"` + Skipped bool `json:"skipped"` + SkippedMessage interface{} `json:"skippedMessage"` + Status string `json:"status"` + Stderr interface{} `json:"stderr"` + Stdout interface{} `json:"stdout"` + } `json:"cases"` + Duration int64 `json:"duration"` + ID interface{} `json:"id"` + Name string `json:"name"` + Stderr interface{} `json:"stderr"` + Stdout interface{} `json:"stdout"` + Timestamp interface{} `json:"timestamp"` + } `json:"suites"` +} + +type BuildResponse struct { + Actions []GeneralObj + Artifacts []struct { + DisplayPath string `json:"displayPath"` + FileName string `json:"fileName"` + RelativePath string `json:"relativePath"` + } `json:"artifacts"` + Building bool `json:"building"` + BuiltOn string `json:"builtOn"` + ChangeSet struct { + Items []struct { + AffectedPaths []string `json:"affectedPaths"` + Author struct { + AbsoluteUrl string `json:"absoluteUrl"` + FullName string `json:"fullName"` + } `json:"author"` + Comment string `json:"comment"` + CommitID string `json:"commitId"` + Date string `json:"date"` + ID string `json:"id"` + Msg string `json:"msg"` + Paths []struct { + EditType string `json:"editType"` + File string `json:"file"` + } `json:"paths"` + Timestamp int64 `json:"timestamp"` + } `json:"items"` + Kind string `json:"kind"` + Revisions []struct { + Module string + Revision int + } `json:"revision"` + } `json:"changeSet"` + Culprits []Culprit `json:"culprits"` + Description interface{} `json:"description"` + Duration int64 `json:"duration"` + EstimatedDuration int64 `json:"estimatedDuration"` + Executor interface{} `json:"executor"` + FullDisplayName string `json:"fullDisplayName"` + ID string `json:"id"` + KeepLog bool `json:"keepLog"` + Number int64 `json:"number"` + QueueID int64 `json:"queueId"` + Result string `json:"result"` + Timestamp int64 `json:"timestamp"` + URL string `json:"url"` + MavenArtifacts interface{} `json:"mavenArtifacts"` + MavenVersionUsed string `json:"mavenVersionUsed"` + FingerPrint []FingerPrintResponse + Runs []struct { + Number int64 + URL string + } `json:"runs"` +} + +// Builds +func (b *Build) Info() *BuildResponse { + return b.Raw +} + +func (b *Build) GetActions() []GeneralObj { + return b.Raw.Actions +} + +func (b *Build) GetUrl() string { + return b.Raw.URL +} + +func (b *Build) GetBuildNumber() int64 { + return b.Raw.Number +} +func (b *Build) GetResult() string { + return b.Raw.Result +} + +func (b *Build) GetArtifacts() []Artifact { + artifacts := make([]Artifact, len(b.Raw.Artifacts)) + for i, artifact := range b.Raw.Artifacts { + artifacts[i] = Artifact{ + Jenkins: b.Jenkins, + Build: b, + FileName: artifact.FileName, + Path: b.Base + "/artifact/" + artifact.RelativePath, + } + } + return artifacts +} + +func (b *Build) GetCulprits() []Culprit { + return b.Raw.Culprits +} + +func (b *Build) Stop() (bool, error) { + if b.IsRunning() { + response, err := b.Jenkins.Requester.Post(b.Base+"/stop", nil, nil, nil) + if err != nil { + return false, err + } + if response.StatusCode != http.StatusOK { + return false, errors.New(strconv.Itoa(response.StatusCode)) + } + } + return true, nil +} + +func (b *Build) GetConsoleOutput() string { + url := b.Base + "/consoleText" + var content string + b.Jenkins.Requester.GetXML(url, &content, nil) + return content +} + +func (b *Build) GetCauses() ([]map[string]interface{}, error) { + _, err := b.Poll() + if err != nil { + return nil, err + } + for _, a := range b.Raw.Actions { + if a.Causes != nil { + return a.Causes, nil + } + } + return nil, errors.New("No Causes") +} + +func (b *Build) GetParameters() []parameter { + for _, a := range b.Raw.Actions { + if a.Parameters != nil { + return a.Parameters + } + } + return nil +} + +func (b *Build) GetInjectedEnvVars() (map[string]string, error) { + var envVars struct { + EnvMap map[string]string `json:"envMap"` + } + endpoint := b.Base + "/injectedEnvVars" + _, err := b.Jenkins.Requester.GetJSON(endpoint, &envVars, nil) + if err != nil { + return envVars.EnvMap, err + } + return envVars.EnvMap, nil +} + +func (b *Build) GetDownstreamBuilds() ([]*Build, error) { + result := make([]*Build, 0) + downstreamJobs, err := b.Job.GetDownstreamJobs() + if err != nil { + return nil, err + } + for _, job := range downstreamJobs { + allBuildIDs, err := job.GetAllBuildIds() + if err != nil { + return nil, err + } + for _, buildID := range allBuildIDs { + build, err := job.GetBuild(buildID.Number) + if err != nil { + return nil, err + } + upstreamBuild, _ := build.GetUpstreamBuild() + // cannot compare only id, it can be from different job + if b.GetUrl() == upstreamBuild.GetUrl() { + result = append(result, build) + break + } + } + } + return result, nil +} + +func (b *Build) GetDownstreamJobNames() []string { + result := make([]string, 0) + downstreamJobs := b.Job.GetDownstreamJobsMetadata() + fingerprints := b.GetAllFingerPrints() + for _, fingerprint := range fingerprints { + for _, usage := range fingerprint.Raw.Usage { + for _, job := range downstreamJobs { + if job.Name == usage.Name { + result = append(result, job.Name) + } + } + } + } + return result +} + +func (b *Build) GetAllFingerPrints() []*FingerPrint { + b.Poll(3) + result := make([]*FingerPrint, len(b.Raw.FingerPrint)) + for i, f := range b.Raw.FingerPrint { + result[i] = &FingerPrint{Jenkins: b.Jenkins, Base: "/fingerprint/", Id: f.Hash, Raw: &f} + } + return result +} + +func (b *Build) GetUpstreamJob() (*Job, error) { + causes, err := b.GetCauses() + if err != nil { + return nil, err + } + if len(causes) > 0 { + if job, ok := causes[0]["upstreamProject"]; ok { + return b.Jenkins.GetJob(job.(string)) + } + } + return nil, errors.New("Unable to get Upstream Job") +} + +func (b *Build) GetUpstreamBuildNumber() (int64, error) { + causes, err := b.GetCauses() + if err != nil { + return 0, err + } + if len(causes) > 0 { + if build, ok := causes[0]["upstreamBuild"]; ok { + switch t := build.(type) { + default: + return t.(int64), nil + case float64: + return int64(t), nil + } + } + } + return 0, nil +} + +func (b *Build) GetUpstreamBuild() (*Build, error) { + job, err := b.GetUpstreamJob() + if err != nil { + return nil, err + } + if job != nil { + buildNumber, err := b.GetUpstreamBuildNumber() + if err == nil { + return job.GetBuild(buildNumber) + } + } + return nil, errors.New("Build not found") +} + +func (b *Build) GetMatrixRuns() ([]*Build, error) { + _, err := b.Poll(0) + if err != nil { + return nil, err + } + runs := b.Raw.Runs + result := make([]*Build, len(b.Raw.Runs)) + r, _ := regexp.Compile(`job/(.*?)/(.*?)/(\d+)/`) + + for i, run := range runs { + result[i] = &Build{Jenkins: b.Jenkins, Job: b.Job, Raw: new(BuildResponse), Depth: 1, Base: "/" + r.FindString(run.URL)} + result[i].Poll() + } + return result, nil +} + +func (b *Build) GetResultSet() (*TestResult, error) { + + url := b.Base + "/testReport" + var report TestResult + + _, err := b.Jenkins.Requester.GetJSON(url, &report, nil) + if err != nil { + return nil, err + } + + return &report, nil + +} + +func (b *Build) GetTimestamp() time.Time { + msInt := int64(b.Raw.Timestamp) + return time.Unix(0, msInt*int64(time.Millisecond)) +} + +func (b *Build) GetDuration() int64 { + return b.Raw.Duration +} + +func (b *Build) GetRevision() string { + vcs := b.Raw.ChangeSet.Kind + + if vcs == "git" || vcs == "hg" { + for _, a := range b.Raw.Actions { + if a.LastBuiltRevision.SHA1 != "" { + return a.LastBuiltRevision.SHA1 + } + if a.MercurialRevisionNumber != "" { + return a.MercurialRevisionNumber + } + } + } else if vcs == "svn" { + return strconv.Itoa(b.Raw.ChangeSet.Revisions[0].Revision) + } + return "" +} + +func (b *Build) GetRevisionBranch() string { + vcs := b.Raw.ChangeSet.Kind + if vcs == "git" { + for _, a := range b.Raw.Actions { + if len(a.LastBuiltRevision.Branch) > 0 && a.LastBuiltRevision.Branch[0].SHA1 != "" { + return a.LastBuiltRevision.Branch[0].SHA1 + } + } + } else { + panic("Not implemented") + } + return "" +} + +func (b *Build) IsGood() bool { + return (!b.IsRunning() && b.Raw.Result == STATUS_SUCCESS) +} + +func (b *Build) IsRunning() bool { + _, err := b.Poll() + if err != nil { + return false + } + return b.Raw.Building +} + +func (b *Build) SetDescription(description string) error { + data := url.Values{} + data.Set("description", description) + _, err := b.Jenkins.Requester.Post(b.Base+"/submitDescription", bytes.NewBufferString(data.Encode()), nil, nil) + return err +} +func (b *Build) PauseToggle() error { + response, err := b.Jenkins.Requester.Post(b.Base+"/pause/toggle", nil, nil, nil) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +// Poll for current data. Optional parameter - depth. +// More about depth here: https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API +func (b *Build) Poll(options ...interface{}) (int, error) { + depth := "-1" + + for _, o := range options { + switch v := o.(type) { + case string: + depth = v + case int: + depth = strconv.Itoa(v) + case int64: + depth = strconv.FormatInt(v, 10) + } + } + if depth == "-1" { + depth = strconv.Itoa(b.Depth) + } + + qr := map[string]string{ + "depth": depth, + } + response, err := b.Jenkins.Requester.GetJSON(b.Base, b.Raw, qr) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/gojenkins/build_history.go b/pkg/gojenkins/build_history.go new file mode 100644 index 000000000..2e7cae117 --- /dev/null +++ b/pkg/gojenkins/build_history.go @@ -0,0 +1,109 @@ +package gojenkins + +import ( + "io" + "strconv" + "strings" + + "golang.org/x/net/html" +) + +// Parse jenkins ajax response in order find the current jenkins build history +func parseBuildHistory(d io.Reader) []*History { + z := html.NewTokenizer(d) + depth := 0 + buildRowCellDepth := -1 + builds := make([]*History, 0) + var curBuild *History + for { + tt := z.Next() + switch tt { + case html.ErrorToken: + if z.Err() == io.EOF { + return builds + } + case html.SelfClosingTagToken: + tn, hasAttr := z.TagName() + // fmt.Println("START__", string(tn), hasAttr) + if hasAttr { + a := attr(z) + // Failed > Console Output + if string(tn) == "img" { + if hasCSSClass(a, "icon-sm") && buildRowCellDepth > -1 { + if alt, found := a["alt"]; found { + curBuild.BuildStatus = strings.Fields(alt)[0] + } + } + } + } + case html.StartTagToken: + depth++ + tn, hasAttr := z.TagName() + // fmt.Println("START__", string(tn), hasAttr) + if hasAttr { + a := attr(z) + // + if string(tn) == "td" { + if hasCSSClass(a, "build-row-cell") { + buildRowCellDepth = depth + curBuild = &History{} + builds = append(builds, curBuild) + } + } + // #227 + if string(tn) == "a" { + if hasCSSClass(a, "build-link") && buildRowCellDepth > -1 { + if href, found := a["href"]; found { + parts := strings.Split(href, "/") + if num, err := strconv.Atoi(parts[len(parts)-2]); err == nil { + curBuild.BuildNumber = num + } + } + } + } + //
...
+ if string(tn) == "div" { + if hasCSSClass(a, "build-details") && buildRowCellDepth > -1 { + if t, found := a["time"]; found { + if msec, err := strconv.ParseInt(t, 10, 0); err == nil { + curBuild.BuildTimestamp = msec / 1000 + } + } + } + } + } + case html.EndTagToken: + tn, _ := z.TagName() + if string(tn) == "td" && depth == buildRowCellDepth { + buildRowCellDepth = -1 + curBuild = nil + } + depth-- + } + } +} + +func attr(z *html.Tokenizer) map[string]string { + a := make(map[string]string) + for { + k, v, more := z.TagAttr() + if k != nil && v != nil { + a[string(k)] = string(v) + } + if !more { + break + } + } + return a +} + +func hasCSSClass(a map[string]string, className string) bool { + if classes, found := a["class"]; found { + for _, class := range strings.Fields(classes) { + if class == className { + return true + } + } + } + return false +} diff --git a/pkg/gojenkins/constants.go b/pkg/gojenkins/constants.go new file mode 100644 index 000000000..1876fb958 --- /dev/null +++ b/pkg/gojenkins/constants.go @@ -0,0 +1,20 @@ +package gojenkins + +const ( + STATUS_FAIL = "FAIL" + STATUS_ERROR = "ERROR" + STATUS_ABORTED = "ABORTED" + STATUS_REGRESSION = "REGRESSION" + STATUS_SUCCESS = "SUCCESS" + STATUS_FIXED = "FIXED" + STATUS_PASSED = "PASSED" + RESULT_STATUS_FAILURE = "FAILURE" + RESULT_STATUS_FAILED = "FAILED" + RESULT_STATUS_SKIPPED = "SKIPPED" + STR_RE_SPLIT_VIEW = "(.*)/view/([^/]*)/?" +) + +const ( + GLOBAL_ROLE = "globalRoles" + PROJECT_ROLE = "projectRoles" +) diff --git a/pkg/gojenkins/credential.go b/pkg/gojenkins/credential.go new file mode 100644 index 000000000..e52968ac8 --- /dev/null +++ b/pkg/gojenkins/credential.go @@ -0,0 +1,225 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gojenkins + +const SSHCrenditalStaplerClass = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey" +const DirectSSHCrenditalStaplerClass = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource" +const UsernamePassswordCredentialStaplerClass = "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl" +const SecretTextCredentialStaplerClass = "org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl" +const KubeconfigCredentialStaplerClass = "com.microsoft.jenkins.kubernetes.credentials.KubeconfigCredentials" +const DirectKubeconfigCredentialStaperClass = "com.microsoft.jenkins.kubernetes.credentials.KubeconfigCredentials$DirectEntryKubeconfigSource" +const GLOBALScope = "GLOBAL" + +type CreateSshCredentialRequest struct { + Credentials SshCredential `json:"credentials"` +} + +type CreateUsernamePasswordCredentialRequest struct { + Credentials UsernamePasswordCredential `json:"credentials"` +} + +type CreateSecretTextCredentialRequest struct { + Credentials SecretTextCredential `json:"credentials"` +} + +type CreateKubeconfigCredentialRequest struct { + Credentials KubeconfigCredential `json:"credentials"` +} + +type UsernamePasswordCredential struct { + Scope string `json:"scope"` + Id string `json:"id"` + Username string `json:"username"` + Password string `json:"password"` + Description string `json:"description"` + StaplerClass string `json:"stapler-class"` +} + +type SshCredential struct { + Scope string `json:"scope"` + Id string `json:"id"` + Username string `json:"username"` + Passphrase string `json:"passphrase"` + KeySource PrivateKeySource `json:"privateKeySource"` + Description string `json:"description"` + StaplerClass string `json:"stapler-class"` +} + +type SecretTextCredential struct { + Scope string `json:"scope"` + Id string `json:"id"` + Secret string `json:"secret"` + Description string `json:"description"` + StaplerClass string `json:"stapler-class"` +} + +type KubeconfigCredential struct { + Scope string `json:"scope"` + Id string `json:"id"` + Description string `json:"description"` + KubeconfigSource KubeconfigSource `json:"kubeconfigSource"` + StaplerClass string `json:"stapler-class"` +} + +type PrivateKeySource struct { + StaplerClass string `json:"stapler-class"` + PrivateKey string `json:"privateKey"` +} + +type KubeconfigSource struct { + StaplerClass string `json:"stapler-class"` + Content string `json:"content"` +} + +type CredentialResponse struct { + Id string `json:"id"` + TypeName string `json:"typeName"` + DisplayName string `json:"displayName"` + Fingerprint *struct { + FileName string `json:"fileName,omitempty"` + Hash string `json:"hash,omitempty"` + Usage []*struct { + Name string `json:"name,omitempty"` + Ranges struct { + Ranges []*struct { + Start int `json:"start"` + End int `json:"end"` + } `json:"ranges"` + } `json:"ranges"` + } `json:"usage,omitempty"` + } `json:"fingerprint,omitempty"` + Description string `json:"description"` + Domain string `json:"domain"` +} + +func NewCreateSshCredentialRequest(id, username, passphrase, privateKey, description string) *CreateSshCredentialRequest { + + keySource := PrivateKeySource{ + StaplerClass: DirectSSHCrenditalStaplerClass, + PrivateKey: privateKey, + } + + sshCredential := SshCredential{ + Scope: GLOBALScope, + Id: id, + Username: username, + Passphrase: passphrase, + KeySource: keySource, + Description: description, + StaplerClass: SSHCrenditalStaplerClass, + } + return &CreateSshCredentialRequest{ + Credentials: sshCredential, + } + +} + +func NewCreateUsernamePasswordRequest(id, username, password, description string) *CreateUsernamePasswordCredentialRequest { + credential := UsernamePasswordCredential{ + Scope: GLOBALScope, + Id: id, + Username: username, + Password: password, + Description: description, + StaplerClass: UsernamePassswordCredentialStaplerClass, + } + return &CreateUsernamePasswordCredentialRequest{ + Credentials: credential, + } +} + +func NewCreateSecretTextCredentialRequest(id, secret, description string) *CreateSecretTextCredentialRequest { + credential := SecretTextCredential{ + Scope: GLOBALScope, + Id: id, + Secret: secret, + Description: description, + StaplerClass: SecretTextCredentialStaplerClass, + } + return &CreateSecretTextCredentialRequest{ + Credentials: credential, + } +} + +func NewCreateKubeconfigCredentialRequest(id, content, description string) *CreateKubeconfigCredentialRequest { + + credentialSource := KubeconfigSource{ + StaplerClass: DirectKubeconfigCredentialStaperClass, + Content: content, + } + + credential := KubeconfigCredential{ + Scope: GLOBALScope, + Id: id, + Description: description, + KubeconfigSource: credentialSource, + StaplerClass: KubeconfigCredentialStaplerClass, + } + return &CreateKubeconfigCredentialRequest{ + credential, + } +} + +func NewSshCredential(id, username, passphrase, privateKey, description string) *SshCredential { + keySource := PrivateKeySource{ + StaplerClass: DirectSSHCrenditalStaplerClass, + PrivateKey: privateKey, + } + + return &SshCredential{ + Scope: GLOBALScope, + Id: id, + Username: username, + Passphrase: passphrase, + KeySource: keySource, + Description: description, + StaplerClass: SSHCrenditalStaplerClass, + } +} + +func NewUsernamePasswordCredential(id, username, password, description string) *UsernamePasswordCredential { + return &UsernamePasswordCredential{ + Scope: GLOBALScope, + Id: id, + Username: username, + Password: password, + Description: description, + StaplerClass: UsernamePassswordCredentialStaplerClass, + } +} + +func NewSecretTextCredential(id, secret, description string) *SecretTextCredential { + return &SecretTextCredential{ + Scope: GLOBALScope, + Id: id, + Secret: secret, + Description: description, + StaplerClass: SecretTextCredentialStaplerClass, + } +} + +func NewKubeconfigCredential(id, content, description string) *KubeconfigCredential { + credentialSource := KubeconfigSource{ + StaplerClass: DirectKubeconfigCredentialStaperClass, + Content: content, + } + + return &KubeconfigCredential{ + Scope: GLOBALScope, + Id: id, + Description: description, + KubeconfigSource: credentialSource, + StaplerClass: KubeconfigCredentialStaplerClass, + } +} diff --git a/pkg/gojenkins/executor.go b/pkg/gojenkins/executor.go new file mode 100644 index 000000000..e5ba6f6c2 --- /dev/null +++ b/pkg/gojenkins/executor.go @@ -0,0 +1,44 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +type Executor struct { + Raw *ExecutorResponse + Jenkins *Jenkins +} +type ViewData struct { + Name string `json:"name"` + URL string `json:"url"` +} +type ExecutorResponse struct { + AssignedLabels []struct{} `json:"assignedLabels"` + Description interface{} `json:"description"` + Jobs []InnerJob `json:"jobs"` + Mode string `json:"mode"` + NodeDescription string `json:"nodeDescription"` + NodeName string `json:"nodeName"` + NumExecutors int64 `json:"numExecutors"` + OverallLoad struct{} `json:"overallLoad"` + PrimaryView struct { + Name string `json:"name"` + URL string `json:"url"` + } `json:"primaryView"` + QuietingDown bool `json:"quietingDown"` + SlaveAgentPort int64 `json:"slaveAgentPort"` + UnlabeledLoad struct{} `json:"unlabeledLoad"` + UseCrumbs bool `json:"useCrumbs"` + UseSecurity bool `json:"useSecurity"` + Views []ViewData `json:"views"` +} diff --git a/pkg/gojenkins/fingerprint.go b/pkg/gojenkins/fingerprint.go new file mode 100644 index 000000000..3afaa8b6a --- /dev/null +++ b/pkg/gojenkins/fingerprint.go @@ -0,0 +1,95 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "errors" + "fmt" +) + +type FingerPrint struct { + Jenkins *Jenkins + Base string + Id string + Raw *FingerPrintResponse +} + +type FingerPrintResponse struct { + FileName string `json:"fileName"` + Hash string `json:"hash"` + Original struct { + Name string + Number int64 + } `json:"original"` + Timestamp int64 `json:"timestamp"` + Usage []struct { + Name string `json:"name"` + Ranges struct { + Ranges []struct { + End int64 `json:"end"` + Start int64 `json:"start"` + } `json:"ranges"` + } `json:"ranges"` + } `json:"usage"` +} + +func (f FingerPrint) Valid() (bool, error) { + status, err := f.Poll() + + if err != nil { + return false, err + } + + if status != 200 || f.Raw.Hash != f.Id { + return false, fmt.Errorf("Jenkins says %s is Invalid or the Status is unknown", f.Id) + } + return true, nil +} + +func (f FingerPrint) ValidateForBuild(filename string, build *Build) (bool, error) { + valid, err := f.Valid() + if err != nil { + return false, err + } + + if valid { + return true, nil + } + + if f.Raw.FileName != filename { + return false, errors.New("Filename does not Match") + } + if build != nil && f.Raw.Original.Name == build.Job.GetName() && + f.Raw.Original.Number == build.GetBuildNumber() { + return true, nil + } + return false, nil +} + +func (f FingerPrint) GetInfo() (*FingerPrintResponse, error) { + _, err := f.Poll() + if err != nil { + return nil, err + } + return f.Raw, nil +} + +func (f FingerPrint) Poll() (int, error) { + response, err := f.Jenkins.Requester.GetJSON(f.Base+f.Id, f.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/gojenkins/folder.go b/pkg/gojenkins/folder.go new file mode 100644 index 000000000..053a8ef33 --- /dev/null +++ b/pkg/gojenkins/folder.go @@ -0,0 +1,77 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "errors" + "strconv" + "strings" +) + +type Folder struct { + Raw *FolderResponse + Jenkins *Jenkins + Base string +} + +type FolderResponse struct { + Actions []GeneralObj + Description string `json:"description"` + DisplayName string `json:"displayName"` + Name string `json:"name"` + URL string `json:"url"` + Jobs []InnerJob `json:"jobs"` + PrimaryView *ViewData `json:"primaryView"` + Views []ViewData `json:"views"` +} + +func (f *Folder) parentBase() string { + return f.Base[:strings.LastIndex(f.Base, "/job")] +} + +func (f *Folder) GetName() string { + return f.Raw.Name +} + +func (f *Folder) Create(name, description string) (*Folder, error) { + mode := "com.cloudbees.hudson.plugins.folder.Folder" + data := map[string]string{ + "name": name, + "mode": mode, + "Submit": "OK", + "json": makeJson(map[string]string{ + "name": name, + "mode": mode, + "description": description, + }), + } + r, err := f.Jenkins.Requester.Post(f.parentBase()+"/createItem", nil, f.Raw, data) + if err != nil { + return nil, err + } + if r.StatusCode == 200 { + f.Poll() + return f, nil + } + return nil, errors.New(strconv.Itoa(r.StatusCode)) +} + +func (f *Folder) Poll() (int, error) { + response, err := f.Jenkins.Requester.GetJSON(f.Base, f.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/gojenkins/jenkins.go b/pkg/gojenkins/jenkins.go new file mode 100644 index 000000000..e40a7fb36 --- /dev/null +++ b/pkg/gojenkins/jenkins.go @@ -0,0 +1,1067 @@ +// Copyright 2015 Vadim Kravcenko +// +// 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. + +// Gojenkins is a Jenkins Client in Go, that exposes the jenkins REST api in a more developer friendly way. +package gojenkins + +import ( + "encoding/json" + "errors" + "fmt" + "log" + "net/http" + "os" + "reflect" + "strconv" + "strings" +) + +// Basic Authentication +type BasicAuth struct { + Username string + Password string +} + +type Jenkins struct { + Server string + Version string + Raw *ExecutorResponse + Requester *Requester +} + +// Loggers +var ( + Info *log.Logger + Warning *log.Logger + Error *log.Logger +) + +// Init Method. Should be called after creating a Jenkins Instance. +// e.g jenkins := CreateJenkins("url").Init() +// HTTP Client is set here, Connection to jenkins is tested here. +func (j *Jenkins) Init() (*Jenkins, error) { + j.initLoggers() + + // Check Connection + j.Raw = new(ExecutorResponse) + rsp, err := j.Requester.GetJSON("/", j.Raw, nil) + if err != nil { + return nil, err + } + + j.Version = rsp.Header.Get("X-Jenkins") + if j.Raw == nil { + return nil, errors.New("Connection Failed, Please verify that the host and credentials are correct.") + } + + return j, nil +} + +func (j *Jenkins) initLoggers() { + Info = log.New(os.Stdout, + "INFO: ", + log.Ldate|log.Ltime|log.Lshortfile) + + Warning = log.New(os.Stdout, + "WARNING: ", + log.Ldate|log.Ltime|log.Lshortfile) + + Error = log.New(os.Stderr, + "ERROR: ", + log.Ldate|log.Ltime|log.Lshortfile) +} + +// Get Basic Information About Jenkins +func (j *Jenkins) Info() (*ExecutorResponse, error) { + _, err := j.Requester.Get("/", j.Raw, nil) + + if err != nil { + return nil, err + } + return j.Raw, nil +} + +// Create a new Node +// Can be JNLPLauncher or SSHLauncher +// Example : jenkins.CreateNode("nodeName", 1, "Description", "/var/lib/jenkins", "jdk8 docker", map[string]string{"method": "JNLPLauncher"}) +// By Default JNLPLauncher is created +// Multiple labels should be separated by blanks +func (j *Jenkins) CreateNode(name string, numExecutors int, description string, remoteFS string, label string, options ...interface{}) (*Node, error) { + params := map[string]string{"method": "JNLPLauncher"} + + if len(options) > 0 { + params, _ = options[0].(map[string]string) + } + + if _, ok := params["method"]; !ok { + params["method"] = "JNLPLauncher" + } + + method := params["method"] + var launcher map[string]string + switch method { + case "": + fallthrough + case "JNLPLauncher": + launcher = map[string]string{"stapler-class": "hudson.slaves.JNLPLauncher"} + case "SSHLauncher": + launcher = map[string]string{ + "stapler-class": "hudson.plugins.sshslaves.SSHLauncher", + "$class": "hudson.plugins.sshslaves.SSHLauncher", + "host": params["host"], + "port": params["port"], + "credentialsId": params["credentialsId"], + "jvmOptions": params["jvmOptions"], + "javaPath": params["javaPath"], + "prefixStartSlaveCmd": params["prefixStartSlaveCmd"], + "suffixStartSlaveCmd": params["suffixStartSlaveCmd"], + "maxNumRetries": params["maxNumRetries"], + "retryWaitTime": params["retryWaitTime"], + "lanuchTimeoutSeconds": params["lanuchTimeoutSeconds"], + "type": "hudson.slaves.DumbSlave", + "stapler-class-bag": "true"} + default: + return nil, errors.New("launcher method not supported") + } + + node := &Node{Jenkins: j, Raw: new(NodeResponse), Base: "/computer/" + name} + NODE_TYPE := "hudson.slaves.DumbSlave$DescriptorImpl" + MODE := "NORMAL" + qr := map[string]string{ + "name": name, + "type": NODE_TYPE, + "json": makeJson(map[string]interface{}{ + "name": name, + "nodeDescription": description, + "remoteFS": remoteFS, + "numExecutors": numExecutors, + "mode": MODE, + "type": NODE_TYPE, + "labelString": label, + "retentionsStrategy": map[string]string{"stapler-class": "hudson.slaves.RetentionStrategy$Always"}, + "nodeProperties": map[string]string{"stapler-class-bag": "true"}, + "launcher": launcher, + }), + } + + resp, err := j.Requester.Post("/computer/doCreateItem", nil, nil, qr) + + if err != nil { + return nil, err + } + + if resp.StatusCode < 400 { + _, err := node.Poll() + if err != nil { + return nil, err + } + return node, nil + } + return nil, errors.New(strconv.Itoa(resp.StatusCode)) +} + +// Delete a Jenkins slave node +func (j *Jenkins) DeleteNode(name string) (bool, error) { + node := Node{Jenkins: j, Raw: new(NodeResponse), Base: "/computer/" + name} + return node.Delete() +} + +// Create a new folder +// This folder can be nested in other parent folders +// Example: jenkins.CreateFolder("newFolder", "grandparentFolder", "parentFolder") +func (j *Jenkins) CreateFolder(name, description string, parents ...string) (*Folder, error) { + folderObj := &Folder{Jenkins: j, Raw: new(FolderResponse), Base: "/job/" + strings.Join(append(parents, name), "/job/")} + folder, err := folderObj.Create(name, description) + if err != nil { + return nil, err + } + return folder, nil +} + +// Create a new job in the folder +// Example: jenkins.CreateJobInFolder("", "newJobName", "myFolder", "parentFolder") +func (j *Jenkins) CreateJobInFolder(config string, jobName string, parentIDs ...string) (*Job, error) { + jobObj := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + strings.Join(append(parentIDs, jobName), "/job/")} + qr := map[string]string{ + "name": jobName, + } + job, err := jobObj.Create(config, qr) + if err != nil { + return nil, err + } + return job, nil +} + +// Create a new job from config File +// Method takes XML string as first parameter, and if the name is not specified in the config file +// takes name as string as second parameter +// e.g jenkins.CreateJob("","newJobName") +func (j *Jenkins) CreateJob(config string, options ...interface{}) (*Job, error) { + qr := make(map[string]string) + if len(options) > 0 { + qr["name"] = options[0].(string) + } else { + return nil, errors.New("Error Creating Job, job name is missing") + } + jobObj := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + qr["name"]} + job, err := jobObj.Create(config, qr) + if err != nil { + return nil, err + } + return job, nil +} + +// Rename a job. +// First parameter job old name, Second parameter job new name. +func (j *Jenkins) RenameJob(job string, name string) *Job { + jobObj := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + job} + jobObj.Rename(name) + return &jobObj +} + +// Create a copy of a job. +// First parameter Name of the job to copy from, Second parameter new job name. +func (j *Jenkins) CopyJob(copyFrom string, newName string) (*Job, error) { + job := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + copyFrom} + _, err := job.Poll() + if err != nil { + return nil, err + } + return job.Copy(newName) +} + +// Delete a job. +func (j *Jenkins) DeleteJob(name string, parentIDs ...string) (bool, error) { + job := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + strings.Join(append(parentIDs, name), "/job/")} + return job.Delete() +} + +// Invoke a job. +// First parameter job name, second parameter is optional Build parameters. +func (j *Jenkins) BuildJob(name string, options ...interface{}) (int64, error) { + job := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + name} + var params map[string]string + if len(options) > 0 { + params, _ = options[0].(map[string]string) + } + return job.InvokeSimple(params) +} + +func (j *Jenkins) GetNode(name string) (*Node, error) { + node := Node{Jenkins: j, Raw: new(NodeResponse), Base: "/computer/" + name} + status, err := node.Poll() + if err != nil { + return nil, err + } + if status == 200 { + return &node, nil + } + return nil, errors.New("No node found") +} + +func (j *Jenkins) GetLabel(name string) (*Label, error) { + label := Label{Jenkins: j, Raw: new(LabelResponse), Base: "/label/" + name} + status, err := label.Poll() + if err != nil { + return nil, err + } + if status == 200 { + return &label, nil + } + return nil, errors.New("No label found") +} + +func (j *Jenkins) GetBuild(jobName string, number int64) (*Build, error) { + job, err := j.GetJob(jobName) + if err != nil { + return nil, err + } + build, err := job.GetBuild(number) + + if err != nil { + return nil, err + } + return build, nil +} + +func (j *Jenkins) GetJob(id string, parentIDs ...string) (*Job, error) { + job := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + strings.Join(append(parentIDs, id), "/job/")} + status, err := job.Poll() + if err != nil { + return nil, err + } + if status == 200 { + return &job, nil + } + return nil, errors.New(strconv.Itoa(status)) +} + +func (j *Jenkins) GetSubJob(parentId string, childId string) (*Job, error) { + job := Job{Jenkins: j, Raw: new(JobResponse), Base: "/job/" + parentId + "/job/" + childId} + status, err := job.Poll() + if err != nil { + return nil, fmt.Errorf("trouble polling job: %v", err) + } + if status == 200 { + return &job, nil + } + return nil, errors.New(strconv.Itoa(status)) +} + +func (j *Jenkins) GetFolder(id string, parents ...string) (*Folder, error) { + folder := Folder{Jenkins: j, Raw: new(FolderResponse), Base: "/job/" + strings.Join(append(parents, id), "/job/")} + status, err := folder.Poll() + if err != nil { + return nil, fmt.Errorf("trouble polling folder: %v", err) + } + if status == 200 { + return &folder, nil + } + return nil, errors.New(strconv.Itoa(status)) +} + +func (j *Jenkins) GetAllNodes() ([]*Node, error) { + computers := new(Computers) + + qr := map[string]string{ + "depth": "1", + } + + _, err := j.Requester.GetJSON("/computer", computers, qr) + if err != nil { + return nil, err + } + + nodes := make([]*Node, len(computers.Computers)) + for i, node := range computers.Computers { + nodes[i] = &Node{Jenkins: j, Raw: node, Base: "/computer/" + node.DisplayName} + } + + return nodes, nil +} + +// Get all builds Numbers and URLS for a specific job. +// There are only build IDs here, +// To get all the other info of the build use jenkins.GetBuild(job,buildNumber) +// or job.GetBuild(buildNumber) +func (j *Jenkins) GetAllBuildIds(job string) ([]JobBuild, error) { + jobObj, err := j.GetJob(job) + if err != nil { + return nil, err + } + return jobObj.GetAllBuildIds() +} + +func (j *Jenkins) GetAllBuildStatus(jobId string) ([]JobBuildStatus, error) { + job, err := j.GetJob(jobId) + if err != nil { + return nil, err + } + return job.GetAllBuildStatus() +} + +// Get Only Array of Job Names, Color, URL +// Does not query each single Job. +func (j *Jenkins) GetAllJobNames() ([]InnerJob, error) { + exec := Executor{Raw: new(ExecutorResponse), Jenkins: j} + _, err := j.Requester.GetJSON("/", exec.Raw, nil) + + if err != nil { + return nil, err + } + + return exec.Raw.Jobs, nil +} + +// Get All Possible Job Objects. +// Each job will be queried. +func (j *Jenkins) GetAllJobs() ([]*Job, error) { + exec := Executor{Raw: new(ExecutorResponse), Jenkins: j} + _, err := j.Requester.GetJSON("/", exec.Raw, nil) + + if err != nil { + return nil, err + } + + jobs := make([]*Job, len(exec.Raw.Jobs)) + for i, job := range exec.Raw.Jobs { + ji, err := j.GetJob(job.Name) + if err != nil { + return nil, err + } + jobs[i] = ji + } + return jobs, nil +} + +// Returns a Queue +func (j *Jenkins) GetQueue() (*Queue, error) { + q := &Queue{Jenkins: j, Raw: new(queueResponse), Base: j.GetQueueUrl()} + _, err := q.Poll() + if err != nil { + return nil, err + } + return q, nil +} + +func (j *Jenkins) GetQueueUrl() string { + return "/queue" +} + +// Get Artifact data by Hash +func (j *Jenkins) GetArtifactData(id string) (*FingerPrintResponse, error) { + fp := FingerPrint{Jenkins: j, Base: "/fingerprint/", Id: id, Raw: new(FingerPrintResponse)} + return fp.GetInfo() +} + +// Returns the list of all plugins installed on the Jenkins server. +// You can supply depth parameter, to limit how much data is returned. +func (j *Jenkins) GetPlugins(depth int) (*Plugins, error) { + p := Plugins{Jenkins: j, Raw: new(PluginResponse), Base: "/pluginManager", Depth: depth} + _, err := p.Poll() + if err != nil { + return nil, err + } + return &p, nil +} + +// Check if the plugin is installed on the server. +// Depth level 1 is used. If you need to go deeper, you can use GetPlugins, and iterate through them. +func (j *Jenkins) HasPlugin(name string) (*Plugin, error) { + p, err := j.GetPlugins(1) + + if err != nil { + return nil, err + } + return p.Contains(name), nil +} + +// Verify FingerPrint +func (j *Jenkins) ValidateFingerPrint(id string) (bool, error) { + fp := FingerPrint{Jenkins: j, Base: "/fingerprint/", Id: id, Raw: new(FingerPrintResponse)} + valid, err := fp.Valid() + if err != nil { + return false, err + } + if valid { + return true, nil + } + return false, nil +} + +func (j *Jenkins) GetView(name string) (*View, error) { + url := "/view/" + name + view := View{Jenkins: j, Raw: new(ViewResponse), Base: url} + _, err := view.Poll() + if err != nil { + return nil, err + } + return &view, nil +} + +func (j *Jenkins) GetAllViews() ([]*View, error) { + _, err := j.Poll() + if err != nil { + return nil, err + } + views := make([]*View, len(j.Raw.Views)) + for i, v := range j.Raw.Views { + views[i], _ = j.GetView(v.Name) + } + return views, nil +} + +// Create View +// First Parameter - name of the View +// Second parameter - Type +// Possible Types: +// gojenkins.LIST_VIEW +// gojenkins.NESTED_VIEW +// gojenkins.MY_VIEW +// gojenkins.DASHBOARD_VIEW +// gojenkins.PIPELINE_VIEW +// Example: jenkins.CreateView("newView",gojenkins.LIST_VIEW) +func (j *Jenkins) CreateView(name string, viewType string) (*View, error) { + view := &View{Jenkins: j, Raw: new(ViewResponse), Base: "/view/" + name} + endpoint := "/createView" + data := map[string]string{ + "name": name, + "mode": viewType, + "Submit": "OK", + "json": makeJson(map[string]string{ + "name": name, + "mode": viewType, + }), + } + r, err := j.Requester.Post(endpoint, nil, view.Raw, data) + + if err != nil { + return nil, err + } + + if r.StatusCode == 200 { + return j.GetView(name) + } + return nil, errors.New(strconv.Itoa(r.StatusCode)) +} + +func (j *Jenkins) Poll() (int, error) { + resp, err := j.Requester.GetJSON("/", j.Raw, nil) + if err != nil { + return 0, err + } + return resp.StatusCode, nil +} + +// Create a ssh credentials +// return credentials id +func (j *Jenkins) CreateSshCredential(id, username, passphrase, privateKey, description string) (*string, error) { + requestStruct := NewCreateSshCredentialRequest(id, username, passphrase, privateKey, description) + param := map[string]string{"json": makeJson(requestStruct)} + responseString := "" + response, err := j.Requester.Post("/credentials/store/system/domain/_/createCredentials", + nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &requestStruct.Credentials.Id, nil +} + +func (j *Jenkins) CreateUsernamePasswordCredential(id, username, password, description string) (*string, error) { + requestStruct := NewCreateUsernamePasswordRequest(id, username, password, description) + param := map[string]string{"json": makeJson(requestStruct)} + responseString := "" + response, err := j.Requester.Post("/credentials/store/system/domain/_/createCredentials", + nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &requestStruct.Credentials.Id, nil +} + +func (j *Jenkins) CreateSshCredentialInFolder(domain, id, username, passphrase, privateKey, description string, folders ...string) (*string, error) { + requestStruct := NewCreateSshCredentialRequest(id, username, passphrase, privateKey, description) + param := map[string]string{"json": makeJson(requestStruct)} + responseString := "" + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/createCredentials", domain), + nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &requestStruct.Credentials.Id, nil +} + +func (j *Jenkins) CreateUsernamePasswordCredentialInFolder(domain, id, username, password, description string, folders ...string) (*string, error) { + requestStruct := NewCreateUsernamePasswordRequest(id, username, password, description) + param := map[string]string{"json": makeJson(requestStruct)} + responseString := "" + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/createCredentials", domain), + nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &requestStruct.Credentials.Id, nil +} + +func (j *Jenkins) CreateSecretTextCredentialInFolder(domain, id, secret, description string, folders ...string) (*string, error) { + requestStruct := NewCreateSecretTextCredentialRequest(id, secret, description) + param := map[string]string{"json": makeJson(requestStruct)} + responseString := "" + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/createCredentials", domain), + nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &requestStruct.Credentials.Id, nil +} + +func (j *Jenkins) CreateKubeconfigCredentialInFolder(domain, id, content, description string, folders ...string) (*string, error) { + requestStruct := NewCreateKubeconfigCredentialRequest(id, content, description) + param := map[string]string{"json": makeJson(requestStruct)} + responseString := "" + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/createCredentials", domain), + nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &requestStruct.Credentials.Id, nil +} + +func (j *Jenkins) UpdateSshCredentialInFolder(domain, id, username, passphrase, privateKey, description string, folders ...string) (*string, error) { + requestStruct := NewSshCredential(id, username, passphrase, privateKey, description) + param := map[string]string{"json": makeJson(requestStruct)} + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s/updateSubmit", domain, id), + nil, nil, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &id, nil +} + +func (j *Jenkins) UpdateUsernamePasswordCredentialInFolder(domain, id, username, password, description string, folders ...string) (*string, error) { + requestStruct := NewUsernamePasswordCredential(id, username, password, description) + param := map[string]string{"json": makeJson(requestStruct)} + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s/updateSubmit", domain, id), + nil, nil, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &id, nil +} + +func (j *Jenkins) UpdateSecretTextCredentialInFolder(domain, id, secret, description string, folders ...string) (*string, error) { + requestStruct := NewSecretTextCredential(id, secret, description) + param := map[string]string{"json": makeJson(requestStruct)} + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s/updateSubmit", domain, id), + nil, nil, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &id, nil +} + +func (j *Jenkins) UpdateKubeconfigCredentialInFolder(domain, id, content, description string, folders ...string) (*string, error) { + requestStruct := NewKubeconfigCredential(id, content, description) + param := map[string]string{"json": makeJson(requestStruct)} + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s/updateSubmit", domain, id), + nil, nil, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &id, nil +} + +func (j *Jenkins) GetCredentialInFolder(domain, id string, folders ...string) (*CredentialResponse, error) { + responseStruct := &CredentialResponse{} + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.GetJSON(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s", domain, id), + responseStruct, map[string]string{ + "depth": "2", + }) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + responseStruct.Domain = domain + return responseStruct, nil +} + +func (j *Jenkins) GetCredentialContentInFolder(domain, id string, folders ...string) (string, error) { + responseStruct := "" + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return "", fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.GetHtml(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s/update", domain, id), + &responseStruct, nil) + if err != nil { + return "", err + } + if response.StatusCode != http.StatusOK { + return "", errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStruct, nil +} + +func (j *Jenkins) GetCredentialsInFolder(domain string, folders ...string) ([]*CredentialResponse, error) { + prePath := "" + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + + if domain == "" { + var responseStruct = &struct { + Domains map[string]struct { + Credentials []*CredentialResponse `json:"credentials"` + } `json:"domains"` + }{} + response, err := j.Requester.GetJSON(prePath+ + "/credentials/store/folder/", + responseStruct, map[string]string{ + "depth": "2", + }) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + responseArray := make([]*CredentialResponse, 0) + for domainName, domain := range responseStruct.Domains { + for _, credential := range domain.Credentials { + credential.Domain = domainName + responseArray = append(responseArray, credential) + } + } + return responseArray, nil + } + + var responseStruct = &struct { + Credentials []*CredentialResponse `json:"credentials"` + }{} + response, err := j.Requester.GetJSON(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s", domain), + responseStruct, map[string]string{ + "depth": "2", + }) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + for _, credential := range responseStruct.Credentials { + credential.Domain = domain + } + return responseStruct.Credentials, nil + +} + +func (j *Jenkins) DeleteCredentialInFolder(domain, id string, folders ...string) (*string, error) { + prePath := "" + if domain == "" { + domain = "_" + } + if len(folders) == 0 { + return nil, fmt.Errorf("folder name shoud not be nil") + } + for _, folder := range folders { + prePath = prePath + fmt.Sprintf("/job/%s", folder) + } + response, err := j.Requester.Post(prePath+ + fmt.Sprintf("/credentials/store/folder/domain/%s/credential/%s/doDelete", domain, id), + nil, nil, nil) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return &id, nil +} + +func (j *Jenkins) GetGlobalRole(roleName string) (*GlobalRole, error) { + roleResponse := &GlobalRoleResponse{ + RoleName: roleName, + } + stringResponse := "" + response, err := j.Requester.Get("/role-strategy/strategy/getRole", + &stringResponse, + map[string]string{ + "roleName": roleName, + "type": GLOBAL_ROLE, + }) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + if stringResponse == "{}" { + return nil, nil + } + err = json.Unmarshal([]byte(stringResponse), roleResponse) + if err != nil { + return nil, err + } + return &GlobalRole{ + Jenkins: j, + Raw: *roleResponse, + }, nil +} + +func (j *Jenkins) GetProjectRole(roleName string) (*ProjectRole, error) { + roleResponse := &ProjectRoleResponse{ + RoleName: roleName, + } + stringResponse := "" + response, err := j.Requester.Get("/role-strategy/strategy/getRole", + &stringResponse, + map[string]string{ + "roleName": roleName, + "type": PROJECT_ROLE, + }) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + if stringResponse == "{}" { + return nil, nil + } + err = json.Unmarshal([]byte(stringResponse), roleResponse) + if err != nil { + return nil, err + } + return &ProjectRole{ + Jenkins: j, + Raw: *roleResponse, + }, nil +} + +func (j *Jenkins) AddGlobalRole(roleName string, ids GlobalPermissionIds, overwrite bool) (*GlobalRole, error) { + responseRole := &GlobalRole{ + Jenkins: j, + Raw: GlobalRoleResponse{ + RoleName: roleName, + PermissionIds: ids, + }} + var idArray []string + values := reflect.ValueOf(ids) + for i := 0; i < values.NumField(); i++ { + field := values.Field(i) + if field.Bool() { + idArray = append(idArray, values.Type().Field(i).Tag.Get("json")) + } + } + param := map[string]string{ + "roleName": roleName, + "type": GLOBAL_ROLE, + "permissionIds": strings.Join(idArray, ","), + "overwrite": strconv.FormatBool(overwrite), + } + responseString := "" + response, err := j.Requester.Post("/role-strategy/strategy/addRole", nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseRole, nil +} + +func (j *Jenkins) DeleteProjectRoles(roleName ...string) error { + responseString := "" + + response, err := j.Requester.Post("/role-strategy/strategy/removeRoles", nil, &responseString, map[string]string{ + "type": PROJECT_ROLE, + "roleNames": strings.Join(roleName, ","), + }) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + fmt.Println(responseString) + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +func (j *Jenkins) AddProjectRole(roleName string, pattern string, ids ProjectPermissionIds, overwrite bool) (*ProjectRole, error) { + responseRole := &ProjectRole{ + Jenkins: j, + Raw: ProjectRoleResponse{ + RoleName: roleName, + PermissionIds: ids, + Pattern: pattern, + }} + var idArray []string + values := reflect.ValueOf(ids) + for i := 0; i < values.NumField(); i++ { + field := values.Field(i) + if field.Bool() { + idArray = append(idArray, values.Type().Field(i).Tag.Get("json")) + } + } + param := map[string]string{ + "roleName": roleName, + "type": PROJECT_ROLE, + "permissionIds": strings.Join(idArray, ","), + "overwrite": strconv.FormatBool(overwrite), + "pattern": pattern, + } + responseString := "" + response, err := j.Requester.Post("/role-strategy/strategy/addRole", nil, &responseString, param) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseRole, nil +} + +func (j *Jenkins) GetQueueItem(number int64) (*QueueItemResponse, error) { + responseItem := &QueueItemResponse{} + response, err := j.Requester.GetJSON(fmt.Sprintf("/queue/item/%s", strconv.FormatInt(number, 10)), + responseItem, nil) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseItem, nil +} + +// Creates a new Jenkins Instance +// Optional parameters are: client, username, password +// After creating an instance call init method. +func CreateJenkins(client *http.Client, base string, maxConnection int, auth ...interface{}) *Jenkins { + j := &Jenkins{} + if strings.HasSuffix(base, "/") { + base = base[:len(base)-1] + } + j.Server = base + j.Requester = &Requester{Base: base, SslVerify: true, Client: client, connControl: make(chan struct{}, maxConnection)} + if j.Requester.Client == nil { + j.Requester.Client = http.DefaultClient + } + if len(auth) == 2 { + j.Requester.BasicAuth = &BasicAuth{Username: auth[0].(string), Password: auth[1].(string)} + } + return j +} diff --git a/pkg/gojenkins/job.go b/pkg/gojenkins/job.go new file mode 100644 index 000000000..c075a42e5 --- /dev/null +++ b/pkg/gojenkins/job.go @@ -0,0 +1,529 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/url" + "path" + "strconv" + "strings" +) + +type Job struct { + Raw *JobResponse + Jenkins *Jenkins + Base string +} + +type JobBuild struct { + Number int64 + URL string +} + +type JobBuildStatus struct { + Number int64 + Building bool + Result string +} + +type InnerJob struct { + Name string `json:"name"` + Url string `json:"url"` + Color string `json:"color"` +} + +type ParameterDefinition struct { + DefaultParameterValue struct { + Name string `json:"name"` + Value interface{} `json:"value"` + } `json:"defaultParameterValue"` + Description string `json:"description"` + Name string `json:"name"` + Type string `json:"type"` +} + +type JobResponse struct { + Class string `json:"_class"` + Actions []GeneralObj + Buildable bool `json:"buildable"` + Builds []JobBuild + Color string `json:"color"` + ConcurrentBuild bool `json:"concurrentBuild"` + Description string `json:"description"` + DisplayName string `json:"displayName"` + DisplayNameOrNull interface{} `json:"displayNameOrNull"` + DownstreamProjects []InnerJob `json:"downstreamProjects"` + FirstBuild JobBuild + HealthReport []struct { + Description string `json:"description"` + IconClassName string `json:"iconClassName"` + IconUrl string `json:"iconUrl"` + Score int64 `json:"score"` + } `json:"healthReport"` + InQueue bool `json:"inQueue"` + KeepDependencies bool `json:"keepDependencies"` + LastBuild JobBuild `json:"lastBuild"` + LastCompletedBuild JobBuild `json:"lastCompletedBuild"` + LastFailedBuild JobBuild `json:"lastFailedBuild"` + LastStableBuild JobBuild `json:"lastStableBuild"` + LastSuccessfulBuild JobBuild `json:"lastSuccessfulBuild"` + LastUnstableBuild JobBuild `json:"lastUnstableBuild"` + LastUnsuccessfulBuild JobBuild `json:"lastUnsuccessfulBuild"` + Name string `json:"name"` + SubJobs []InnerJob `json:"subJobs"` + NextBuildNumber int64 `json:"nextBuildNumber"` + Property []struct { + ParameterDefinitions []ParameterDefinition `json:"parameterDefinitions"` + } `json:"property"` + QueueItem interface{} `json:"queueItem"` + Scm struct{} `json:"scm"` + UpstreamProjects []InnerJob `json:"upstreamProjects"` + URL string `json:"url"` + Jobs []InnerJob `json:"jobs"` + PrimaryView *ViewData `json:"primaryView"` + Views []ViewData `json:"views"` +} + +func (j *Job) parentBase() string { + return j.Base[:strings.LastIndex(j.Base, "/job/")] +} + +type History struct { + BuildNumber int + BuildStatus string + BuildTimestamp int64 +} + +func (j *Job) GetName() string { + return j.Raw.Name +} + +func (j *Job) GetDescription() string { + return j.Raw.Description +} + +func (j *Job) GetDetails() *JobResponse { + return j.Raw +} + +func (j *Job) GetBuild(id int64) (*Build, error) { + build := Build{Jenkins: j.Jenkins, Job: j, Raw: new(BuildResponse), Depth: 1, Base: "/job/" + j.GetName() + "/" + strconv.FormatInt(id, 10)} + status, err := build.Poll() + if err != nil { + return nil, err + } + if status == 200 { + return &build, nil + } + return nil, errors.New(strconv.Itoa(status)) +} + +func (j *Job) getBuildByType(buildType string) (*Build, error) { + allowed := map[string]JobBuild{ + "lastStableBuild": j.Raw.LastStableBuild, + "lastSuccessfulBuild": j.Raw.LastSuccessfulBuild, + "lastBuild": j.Raw.LastBuild, + "lastCompletedBuild": j.Raw.LastCompletedBuild, + "firstBuild": j.Raw.FirstBuild, + "lastFailedBuild": j.Raw.LastFailedBuild, + } + number := "" + if val, ok := allowed[buildType]; ok { + number = strconv.FormatInt(val.Number, 10) + } else { + panic("No Such Build") + } + build := Build{ + Jenkins: j.Jenkins, + Depth: 1, + Job: j, + Raw: new(BuildResponse), + Base: j.Base + "/" + number} + status, err := build.Poll() + if err != nil { + return nil, err + } + if status == 200 { + return &build, nil + } + return nil, errors.New(strconv.Itoa(status)) +} + +func (j *Job) GetLastSuccessfulBuild() (*Build, error) { + return j.getBuildByType("lastSuccessfulBuild") +} + +func (j *Job) GetFirstBuild() (*Build, error) { + return j.getBuildByType("firstBuild") +} + +func (j *Job) GetLastBuild() (*Build, error) { + return j.getBuildByType("lastBuild") +} + +func (j *Job) GetLastStableBuild() (*Build, error) { + return j.getBuildByType("lastStableBuild") +} + +func (j *Job) GetLastFailedBuild() (*Build, error) { + return j.getBuildByType("lastFailedBuild") +} + +func (j *Job) GetLastCompletedBuild() (*Build, error) { + return j.getBuildByType("lastCompletedBuild") +} + +// Returns All Builds with Number and URL +func (j *Job) GetAllBuildIds() ([]JobBuild, error) { + var buildsResp struct { + Builds []JobBuild `json:"allBuilds"` + } + _, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,url]"}) + if err != nil { + return nil, err + } + return buildsResp.Builds, nil +} + +func (j *Job) GetAllBuildStatus() ([]JobBuildStatus, error) { + var buildsResp struct { + Builds []JobBuildStatus `json:"allBuilds"` + } + _, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,building,result]"}) + if err != nil { + return nil, err + } + return buildsResp.Builds, nil +} + +func (j *Job) GetSubJobsMetadata() []InnerJob { + return j.Raw.SubJobs +} + +func (j *Job) GetUpstreamJobsMetadata() []InnerJob { + return j.Raw.UpstreamProjects +} + +func (j *Job) GetDownstreamJobsMetadata() []InnerJob { + return j.Raw.DownstreamProjects +} + +func (j *Job) GetSubJobs() ([]*Job, error) { + jobs := make([]*Job, len(j.Raw.SubJobs)) + for i, job := range j.Raw.SubJobs { + ji, err := j.Jenkins.GetSubJob(j.GetName(), job.Name) + if err != nil { + return nil, err + } + jobs[i] = ji + } + return jobs, nil +} + +func (j *Job) GetInnerJobsMetadata() []InnerJob { + return j.Raw.Jobs +} + +func (j *Job) GetUpstreamJobs() ([]*Job, error) { + jobs := make([]*Job, len(j.Raw.UpstreamProjects)) + for i, job := range j.Raw.UpstreamProjects { + ji, err := j.Jenkins.GetJob(job.Name) + if err != nil { + return nil, err + } + jobs[i] = ji + } + return jobs, nil +} + +func (j *Job) GetDownstreamJobs() ([]*Job, error) { + jobs := make([]*Job, len(j.Raw.DownstreamProjects)) + for i, job := range j.Raw.DownstreamProjects { + ji, err := j.Jenkins.GetJob(job.Name) + if err != nil { + return nil, err + } + jobs[i] = ji + } + return jobs, nil +} + +func (j *Job) GetInnerJob(id string) (*Job, error) { + job := Job{Jenkins: j.Jenkins, Raw: new(JobResponse), Base: j.Base + "/job/" + id} + status, err := job.Poll() + if err != nil { + return nil, err + } + if status == 200 { + return &job, nil + } + return nil, errors.New(strconv.Itoa(status)) +} + +func (j *Job) GetInnerJobs() ([]*Job, error) { + jobs := make([]*Job, len(j.Raw.Jobs)) + for i, job := range j.Raw.Jobs { + ji, err := j.GetInnerJob(job.Name) + if err != nil { + return nil, err + } + jobs[i] = ji + } + return jobs, nil +} + +func (j *Job) Enable() (bool, error) { + resp, err := j.Jenkins.Requester.Post(j.Base+"/enable", nil, nil, nil) + if err != nil { + return false, err + } + if resp.StatusCode != 200 { + return false, errors.New(strconv.Itoa(resp.StatusCode)) + } + return true, nil +} + +func (j *Job) Disable() (bool, error) { + resp, err := j.Jenkins.Requester.Post(j.Base+"/disable", nil, nil, nil) + if err != nil { + return false, err + } + if resp.StatusCode != 200 { + return false, errors.New(strconv.Itoa(resp.StatusCode)) + } + return true, nil +} + +func (j *Job) Delete() (bool, error) { + resp, err := j.Jenkins.Requester.Post(j.Base+"/doDelete", nil, nil, nil) + if err != nil { + return false, err + } + if resp.StatusCode != 200 { + return false, errors.New(strconv.Itoa(resp.StatusCode)) + } + return true, nil +} + +func (j *Job) Rename(name string) (bool, error) { + data := url.Values{} + data.Set("newName", name) + _, err := j.Jenkins.Requester.Post(j.Base+"/doRename", bytes.NewBufferString(data.Encode()), nil, nil) + if err != nil { + return false, err + } + j.Base = "/job/" + name + j.Poll() + return true, nil +} + +func (j *Job) Create(config string, qr ...interface{}) (*Job, error) { + var querystring map[string]string + if len(qr) > 0 { + querystring = qr[0].(map[string]string) + } + resp, err := j.Jenkins.Requester.PostXML(j.parentBase()+"/createItem", config, j.Raw, querystring) + if err != nil { + return nil, err + } + if resp.StatusCode == 200 { + j.Poll() + return j, nil + } + return nil, errors.New(strconv.Itoa(resp.StatusCode)) +} + +func (j *Job) Copy(destinationName string) (*Job, error) { + qr := map[string]string{"name": destinationName, "from": j.GetName(), "mode": "copy"} + resp, err := j.Jenkins.Requester.Post(j.parentBase()+"/createItem", nil, nil, qr) + if err != nil { + return nil, err + } + if resp.StatusCode == 200 { + newJob := &Job{Jenkins: j.Jenkins, Raw: new(JobResponse), Base: "/job/" + destinationName} + _, err := newJob.Poll() + if err != nil { + return nil, err + } + return newJob, nil + } + return nil, errors.New(strconv.Itoa(resp.StatusCode)) +} + +func (j *Job) UpdateConfig(config string) error { + + var querystring map[string]string + + resp, err := j.Jenkins.Requester.PostXML(j.Base+"/config.xml", config, nil, querystring) + if err != nil { + return err + } + if resp.StatusCode == 200 { + j.Poll() + return nil + } + return errors.New(strconv.Itoa(resp.StatusCode)) + +} + +func (j *Job) GetConfig() (string, error) { + var data string + _, err := j.Jenkins.Requester.GetXML(j.Base+"/config.xml", &data, nil) + if err != nil { + return "", err + } + return data, nil +} + +func (j *Job) GetParameters() ([]ParameterDefinition, error) { + _, err := j.Poll() + if err != nil { + return nil, err + } + var parameters []ParameterDefinition + for _, property := range j.Raw.Property { + parameters = append(parameters, property.ParameterDefinitions...) + } + return parameters, nil +} + +func (j *Job) IsQueued() (bool, error) { + if _, err := j.Poll(); err != nil { + return false, err + } + return j.Raw.InQueue, nil +} + +func (j *Job) IsRunning() (bool, error) { + if _, err := j.Poll(); err != nil { + return false, err + } + lastBuild, err := j.GetLastBuild() + if err != nil { + return false, err + } + return lastBuild.IsRunning(), nil +} + +func (j *Job) IsEnabled() (bool, error) { + if _, err := j.Poll(); err != nil { + return false, err + } + return j.Raw.Color != "disabled", nil +} + +func (j *Job) HasQueuedBuild() { + panic("Not Implemented yet") +} + +func (j *Job) InvokeSimple(params map[string]string) (int64, error) { + endpoint := "/build" + parameters, err := j.GetParameters() + if err != nil { + return 0, err + } + if len(parameters) > 0 { + endpoint = "/buildWithParameters" + } + data := url.Values{} + for k, v := range params { + data.Set(k, v) + } + resp, err := j.Jenkins.Requester.Post(j.Base+endpoint, bytes.NewBufferString(data.Encode()), nil, nil) + if err != nil { + return 0, err + } + + if resp.StatusCode != 200 && resp.StatusCode != 201 { + return 0, errors.New("Could not invoke job " + j.GetName()) + } + + location := resp.Header.Get("Location") + if location == "" { + return 0, errors.New("Don't have key \"Location\" in response of header") + } + + u, err := url.Parse(location) + if err != nil { + return 0, err + } + + number, err := strconv.ParseInt(path.Base(u.Path), 10, 64) + if err != nil { + return 0, err + } + + return number, nil +} + +func (j *Job) Invoke(files []string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) { + isRunning, err := j.IsRunning() + if err != nil { + return false, err + } + if isRunning && skipIfRunning { + return false, fmt.Errorf("Will not request new build because %s is already running", j.GetName()) + } + + base := "/build" + + // If parameters are specified - url is /builWithParameters + if params != nil { + base = "/buildWithParameters" + } else { + params = make(map[string]string) + } + + // If files are specified - url is /build + if files != nil { + base = "/build" + } + reqParams := map[string]string{} + buildParams := map[string]string{} + if securityToken != "" { + reqParams["token"] = securityToken + } + + buildParams["json"] = string(makeJson(params)) + b, _ := json.Marshal(buildParams) + resp, err := j.Jenkins.Requester.PostFiles(j.Base+base, bytes.NewBuffer(b), nil, reqParams, files) + if err != nil { + return false, err + } + if resp.StatusCode == 200 || resp.StatusCode == 201 { + return true, nil + } + return false, errors.New(strconv.Itoa(resp.StatusCode)) +} + +func (j *Job) Poll() (int, error) { + response, err := j.Jenkins.Requester.GetJSON(j.Base, j.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} + +func (j *Job) History() ([]*History, error) { + resp, err := j.Jenkins.Requester.Get(j.Base+"/buildHistory/ajax", nil, nil) + if err != nil { + return nil, err + } + return parseBuildHistory(resp.Body), nil +} diff --git a/pkg/gojenkins/label.go b/pkg/gojenkins/label.go new file mode 100644 index 000000000..a757b630f --- /dev/null +++ b/pkg/gojenkins/label.go @@ -0,0 +1,62 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +type Label struct { + Raw *LabelResponse + Jenkins *Jenkins + Base string +} + +type MODE string + +const ( + NORMAL MODE = "NORMAL" + EXCLUSIVE = "EXCLUSIVE" +) + +type LabelNode struct { + NodeName string `json:"nodeName"` + NodeDescription string `json:"nodeDescription"` + NumExecutors int64 `json:"numExecutors"` + Mode string `json:"mode"` + Class string `json:"_class"` +} + +type LabelResponse struct { + Name string `json:"name"` + Description string `json:"description"` + Nodes []LabelNode `json:"nodes"` + Offline bool `json:"offline"` + IdleExecutors int64 `json:"idleExecutors"` + BusyExecutors int64 `json:"busyExecutors"` + TotalExecutors int64 `json:"totalExecutors"` +} + +func (l *Label) GetName() string { + return l.Raw.Name +} + +func (l *Label) GetNodes() []LabelNode { + return l.Raw.Nodes +} + +func (l *Label) Poll() (int, error) { + response, err := l.Jenkins.Requester.GetJSON(l.Base, l.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/gojenkins/node.go b/pkg/gojenkins/node.go new file mode 100644 index 000000000..14a6face8 --- /dev/null +++ b/pkg/gojenkins/node.go @@ -0,0 +1,230 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import "errors" + +// Nodes + +type Computers struct { + BusyExecutors int `json:"busyExecutors"` + Computers []*NodeResponse `json:"computer"` + DisplayName string `json:"displayName"` + TotalExecutors int `json:"totalExecutors"` +} + +type Node struct { + Raw *NodeResponse + Jenkins *Jenkins + Base string +} + +type NodeResponse struct { + Actions []interface{} `json:"actions"` + DisplayName string `json:"displayName"` + Executors []struct { + CurrentExecutable struct { + Number int `json:"number"` + URL string `json:"url"` + SubBuilds []struct { + Abort bool `json:"abort"` + Build interface{} `json:"build"` + BuildNumber int `json:"buildNumber"` + Duration string `json:"duration"` + Icon string `json:"icon"` + JobName string `json:"jobName"` + ParentBuildNumber int `json:"parentBuildNumber"` + ParentJobName string `json:"parentJobName"` + PhaseName string `json:"phaseName"` + Result string `json:"result"` + Retry bool `json:"retry"` + URL string `json:"url"` + } `json:"subBuilds"` + } `json:"currentExecutable"` + } `json:"executors"` + Icon string `json:"icon"` + IconClassName string `json:"iconClassName"` + Idle bool `json:"idle"` + JnlpAgent bool `json:"jnlpAgent"` + LaunchSupported bool `json:"launchSupported"` + LoadStatistics struct{} `json:"loadStatistics"` + ManualLaunchAllowed bool `json:"manualLaunchAllowed"` + MonitorData struct { + Hudson_NodeMonitors_ArchitectureMonitor interface{} `json:"hudson.node_monitors.ArchitectureMonitor"` + Hudson_NodeMonitors_ClockMonitor interface{} `json:"hudson.node_monitors.ClockMonitor"` + Hudson_NodeMonitors_DiskSpaceMonitor interface{} `json:"hudson.node_monitors.DiskSpaceMonitor"` + Hudson_NodeMonitors_ResponseTimeMonitor struct { + Average int64 `json:"average"` + } `json:"hudson.node_monitors.ResponseTimeMonitor"` + Hudson_NodeMonitors_SwapSpaceMonitor interface{} `json:"hudson.node_monitors.SwapSpaceMonitor"` + Hudson_NodeMonitors_TemporarySpaceMonitor interface{} `json:"hudson.node_monitors.TemporarySpaceMonitor"` + } `json:"monitorData"` + NumExecutors int64 `json:"numExecutors"` + Offline bool `json:"offline"` + OfflineCause struct{} `json:"offlineCause"` + OfflineCauseReason string `json:"offlineCauseReason"` + OneOffExecutors []interface{} `json:"oneOffExecutors"` + TemporarilyOffline bool `json:"temporarilyOffline"` +} + +func (n *Node) Info() (*NodeResponse, error) { + _, err := n.Poll() + if err != nil { + return nil, err + } + return n.Raw, nil +} + +func (n *Node) GetName() string { + return n.Raw.DisplayName +} + +func (n *Node) Delete() (bool, error) { + resp, err := n.Jenkins.Requester.Post(n.Base+"/doDelete", nil, nil, nil) + if err != nil { + return false, err + } + return resp.StatusCode == 200, nil +} + +func (n *Node) IsOnline() (bool, error) { + _, err := n.Poll() + if err != nil { + return false, err + } + return !n.Raw.Offline, nil +} + +func (n *Node) IsTemporarilyOffline() (bool, error) { + _, err := n.Poll() + if err != nil { + return false, err + } + return n.Raw.TemporarilyOffline, nil +} + +func (n *Node) IsIdle() (bool, error) { + _, err := n.Poll() + if err != nil { + return false, err + } + return n.Raw.Idle, nil +} + +func (n *Node) IsJnlpAgent() (bool, error) { + _, err := n.Poll() + if err != nil { + return false, err + } + return n.Raw.JnlpAgent, nil +} + +func (n *Node) SetOnline() (bool, error) { + _, err := n.Poll() + + if err != nil { + return false, err + } + + if n.Raw.Offline && !n.Raw.TemporarilyOffline { + return false, errors.New("Node is Permanently offline, can't bring it up") + } + + if n.Raw.Offline && n.Raw.TemporarilyOffline { + return n.ToggleTemporarilyOffline() + } + + return true, nil +} + +func (n *Node) SetOffline(options ...interface{}) (bool, error) { + if !n.Raw.Offline { + return n.ToggleTemporarilyOffline(options...) + } + return false, errors.New("Node already Offline") +} + +func (n *Node) ToggleTemporarilyOffline(options ...interface{}) (bool, error) { + state_before, err := n.IsTemporarilyOffline() + if err != nil { + return false, err + } + qr := map[string]string{"offlineMessage": "requested from gojenkins"} + if len(options) > 0 { + qr["offlineMessage"] = options[0].(string) + } + _, err = n.Jenkins.Requester.Post(n.Base+"/toggleOffline", nil, nil, qr) + if err != nil { + return false, err + } + new_state, err := n.IsTemporarilyOffline() + if err != nil { + return false, err + } + if state_before == new_state { + return false, errors.New("Node state not changed") + } + return true, nil +} + +func (n *Node) Poll() (int, error) { + response, err := n.Jenkins.Requester.GetJSON(n.Base, n.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} + +func (n *Node) LaunchNodeBySSH() (int, error) { + qr := map[string]string{ + "json": "", + "Submit": "Launch slave agent", + } + response, err := n.Jenkins.Requester.Post(n.Base+"/launchSlaveAgent", nil, nil, qr) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} + +func (n *Node) Disconnect() (int, error) { + qr := map[string]string{ + "offlineMessage": "", + "json": makeJson(map[string]string{"offlineMessage": ""}), + "Submit": "Yes", + } + response, err := n.Jenkins.Requester.Post(n.Base+"/doDisconnect", nil, nil, qr) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} + +func (n *Node) GetLogText() (string, error) { + var log string + + _, err := n.Jenkins.Requester.Post(n.Base+"/log", nil, nil, nil) + if err != nil { + return "", err + } + + qr := map[string]string{"start": "0"} + _, err = n.Jenkins.Requester.GetJSON(n.Base+"/logText/progressiveHtml/", &log, qr) + if err != nil { + return "", nil + } + + return log, nil +} diff --git a/pkg/gojenkins/pipeline_model_converter.go b/pkg/gojenkins/pipeline_model_converter.go new file mode 100644 index 000000000..12b9003a1 --- /dev/null +++ b/pkg/gojenkins/pipeline_model_converter.go @@ -0,0 +1,162 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gojenkins + +import ( + "errors" + "net/http" + "strconv" +) + +type ValidateJenkinsfileResponse struct { + Status string `json:"status"` + Data struct { + Result string `json:"result"` + Errors []map[string]interface{} `json:"errors"` + } `json:"data"` +} +type ValidatePipelineJsonResponse struct { + Status string `json:"status"` + Data struct { + Result string `json:"result"` + Errors []map[string]interface{} `json:"errors"` + } +} + +type PipelineJsonToJenkinsfileResponse struct { + Status string `json:"status"` + Data struct { + Result string `json:"result"` + Errors []map[string]interface{} `json:"errors"` + Jenkinsfile string `json:"jenkinsfile"` + } `json:"data"` +} + +type JenkinsfileToPipelineJsonResponse struct { + Status string `json:"status"` + Data struct { + Result string `json:"result"` + Errors []map[string]interface{} `json:"errors"` + Json map[string]interface{} `json:"json"` + } `json:"data"` +} +type StepJsonToJenkinsfileResponse struct { + Status string `json:"status"` + Data struct { + Result string `json:"result"` + Errors []map[string]interface{} `json:"errors"` + Jenkinsfile string `json:"jenkinsfile"` + } `json:"data"` +} + +type StepsJenkinsfileToJsonResponse struct { + Status string `json:"status"` + Data struct { + Result string `json:"result"` + Errors []map[string]interface{} `json:"errors"` + Json []map[string]interface{} `json:"json"` + } `json:"data"` +} + +func (j *Jenkins) ValidateJenkinsfile(jenkinsfile string) (*ValidateJenkinsfileResponse, error) { + responseStrut := &ValidateJenkinsfileResponse{} + query := map[string]string{ + "jenkinsfile": jenkinsfile, + } + response, err := j.Requester.PostForm("/pipeline-model-converter/validateJenkinsfile", nil, responseStrut, query) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStrut, nil + +} + +func (j *Jenkins) ValidatePipelineJson(json string) (*ValidatePipelineJsonResponse, error) { + + responseStruct := &ValidatePipelineJsonResponse{} + query := map[string]string{ + "json": json, + } + response, err := j.Requester.PostForm("/pipeline-model-converter/validateJson", nil, responseStruct, query) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStruct, nil +} + +func (j *Jenkins) PipelineJsonToJenkinsfile(json string) (*PipelineJsonToJenkinsfileResponse, error) { + responseStrut := &PipelineJsonToJenkinsfileResponse{} + query := map[string]string{ + "json": json, + } + response, err := j.Requester.PostForm("/pipeline-model-converter/toJenkinsfile", nil, responseStrut, query) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStrut, nil +} + +func (j *Jenkins) JenkinsfileToPipelineJson(jenkinsfile string) (*JenkinsfileToPipelineJsonResponse, error) { + responseStrut := &JenkinsfileToPipelineJsonResponse{} + query := map[string]string{ + "jenkinsfile": jenkinsfile, + } + response, err := j.Requester.PostForm("/pipeline-model-converter/toJson", nil, responseStrut, query) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStrut, nil +} + +func (j *Jenkins) StepsJsonToJenkinsfile(json string) (*StepJsonToJenkinsfileResponse, error) { + responseStrut := &StepJsonToJenkinsfileResponse{} + query := map[string]string{ + "json": json, + } + response, err := j.Requester.PostForm("/pipeline-model-converter/stepsToJenkinsfile", nil, responseStrut, query) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStrut, nil +} + +func (j *Jenkins) StepsJenkinsfileToJson(jenkinsfile string) (*StepsJenkinsfileToJsonResponse, error) { + responseStrut := &StepsJenkinsfileToJsonResponse{} + query := map[string]string{ + "jenkinsfile": jenkinsfile, + } + response, err := j.Requester.PostForm("/pipeline-model-converter/stepsToJson", nil, responseStrut, query) + if err != nil { + return nil, err + } + if response.StatusCode != http.StatusOK { + return nil, errors.New(strconv.Itoa(response.StatusCode)) + } + return responseStrut, nil +} diff --git a/pkg/gojenkins/plugin.go b/pkg/gojenkins/plugin.go new file mode 100644 index 000000000..5e44717db --- /dev/null +++ b/pkg/gojenkins/plugin.go @@ -0,0 +1,75 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "strconv" +) + +type Plugins struct { + Jenkins *Jenkins + Raw *PluginResponse + Base string + Depth int +} + +type PluginResponse struct { + Plugins []Plugin `json:"plugins"` +} + +type Plugin struct { + Active bool `json:"active"` + BackupVersion interface{} `json:"backupVersion"` + Bundled bool `json:"bundled"` + Deleted bool `json:"deleted"` + Dependencies []struct { + Optional string `json:"optional"` + ShortName string `json:"shortname"` + Version string `json:"version"` + } `json:"dependencies"` + Downgradable bool `json:"downgradable"` + Enabled bool `json:"enabled"` + HasUpdate bool `json:"hasUpdate"` + LongName string `json:"longName"` + Pinned bool `json:"pinned"` + ShortName string `json:"shortName"` + SupportsDynamicLoad string `json:"supportsDynamicLoad"` + URL string `json:"url"` + Version string `json:"version"` +} + +func (p *Plugins) Count() int { + return len(p.Raw.Plugins) +} + +func (p *Plugins) Contains(name string) *Plugin { + for _, p := range p.Raw.Plugins { + if p.LongName == name || p.ShortName == name { + return &p + } + } + return nil +} + +func (p *Plugins) Poll() (int, error) { + qr := map[string]string{ + "depth": strconv.Itoa(p.Depth), + } + response, err := p.Jenkins.Requester.GetJSON(p.Base, p.Raw, qr) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/gojenkins/queue.go b/pkg/gojenkins/queue.go new file mode 100644 index 000000000..e73c939b5 --- /dev/null +++ b/pkg/gojenkins/queue.go @@ -0,0 +1,158 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "strconv" +) + +type Queue struct { + Jenkins *Jenkins + Raw *queueResponse + Base string +} + +type queueResponse struct { + Items []taskResponse +} + +type Task struct { + Raw *taskResponse + Jenkins *Jenkins + Queue *Queue +} + +type taskResponse struct { + Actions []generalAction `json:"actions"` + Blocked bool `json:"blocked"` + Buildable bool `json:"buildable"` + BuildableStartMilliseconds int64 `json:"buildableStartMilliseconds"` + ID int64 `json:"id"` + InQueueSince int64 `json:"inQueueSince"` + Params string `json:"params"` + Pending bool `json:"pending"` + Stuck bool `json:"stuck"` + Task struct { + Color string `json:"color"` + Name string `json:"name"` + URL string `json:"url"` + } `json:"task"` + URL string `json:"url"` + Why string `json:"why"` +} + +type generalAction struct { + Causes []map[string]interface{} + Parameters []parameter +} + +type QueueItemResponse struct { + Actions []generalAction `json:"actions"` + Blocked bool `json:"blocked"` + Buildable bool `json:"buildable"` + ID int64 `json:"id"` + InQueueSince int64 `json:"inQueueSince"` + Params string `json:"params"` + Stuck bool `json:"stuck"` + Task struct { + Color string `json:"color"` + Name string `json:"name"` + URL string `json:"url"` + } `json:"task"` + URL string `json:"url"` + Cancelled bool `json:"cancelled"` + Why string `json:"why"` + Executable struct { + Number int64 `json:"number"` + Url string `json:"url"` + } `json:"executable"` +} + +func (q *Queue) Tasks() []*Task { + tasks := make([]*Task, len(q.Raw.Items)) + for i, t := range q.Raw.Items { + tasks[i] = &Task{Jenkins: q.Jenkins, Queue: q, Raw: &t} + } + return tasks +} + +func (q *Queue) GetTaskById(id int64) *Task { + for _, t := range q.Raw.Items { + if t.ID == id { + return &Task{Jenkins: q.Jenkins, Queue: q, Raw: &t} + } + } + return nil +} + +func (q *Queue) GetTasksForJob(name string) []*Task { + tasks := make([]*Task, 0) + for _, t := range q.Raw.Items { + if t.Task.Name == name { + tasks = append(tasks, &Task{Jenkins: q.Jenkins, Queue: q, Raw: &t}) + } + } + return tasks +} + +func (q *Queue) CancelTask(id int64) (bool, error) { + task := q.GetTaskById(id) + return task.Cancel() +} + +func (t *Task) Cancel() (bool, error) { + qr := map[string]string{ + "id": strconv.FormatInt(t.Raw.ID, 10), + } + response, err := t.Jenkins.Requester.Post(t.Jenkins.GetQueueUrl()+"/cancelItem", nil, t.Raw, qr) + if err != nil { + return false, err + } + return response.StatusCode == 200, nil +} + +func (t *Task) GetJob() (*Job, error) { + return t.Jenkins.GetJob(t.Raw.Task.Name) +} + +func (t *Task) GetWhy() string { + return t.Raw.Why +} + +func (t *Task) GetParameters() []parameter { + for _, a := range t.Raw.Actions { + if a.Parameters != nil { + return a.Parameters + } + } + return nil +} + +func (t *Task) GetCauses() []map[string]interface{} { + for _, a := range t.Raw.Actions { + if a.Causes != nil { + return a.Causes + } + } + return nil +} + +func (q *Queue) Poll() (int, error) { + response, err := q.Jenkins.Requester.GetJSON(q.Base, q.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/gojenkins/request.go b/pkg/gojenkins/request.go new file mode 100644 index 000000000..7daec813f --- /dev/null +++ b/pkg/gojenkins/request.go @@ -0,0 +1,468 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "io/ioutil" + "mime/multipart" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" +) + +// Request Methods + +type APIRequest struct { + Method string + Endpoint string + Payload io.Reader + Headers http.Header + Suffix string +} + +func (ar *APIRequest) SetHeader(key string, value string) *APIRequest { + ar.Headers.Set(key, value) + return ar +} + +func NewAPIRequest(method string, endpoint string, payload io.Reader) *APIRequest { + var headers = http.Header{} + var suffix string + ar := &APIRequest{method, endpoint, payload, headers, suffix} + return ar +} + +type Requester struct { + Base string + BasicAuth *BasicAuth + Client *http.Client + CACert []byte + SslVerify bool + connControl chan struct{} +} + +func (r *Requester) SetCrumb(ar *APIRequest) error { + crumbData := map[string]string{} + response, err := r.GetJSON("/crumbIssuer/api/json", &crumbData, nil) + if err != nil { + jenkinsError, ok := err.(*ErrorResponse) + if ok && jenkinsError.Response.StatusCode == http.StatusNotFound { + return nil + } + return err + } + if response.StatusCode == 200 && crumbData["crumbRequestField"] != "" { + ar.SetHeader(crumbData["crumbRequestField"], crumbData["crumb"]) + } + + return nil +} + +func (r *Requester) PostJSON(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string) (*http.Response, error) { + ar := NewAPIRequest("POST", endpoint, payload) + if err := r.SetCrumb(ar); err != nil { + return nil, err + } + ar.SetHeader("Content-Type", "application/x-www-form-urlencoded") + ar.Suffix = "api/json" + return r.Do(ar, &responseStruct, querystring) +} + +func (r *Requester) Post(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string) (*http.Response, error) { + ar := NewAPIRequest("POST", endpoint, payload) + if err := r.SetCrumb(ar); err != nil { + return nil, err + } + ar.SetHeader("Content-Type", "application/x-www-form-urlencoded") + ar.Suffix = "" + return r.Do(ar, &responseStruct, querystring) +} +func (r *Requester) PostForm(endpoint string, payload io.Reader, responseStruct interface{}, formString map[string]string) (*http.Response, error) { + ar := NewAPIRequest("POST", endpoint, payload) + if err := r.SetCrumb(ar); err != nil { + return nil, err + } + ar.SetHeader("Content-Type", "application/x-www-form-urlencoded") + ar.Suffix = "" + return r.DoPostForm(ar, &responseStruct, formString) +} + +func (r *Requester) PostFiles(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string, files []string) (*http.Response, error) { + ar := NewAPIRequest("POST", endpoint, payload) + if err := r.SetCrumb(ar); err != nil { + return nil, err + } + return r.Do(ar, &responseStruct, querystring, files) +} + +func (r *Requester) PostXML(endpoint string, xml string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) { + payload := bytes.NewBuffer([]byte(xml)) + ar := NewAPIRequest("POST", endpoint, payload) + if err := r.SetCrumb(ar); err != nil { + return nil, err + } + ar.SetHeader("Content-Type", "application/xml;charset=utf-8") + ar.Suffix = "" + return r.Do(ar, &responseStruct, querystring) +} + +func (r *Requester) GetJSON(endpoint string, responseStruct interface{}, query map[string]string) (*http.Response, error) { + ar := NewAPIRequest("GET", endpoint, nil) + ar.SetHeader("Content-Type", "application/json") + ar.Suffix = "api/json" + return r.Do(ar, &responseStruct, query) +} + +func (r *Requester) GetXML(endpoint string, responseStruct interface{}, query map[string]string) (*http.Response, error) { + ar := NewAPIRequest("GET", endpoint, nil) + ar.SetHeader("Content-Type", "application/xml") + ar.Suffix = "" + return r.Do(ar, responseStruct, query) +} + +func (r *Requester) Get(endpoint string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) { + ar := NewAPIRequest("GET", endpoint, nil) + ar.Suffix = "" + return r.Do(ar, responseStruct, querystring) +} + +func (r *Requester) GetHtml(endpoint string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) { + ar := NewAPIRequest("GET", endpoint, nil) + ar.Suffix = "" + return r.DoGet(ar, responseStruct, querystring) +} + +func (r *Requester) SetClient(client *http.Client) *Requester { + r.Client = client + return r +} + +//Add auth on redirect if required. +func (r *Requester) redirectPolicyFunc(req *http.Request, via []*http.Request) error { + if r.BasicAuth != nil { + req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password) + } + return nil +} + +func (r *Requester) DoGet(ar *APIRequest, responseStruct interface{}, options ...interface{}) (*http.Response, error) { + fileUpload := false + var files []string + URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix) + + if err != nil { + return nil, err + } + + for _, o := range options { + switch v := o.(type) { + case map[string]string: + + querystring := make(url.Values) + for key, val := range v { + querystring.Set(key, val) + } + + URL.RawQuery = querystring.Encode() + case []string: + fileUpload = true + files = v + } + } + var req *http.Request + if fileUpload { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + for _, file := range files { + fileData, err := os.Open(file) + if err != nil { + Error.Println(err.Error()) + return nil, err + } + + part, err := writer.CreateFormFile("file", filepath.Base(file)) + if err != nil { + Error.Println(err.Error()) + return nil, err + } + if _, err = io.Copy(part, fileData); err != nil { + return nil, err + } + defer fileData.Close() + } + var params map[string]string + json.NewDecoder(ar.Payload).Decode(¶ms) + for key, val := range params { + if err = writer.WriteField(key, val); err != nil { + return nil, err + } + } + if err = writer.Close(); err != nil { + return nil, err + } + req, err = http.NewRequest(ar.Method, URL.String(), body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + } else { + req, err = http.NewRequest(ar.Method, URL.String(), ar.Payload) + if err != nil { + return nil, err + } + } + + if r.BasicAuth != nil { + req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password) + } + req.Close = true + req.Header.Add("Accept", "*/*") + for k := range ar.Headers { + req.Header.Add(k, ar.Headers.Get(k)) + } + r.connControl <- struct{}{} + if response, err := r.Client.Do(req); err != nil { + <-r.connControl + return nil, err + } else { + <-r.connControl + errorText := response.Header.Get("X-Error") + if errorText != "" { + return nil, errors.New(errorText) + } + err := CheckResponse(response) + if err != nil { + return nil, err + } + switch responseStruct.(type) { + case *string: + return r.ReadRawResponse(response, responseStruct) + default: + return r.ReadJSONResponse(response, responseStruct) + } + + } + +} + +func (r *Requester) Do(ar *APIRequest, responseStruct interface{}, options ...interface{}) (*http.Response, error) { + if !strings.HasSuffix(ar.Endpoint, "/") && ar.Method != "POST" { + ar.Endpoint += "/" + } + + fileUpload := false + var files []string + URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix) + + if err != nil { + return nil, err + } + + for _, o := range options { + switch v := o.(type) { + case map[string]string: + + querystring := make(url.Values) + for key, val := range v { + querystring.Set(key, val) + } + + URL.RawQuery = querystring.Encode() + case []string: + fileUpload = true + files = v + } + } + var req *http.Request + if fileUpload { + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + for _, file := range files { + fileData, err := os.Open(file) + if err != nil { + Error.Println(err.Error()) + return nil, err + } + + part, err := writer.CreateFormFile("file", filepath.Base(file)) + if err != nil { + Error.Println(err.Error()) + return nil, err + } + if _, err = io.Copy(part, fileData); err != nil { + return nil, err + } + defer fileData.Close() + } + var params map[string]string + json.NewDecoder(ar.Payload).Decode(¶ms) + for key, val := range params { + if err = writer.WriteField(key, val); err != nil { + return nil, err + } + } + if err = writer.Close(); err != nil { + return nil, err + } + req, err = http.NewRequest(ar.Method, URL.String(), body) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", writer.FormDataContentType()) + } else { + req, err = http.NewRequest(ar.Method, URL.String(), ar.Payload) + if err != nil { + return nil, err + } + } + + if r.BasicAuth != nil { + req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password) + } + req.Close = true + req.Header.Add("Accept", "*/*") + for k := range ar.Headers { + req.Header.Add(k, ar.Headers.Get(k)) + } + r.connControl <- struct{}{} + if response, err := r.Client.Do(req); err != nil { + <-r.connControl + return nil, err + } else { + <-r.connControl + errorText := response.Header.Get("X-Error") + if errorText != "" { + return nil, errors.New(errorText) + } + err := CheckResponse(response) + if err != nil { + return nil, err + } + switch responseStruct.(type) { + case *string: + return r.ReadRawResponse(response, responseStruct) + default: + return r.ReadJSONResponse(response, responseStruct) + } + + } + +} + +func (r *Requester) DoPostForm(ar *APIRequest, responseStruct interface{}, form map[string]string) (*http.Response, error) { + + if !strings.HasSuffix(ar.Endpoint, "/") && ar.Method != "POST" { + ar.Endpoint += "/" + } + URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix) + + if err != nil { + return nil, err + } + formValue := make(url.Values) + for k, v := range form { + formValue.Set(k, v) + } + req, err := http.NewRequest("POST", URL.String(), strings.NewReader(formValue.Encode())) + if r.BasicAuth != nil { + req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password) + } + req.Close = true + req.Header.Add("Accept", "*/*") + for k := range ar.Headers { + req.Header.Add(k, ar.Headers.Get(k)) + } + r.connControl <- struct{}{} + if response, err := r.Client.Do(req); err != nil { + <-r.connControl + return nil, err + } else { + <-r.connControl + errorText := response.Header.Get("X-Error") + if errorText != "" { + return nil, errors.New(errorText) + } + err := CheckResponse(response) + if err != nil { + return nil, err + } + switch responseStruct.(type) { + case *string: + return r.ReadRawResponse(response, responseStruct) + default: + return r.ReadJSONResponse(response, responseStruct) + } + + } +} + +func (r *Requester) ReadRawResponse(response *http.Response, responseStruct interface{}) (*http.Response, error) { + defer response.Body.Close() + + content, err := ioutil.ReadAll(response.Body) + if err != nil { + return nil, err + } + if str, ok := responseStruct.(*string); ok { + *str = string(content) + } else { + return nil, fmt.Errorf("Could not cast responseStruct to *string") + } + + return response, nil +} + +func (r *Requester) ReadJSONResponse(response *http.Response, responseStruct interface{}) (*http.Response, error) { + defer response.Body.Close() + err := json.NewDecoder(response.Body).Decode(responseStruct) + if err != nil && err.Error() == "EOF" { + return response, nil + } + return response, nil +} + +type ErrorResponse struct { + Body []byte + Response *http.Response + Message string +} + +func (e *ErrorResponse) Error() string { + u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, e.Response.Request.URL.RequestURI()) + return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message) +} +func CheckResponse(r *http.Response) error { + + switch r.StatusCode { + case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent, http.StatusFound, http.StatusNotModified: + return nil + } + defer r.Body.Close() + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + errorResponse.Body = data + errorResponse.Message = string(data) + } + + return errorResponse +} diff --git a/pkg/gojenkins/role.go b/pkg/gojenkins/role.go new file mode 100644 index 000000000..872d32655 --- /dev/null +++ b/pkg/gojenkins/role.go @@ -0,0 +1,204 @@ +package gojenkins + +import ( + "errors" + "net/http" + "reflect" + "strconv" + "strings" +) + +type GlobalRoleResponse struct { + RoleName string `json:"roleName"` + PermissionIds GlobalPermissionIds `json:"permissionIds"` +} + +type GlobalRole struct { + Jenkins *Jenkins + Raw GlobalRoleResponse +} + +type GlobalPermissionIds struct { + Administer bool `json:"hudson.model.Hudson.Administer"` + GlobalRead bool `json:"hudson.model.Hudson.Read"` + CredentialCreate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Create"` + CredentialUpdate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Update"` + CredentialView bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.View"` + CredentialDelete bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Delete"` + CredentialManageDomains bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains"` + SlaveCreate bool `json:"hudson.model.Computer.Create"` + SlaveConfigure bool `json:"hudson.model.Computer.Configure"` + SlaveDelete bool `json:"hudson.model.Computer.Delete"` + SlaveBuild bool `json:"hudson.model.Computer.Build"` + SlaveConnect bool `json:"hudson.model.Computer.Connect"` + SlaveDisconnect bool `json:"hudson.model.Computer.Disconnect"` + ItemBuild bool `json:"hudson.model.Item.Build"` + ItemCreate bool `json:"hudson.model.Item.Create"` + ItemRead bool `json:"hudson.model.Item.Read"` + ItemConfigure bool `json:"hudson.model.Item.Configure"` + ItemCancel bool `json:"hudson.model.Item.Cancel"` + ItemMove bool `json:"hudson.model.Item.Move"` + ItemDiscover bool `json:"hudson.model.Item.Discover"` + ItemWorkspace bool `json:"hudson.model.Item.Workspace"` + ItemDelete bool `json:"hudson.model.Item.Delete"` + RunUpdate bool `json:"hudson.model.Run.Update"` + RunDelete bool `json:"hudson.model.Run.Delete"` + ViewCreate bool `json:"hudson.model.View.Create"` + ViewConfigure bool `json:"hudson.model.View.Configure"` + ViewRead bool `json:"hudson.model.View.Read"` + ViewDelete bool `json:"hudson.model.View.Delete"` + SCMTag bool `json:"hudson.scm.SCM.Tag"` +} + +type ProjectRole struct { + Jenkins *Jenkins + Raw ProjectRoleResponse +} + +type ProjectRoleResponse struct { + RoleName string `json:"roleName"` + PermissionIds ProjectPermissionIds `json:"permissionIds"` + Pattern string `json:"pattern"` +} + +type ProjectPermissionIds struct { + CredentialCreate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Create"` + CredentialUpdate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Update"` + CredentialView bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.View"` + CredentialDelete bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Delete"` + CredentialManageDomains bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains"` + ItemBuild bool `json:"hudson.model.Item.Build"` + ItemCreate bool `json:"hudson.model.Item.Create"` + ItemRead bool `json:"hudson.model.Item.Read"` + ItemConfigure bool `json:"hudson.model.Item.Configure"` + ItemCancel bool `json:"hudson.model.Item.Cancel"` + ItemMove bool `json:"hudson.model.Item.Move"` + ItemDiscover bool `json:"hudson.model.Item.Discover"` + ItemWorkspace bool `json:"hudson.model.Item.Workspace"` + ItemDelete bool `json:"hudson.model.Item.Delete"` + RunUpdate bool `json:"hudson.model.Run.Update"` + RunDelete bool `json:"hudson.model.Run.Delete"` + RunReplay bool `json:"hudson.model.Run.Replay"` + SCMTag bool `json:"hudson.scm.SCM.Tag"` +} + +func (j *GlobalRole) Update(ids GlobalPermissionIds) error { + var idArray []string + values := reflect.ValueOf(ids) + for i := 0; i < values.NumField(); i++ { + field := values.Field(i) + if field.Bool() { + idArray = append(idArray, values.Type().Field(i).Tag.Get("json")) + } + } + param := map[string]string{ + "roleName": j.Raw.RoleName, + "type": GLOBAL_ROLE, + "permissionIds": strings.Join(idArray, ","), + "overwrite": strconv.FormatBool(true), + } + responseString := "" + response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/addRole", nil, &responseString, param) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +func (j *GlobalRole) AssignRole(sid string) error { + param := map[string]string{ + "type": GLOBAL_ROLE, + "roleName": j.Raw.RoleName, + "sid": sid, + } + responseString := "" + response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/assignRole", nil, &responseString, param) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +func (j *GlobalRole) UnAssignRole(sid string) error { + param := map[string]string{ + "type": GLOBAL_ROLE, + "roleName": j.Raw.RoleName, + "sid": sid, + } + responseString := "" + response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/unassignRole", nil, &responseString, param) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +func (j *ProjectRole) Update(pattern string, ids ProjectPermissionIds) error { + var idArray []string + values := reflect.ValueOf(ids) + for i := 0; i < values.NumField(); i++ { + field := values.Field(i) + if field.Bool() { + idArray = append(idArray, values.Type().Field(i).Tag.Get("json")) + } + } + param := map[string]string{ + "roleName": j.Raw.RoleName, + "type": PROJECT_ROLE, + "permissionIds": strings.Join(idArray, ","), + "overwrite": strconv.FormatBool(true), + "pattern": pattern, + } + responseString := "" + response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/addRole", nil, &responseString, param) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +func (j *ProjectRole) AssignRole(sid string) error { + param := map[string]string{ + "type": PROJECT_ROLE, + "roleName": j.Raw.RoleName, + "sid": sid, + } + responseString := "" + response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/assignRole", nil, &responseString, param) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} + +func (j *ProjectRole) UnAssignRole(sid string) error { + param := map[string]string{ + "type": PROJECT_ROLE, + "roleName": j.Raw.RoleName, + "sid": sid, + } + responseString := "" + response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/unassignRole", nil, &responseString, param) + if err != nil { + return err + } + if response.StatusCode != http.StatusOK { + return errors.New(strconv.Itoa(response.StatusCode)) + } + return nil +} diff --git a/pkg/gojenkins/utils.go b/pkg/gojenkins/utils.go new file mode 100644 index 000000000..92b714492 --- /dev/null +++ b/pkg/gojenkins/utils.go @@ -0,0 +1,61 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "encoding/json" + "strings" + "time" + "unicode/utf8" +) + +func makeJson(data interface{}) string { + str, err := json.Marshal(data) + if err != nil { + return "" + } + return string(json.RawMessage(str)) +} + +func Reverse(s string) string { + size := len(s) + buf := make([]byte, size) + for start := 0; start < size; { + r, n := utf8.DecodeRuneInString(s[start:]) + start += n + utf8.EncodeRune(buf[size-start:], r) + } + return string(buf) +} + +type JenkinsBlueTime time.Time + +func (t *JenkinsBlueTime) UnmarshalJSON(b []byte) error { + if b == nil || strings.Trim(string(b), "\"") == "null" { + *t = JenkinsBlueTime(time.Time{}) + return nil + } + j, err := time.Parse("2006-01-02T15:04:05.000-0700", strings.Trim(string(b), "\"")) + + if err != nil { + return err + } + *t = JenkinsBlueTime(j) + return nil +} + +func (t JenkinsBlueTime) MarshalJSON() ([]byte, error) { + return json.Marshal(time.Time(t)) +} diff --git a/pkg/gojenkins/utils/utils.go b/pkg/gojenkins/utils/utils.go new file mode 100644 index 000000000..93ad30b3b --- /dev/null +++ b/pkg/gojenkins/utils/utils.go @@ -0,0 +1,21 @@ +package utils + +import ( + "github.com/asaskevich/govalidator" + "kubesphere.io/kubesphere/pkg/gojenkins" + "net/http" + "strconv" +) + +func GetJenkinsStatusCode(jenkinsErr error) int { + if code, err := strconv.Atoi(jenkinsErr.Error()); err == nil { + message := http.StatusText(code) + if !govalidator.IsNull(message) { + return code + } + } + if jErr, ok := jenkinsErr.(*gojenkins.ErrorResponse); ok { + return jErr.Response.StatusCode + } + return http.StatusInternalServerError +} diff --git a/pkg/gojenkins/views.go b/pkg/gojenkins/views.go new file mode 100644 index 000000000..99b802643 --- /dev/null +++ b/pkg/gojenkins/views.go @@ -0,0 +1,94 @@ +// Copyright 2015 Vadim Kravcenko +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package gojenkins + +import ( + "errors" + "strconv" +) + +type View struct { + Raw *ViewResponse + Jenkins *Jenkins + Base string +} + +type ViewResponse struct { + Description string `json:"description"` + Jobs []InnerJob `json:"jobs"` + Name string `json:"name"` + Property []interface{} `json:"property"` + URL string `json:"url"` +} + +var ( + LIST_VIEW = "hudson.model.ListView" + NESTED_VIEW = "hudson.plugins.nested_view.NestedView" + MY_VIEW = "hudson.model.MyView" + DASHBOARD_VIEW = "hudson.plugins.view.dashboard.Dashboard" + PIPELINE_VIEW = "au.com.centrumsystems.hudson.plugin.buildpipeline.BuildPipelineView" +) + +// Returns True if successfully added Job, otherwise false +func (v *View) AddJob(name string) (bool, error) { + url := "/addJobToView" + qr := map[string]string{"name": name} + resp, err := v.Jenkins.Requester.Post(v.Base+url, nil, nil, qr) + if err != nil { + return false, err + } + if resp.StatusCode == 200 { + return true, nil + } + return false, errors.New(strconv.Itoa(resp.StatusCode)) +} + +// Returns True if successfully deleted Job, otherwise false +func (v *View) DeleteJob(name string) (bool, error) { + url := "/removeJobFromView" + qr := map[string]string{"name": name} + resp, err := v.Jenkins.Requester.Post(v.Base+url, nil, nil, qr) + if err != nil { + return false, err + } + if resp.StatusCode == 200 { + return true, nil + } + return false, errors.New(strconv.Itoa(resp.StatusCode)) +} + +func (v *View) GetDescription() string { + return v.Raw.Description +} + +func (v *View) GetJobs() []InnerJob { + return v.Raw.Jobs +} + +func (v *View) GetName() string { + return v.Raw.Name +} + +func (v *View) GetUrl() string { + return v.Raw.URL +} + +func (v *View) Poll() (int, error) { + response, err := v.Jenkins.Requester.GetJSON(v.Base, v.Raw, nil) + if err != nil { + return 0, err + } + return response.StatusCode, nil +} diff --git a/pkg/models/devops/common.go b/pkg/models/devops/common.go new file mode 100644 index 000000000..44ac74fe5 --- /dev/null +++ b/pkg/models/devops/common.go @@ -0,0 +1,61 @@ +package devops + +import ( + "github.com/fatih/structs" + "kubesphere.io/kubesphere/pkg/utils/stringutils" +) + +func GetColumnsFromStruct(s interface{}) []string { + names := structs.Names(s) + for i, name := range names { + names[i] = stringutils.CamelCaseToUnderscore(name) + } + return names +} + +func GetColumnsFromStructWithPrefix(prefix string, s interface{}) []string { + names := structs.Names(s) + for i, name := range names { + names[i] = WithPrefix(prefix, stringutils.CamelCaseToUnderscore(name)) + } + return names +} + +func WithPrefix(prefix, str string) string { + return prefix + "." + str +} + +const ( + StatusActive = "active" + StatusDeleted = "deleted" + StatusDeleting = "deleting" + StatusFailed = "failed" + StatusPending = "pending" + StatusWorking = "working" + StatusSuccessful = "successful" +) + +const ( + StatusColumn = "status" + StatusTimeColumn = "status_time" +) + +const ( + VisibilityPrivate = "private" + VisibilityPublic = "public" +) + +const ( + KS_ADMIN = "admin" +) + +const ( + ProjectOwner = "owner" + ProjectMaintainer = "maintainer" + ProjectDeveloper = "developer" + ProjectReporter = "reporter" +) + +const ( + JenkinsAllUserRoleName = "kubesphere-user" +) diff --git a/pkg/models/devops/membership.go b/pkg/models/devops/membership.go new file mode 100644 index 000000000..05dae0a54 --- /dev/null +++ b/pkg/models/devops/membership.go @@ -0,0 +1,28 @@ +package devops + +const ( + DevOpsProjectMembershipTableName = "project_membership" + DevOpsProjectMembershipUsernameColumn = "project_membership.username" + DevOpsProjectMembershipProjectIdColumn = "project_membership.project_id" + DevOpsProjectMembershipRoleColumn = "project_membership.role" +) + +type DevOpsProjectMembership struct { + Username string `json:"username"` + ProjectId string `json:"project_id" db:"project_id"` + Role string `json:"role"` + Status string `json:"status"` + GrantBy string `json:"grand_by,omitempty"` +} + +var DevOpsProjectMembershipColumns = GetColumnsFromStruct(&DevOpsProjectMembership{}) + +func NewDevOpsProjectMemberShip(username, projectId, role, grantBy string) *DevOpsProjectMembership { + return &DevOpsProjectMembership{ + Username: username, + ProjectId: projectId, + Role: role, + Status: StatusActive, + GrantBy: grantBy, + } +} diff --git a/pkg/models/devops/project.go b/pkg/models/devops/project.go new file mode 100644 index 000000000..f8b81c03e --- /dev/null +++ b/pkg/models/devops/project.go @@ -0,0 +1,45 @@ +package devops + +import ( + "kubesphere.io/kubesphere/pkg/utils/idutils" + "time" +) + +var DevOpsProjectColumns = GetColumnsFromStruct(&DevOpsProject{}) + +const ( + DevOpsProjectTableName = "project" + DevOpsProjectPrefix = "project-" + DevOpsProjectDescriptionColumn = "description" + DevOpsProjectIdColumn = "project.project_id" + DevOpsProjectNameColumn = "project.name" + DevOpsProjectExtraColumn = "project.extra" + DevOpsProjectWorkSpaceColumn = "project.workspace" + DevOpsProjectCreateTimeColumn = "project.create_time" +) + +type DevOpsProject struct { + ProjectId string `json:"project_id" db:"project_id"` + Name string `json:"name"` + Description string `json:"description"` + Creator string `json:"creator"` + CreateTime time.Time `json:"create_time"` + Status string `json:"status"` + Visibility string `json:"visibility"` + Extra string `json:"extra"` + Workspace string `json:"workspace"` +} + +func NewDevOpsProject(name, description, creator, extra, workspace string) *DevOpsProject { + return &DevOpsProject{ + ProjectId: idutils.GetUuid(DevOpsProjectPrefix), + Name: name, + Description: description, + Creator: creator, + CreateTime: time.Now(), + Status: StatusActive, + Visibility: VisibilityPrivate, + Extra: extra, + Workspace: workspace, + } +} diff --git a/pkg/models/tenant/devops.go b/pkg/models/tenant/devops.go index 68fad574f..4c5100337 100644 --- a/pkg/models/tenant/devops.go +++ b/pkg/models/tenant/devops.go @@ -18,73 +18,469 @@ package tenant import ( + "fmt" + "github.com/gocraft/dbr" + "github.com/golang/glog" + "kubesphere.io/kubesphere/pkg/db" + "kubesphere.io/kubesphere/pkg/gojenkins" + "kubesphere.io/kubesphere/pkg/gojenkins/utils" "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/devops" "kubesphere.io/kubesphere/pkg/params" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" - "kubesphere.io/kubesphere/pkg/simple/client/mysql" - "sort" - "strings" + "kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins" + "kubesphere.io/kubesphere/pkg/simple/client/devops_mysql" + "kubesphere.io/kubesphere/pkg/utils/reflectutils" + "net/http" + "sync" ) +const ( + ProjectOwner = "owner" + ProjectMaintainer = "maintainer" + ProjectDeveloper = "developer" + ProjectReporter = "reporter" +) + +var AllRoleSlice = []string{ProjectDeveloper, ProjectReporter, ProjectMaintainer, ProjectOwner} + +var JenkinsOwnerProjectPermissionIds = &gojenkins.ProjectPermissionIds{ + CredentialCreate: true, + CredentialDelete: true, + CredentialManageDomains: true, + CredentialUpdate: true, + CredentialView: true, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: true, + ItemCreate: true, + ItemDelete: true, + ItemDiscover: true, + ItemMove: true, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: true, +} + +var JenkinsProjectPermissionMap = map[string]gojenkins.ProjectPermissionIds{ + ProjectOwner: gojenkins.ProjectPermissionIds{ + CredentialCreate: true, + CredentialDelete: true, + CredentialManageDomains: true, + CredentialUpdate: true, + CredentialView: true, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: true, + ItemCreate: true, + ItemDelete: true, + ItemDiscover: true, + ItemMove: true, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: true, + }, + ProjectMaintainer: gojenkins.ProjectPermissionIds{ + CredentialCreate: true, + CredentialDelete: true, + CredentialManageDomains: true, + CredentialUpdate: true, + CredentialView: true, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: false, + ItemCreate: true, + ItemDelete: false, + ItemDiscover: true, + ItemMove: false, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: true, + }, + ProjectDeveloper: gojenkins.ProjectPermissionIds{ + CredentialCreate: false, + CredentialDelete: false, + CredentialManageDomains: false, + CredentialUpdate: false, + CredentialView: false, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: false, + ItemCreate: false, + ItemDelete: false, + ItemDiscover: true, + ItemMove: false, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: false, + }, + ProjectReporter: gojenkins.ProjectPermissionIds{ + CredentialCreate: false, + CredentialDelete: false, + CredentialManageDomains: false, + CredentialUpdate: false, + CredentialView: false, + ItemBuild: false, + ItemCancel: false, + ItemConfigure: false, + ItemCreate: false, + ItemDelete: false, + ItemDiscover: true, + ItemMove: false, + ItemRead: true, + ItemWorkspace: false, + RunDelete: false, + RunReplay: false, + RunUpdate: false, + SCMTag: false, + }, +} + +var JenkinsPipelinePermissionMap = map[string]gojenkins.ProjectPermissionIds{ + ProjectOwner: gojenkins.ProjectPermissionIds{ + CredentialCreate: true, + CredentialDelete: true, + CredentialManageDomains: true, + CredentialUpdate: true, + CredentialView: true, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: true, + ItemCreate: true, + ItemDelete: true, + ItemDiscover: true, + ItemMove: true, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: true, + }, + ProjectMaintainer: gojenkins.ProjectPermissionIds{ + CredentialCreate: true, + CredentialDelete: true, + CredentialManageDomains: true, + CredentialUpdate: true, + CredentialView: true, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: true, + ItemCreate: true, + ItemDelete: true, + ItemDiscover: true, + ItemMove: true, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: true, + }, + ProjectDeveloper: gojenkins.ProjectPermissionIds{ + CredentialCreate: false, + CredentialDelete: false, + CredentialManageDomains: false, + CredentialUpdate: false, + CredentialView: false, + ItemBuild: true, + ItemCancel: true, + ItemConfigure: false, + ItemCreate: false, + ItemDelete: false, + ItemDiscover: true, + ItemMove: false, + ItemRead: true, + ItemWorkspace: true, + RunDelete: true, + RunReplay: true, + RunUpdate: true, + SCMTag: false, + }, + ProjectReporter: gojenkins.ProjectPermissionIds{ + CredentialCreate: false, + CredentialDelete: false, + CredentialManageDomains: false, + CredentialUpdate: false, + CredentialView: false, + ItemBuild: false, + ItemCancel: false, + ItemConfigure: false, + ItemCreate: false, + ItemDelete: false, + ItemDiscover: true, + ItemMove: false, + ItemRead: true, + ItemWorkspace: false, + RunDelete: false, + RunReplay: false, + RunUpdate: false, + SCMTag: false, + }, +} + +func GetProjectRoleName(projectId, role string) string { + return fmt.Sprintf("%s-%s-project", projectId, role) +} + +func GetPipelineRoleName(projectId, role string) string { + return fmt.Sprintf("%s-%s-pipeline", projectId, role) +} + +func GetProjectRolePattern(projectId string) string { + return fmt.Sprintf("^%s$", projectId) +} + +func GetPipelineRolePattern(projectId string) string { + return fmt.Sprintf("^%s/.*", projectId) +} + +type DevOpsProjectRoleResponse struct { + ProjectRole *gojenkins.ProjectRole + Err error +} + +func CheckProjectUserInRole(username, projectId string, roles []string) error { + if username == devops.KS_ADMIN { + return nil + } + dbconn := devops_mysql.OpenDatabase() + membership := &devops.DevOpsProjectMembership{} + err := dbconn.Select(devops.DevOpsProjectMembershipColumns...). + From(devops.DevOpsProjectMembershipTableName). + Where(db.And( + db.Eq(devops.DevOpsProjectMembershipUsernameColumn, username), + db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId))).LoadOne(membership) + if err != nil { + return err + } + if !reflectutils.In(membership.Role, roles) { + return fmt.Errorf("user [%s] in project [%s] role is not in %s", username, projectId, roles) + } + return nil +} + func ListDevopsProjects(workspace, username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) { - db := mysql.Client() + dbconn := devops_mysql.OpenDatabase() - var workspaceDOPBindings []models.WorkspaceDPBinding + query := dbconn.Select(devops.GetColumnsFromStructWithPrefix(devops.DevOpsProjectTableName, devops.DevOpsProject{})...). + From(devops.DevOpsProjectTableName) + var sqconditions []dbr.Builder - if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil { - return nil, err - } - - projects, err := kubesphere.Client().ListDevopsProjects(username) - if err != nil { - return nil, err + sqconditions = append(sqconditions, db.Eq(devops.DevOpsProjectWorkSpaceColumn, workspace)) + + switch username { + case devops.KS_ADMIN: + default: + onCondition := fmt.Sprintf("%s = %s", devops.DevOpsProjectMembershipProjectIdColumn, devops.DevOpsProjectIdColumn) + query.Join(devops.DevOpsProjectMembershipTableName, onCondition) + sqconditions = append(sqconditions, db.Eq(devops.DevOpsProjectMembershipUsernameColumn, username)) + sqconditions = append(sqconditions, db.Eq( + devops.DevOpsProjectMembershipTableName+"."+devops.StatusColumn, devops.StatusActive)) } + sqconditions = append(sqconditions, db.Eq( + devops.DevOpsProjectTableName+"."+devops.StatusColumn, devops.StatusActive)) if keyword := conditions.Match["keyword"]; keyword != "" { - for i := 0; i < len(projects); i++ { - if !strings.Contains(projects[i].Name, keyword) { - projects = append(projects[:i], projects[i+1:]...) - i-- - } - } + sqconditions = append(sqconditions, db.Like(devops.DevOpsProjectNameColumn, keyword)) } + projects := make([]*devops.DevOpsProject, 0) - sort.Slice(projects, func(i, j int) bool { + if len(sqconditions) > 0 { + query.Where(db.And(sqconditions...)) + } + switch orderBy { + case "name": if reverse { - tmp := i - i = j - j = tmp + query.OrderDesc(devops.DevOpsProjectNameColumn) + } else { + query.OrderAsc(devops.DevOpsProjectNameColumn) } - switch orderBy { - case "name": - return projects[i].Name > projects[j].Name - default: - return projects[i].CreateTime.Before(*projects[j].CreateTime) + default: + if reverse { + query.OrderAsc(devops.DevOpsProjectCreateTimeColumn) + } else { + query.OrderDesc(devops.DevOpsProjectCreateTimeColumn) } - }) - for i := 0; i < len(projects); i++ { - inWorkspace := false - - for _, binding := range workspaceDOPBindings { - if binding.DevOpsProject == projects[i].ProjectId { - inWorkspace = true - } - } - if !inWorkspace { - projects = append(projects[:i], projects[i+1:]...) - i-- - } + } + query.Limit(uint64(limit)) + query.Offset(uint64(offset)) + _, err := query.Load(&projects) + if err != nil { + glog.Errorf("%+v", err) + return nil, err + } + count, err := query.Count() + if err != nil { + glog.Errorf("%+v", err) + return nil, err } - // limit offset result := make([]interface{}, 0) - for i, v := range projects { - if len(result) < limit && i >= offset { - result = append(result, v) + for _, v := range projects { + result = append(result, v) + } + + return &models.PageableResponse{Items: result, TotalCount: int(count)}, nil +} + +func DeleteDevOpsProject(projectId, username string) (error, int) { + err := CheckProjectUserInRole(username, projectId, []string{ProjectOwner}) + if err != nil { + glog.Errorf("%+v", err) + return err, http.StatusForbidden + } + gojenkins := admin_jenkins.Client() + devopsdb := devops_mysql.OpenDatabase() + _, err = gojenkins.DeleteJob(projectId) + + if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound { + glog.Errorf("%+v", err) + return err, utils.GetJenkinsStatusCode(err) + } + + roleNames := make([]string, 0) + for role := range JenkinsProjectPermissionMap { + roleNames = append(roleNames, GetProjectRoleName(projectId, role)) + roleNames = append(roleNames, GetPipelineRoleName(projectId, role)) + } + err = gojenkins.DeleteProjectRoles(roleNames...) + if err != nil { + glog.Errorf("%+v", err) + return err, utils.GetJenkinsStatusCode(err) + } + _, err = devopsdb.DeleteFrom(devops.DevOpsProjectMembershipTableName). + Where(db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId)).Exec() + if err != nil { + glog.Errorf("%+v", err) + return err, http.StatusInternalServerError + } + _, err = devopsdb.Update(devops.DevOpsProjectTableName). + Set(devops.StatusColumn, devops.StatusDeleted). + Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)).Exec() + if err != nil { + glog.Errorf("%+v", err) + return err, http.StatusInternalServerError + } + project := &devops.DevOpsProject{} + err = devopsdb.Select(devops.DevOpsProjectColumns...). + From(devops.DevOpsProjectTableName). + Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)). + LoadOne(project) + if err != nil { + glog.Errorf("%+v", err) + return err, http.StatusInternalServerError + } + return nil, http.StatusOK +} + +func CreateDevopsProject(username string, workspace string, req *devops.DevOpsProject) (*devops.DevOpsProject, error, int) { + + jenkinsClient := admin_jenkins.Client() + devopsdb := devops_mysql.OpenDatabase() + project := devops.NewDevOpsProject(req.Name, req.Description, username, req.Extra, workspace) + _, err := jenkinsClient.CreateFolder(project.ProjectId, project.Description) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + + var addRoleCh = make(chan *DevOpsProjectRoleResponse, 8) + var addRoleWg sync.WaitGroup + for role, permission := range JenkinsProjectPermissionMap { + addRoleWg.Add(1) + go func(role string, permission gojenkins.ProjectPermissionIds) { + _, err := jenkinsClient.AddProjectRole(GetProjectRoleName(project.ProjectId, role), + GetProjectRolePattern(project.ProjectId), permission, true) + addRoleCh <- &DevOpsProjectRoleResponse{nil, err} + addRoleWg.Done() + }(role, permission) + } + for role, permission := range JenkinsPipelinePermissionMap { + addRoleWg.Add(1) + go func(role string, permission gojenkins.ProjectPermissionIds) { + _, err := jenkinsClient.AddProjectRole(GetPipelineRoleName(project.ProjectId, role), + GetPipelineRolePattern(project.ProjectId), permission, true) + addRoleCh <- &DevOpsProjectRoleResponse{nil, err} + addRoleWg.Done() + }(role, permission) + } + addRoleWg.Wait() + close(addRoleCh) + for addRoleResponse := range addRoleCh { + if addRoleResponse.Err != nil { + glog.Errorf("%+v", addRoleResponse.Err) + return nil, addRoleResponse.Err, utils.GetJenkinsStatusCode(addRoleResponse.Err) } } - return &models.PageableResponse{Items: result, TotalCount: len(projects)}, nil + globalRole, err := jenkinsClient.GetGlobalRole(devops.JenkinsAllUserRoleName) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + if globalRole == nil { + _, err := jenkinsClient.AddGlobalRole(devops.JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{ + GlobalRead: true, + }, true) + if err != nil { + glog.Error("failed to create jenkins global role") + return nil, err, utils.GetJenkinsStatusCode(err) + } + } + err = globalRole.AssignRole(username) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + + projectRole, err := jenkinsClient.GetProjectRole(GetProjectRoleName(project.ProjectId, ProjectOwner)) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + err = projectRole.AssignRole(username) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + + pipelineRole, err := jenkinsClient.GetProjectRole(GetPipelineRoleName(project.ProjectId, ProjectOwner)) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + err = pipelineRole.AssignRole(username) + if err != nil { + glog.Errorf("%+v", err) + return nil, err, utils.GetJenkinsStatusCode(err) + } + _, err = devopsdb.InsertInto(devops.DevOpsProjectTableName). + Columns(devops.DevOpsProjectColumns...).Record(project).Exec() + if err != nil { + glog.Errorf("%+v", err) + return nil, err, http.StatusInternalServerError + } + + projectMembership := devops.NewDevOpsProjectMemberShip(username, project.ProjectId, ProjectOwner, username) + _, err = devopsdb.InsertInto(devops.DevOpsProjectMembershipTableName). + Columns(devops.DevOpsProjectMembershipColumns...).Record(projectMembership).Exec() + if err != nil { + glog.Errorf("%+v", err) + return nil, err, http.StatusInternalServerError + } + return project, nil, http.StatusOK } diff --git a/pkg/models/workspaces/workspaces.go b/pkg/models/workspaces/workspaces.go index 89305defd..168c9c187 100644 --- a/pkg/models/workspaces/workspaces.go +++ b/pkg/models/workspaces/workspaces.go @@ -21,17 +21,17 @@ import ( "fmt" "k8s.io/apimachinery/pkg/runtime/schema" "kubesphere.io/kubesphere/pkg/constants" + "kubesphere.io/kubesphere/pkg/db" "kubesphere.io/kubesphere/pkg/informers" "kubesphere.io/kubesphere/pkg/models" + "kubesphere.io/kubesphere/pkg/models/devops" "kubesphere.io/kubesphere/pkg/models/iam" "kubesphere.io/kubesphere/pkg/models/resources" "kubesphere.io/kubesphere/pkg/params" + "kubesphere.io/kubesphere/pkg/simple/client/devops_mysql" "kubesphere.io/kubesphere/pkg/simple/client/k8s" - "kubesphere.io/kubesphere/pkg/simple/client/kubesphere" - "kubesphere.io/kubesphere/pkg/simple/client/mysql" "kubesphere.io/kubesphere/pkg/utils/k8sutil" "kubesphere.io/kubesphere/pkg/utils/sliceutil" - "strings" core "k8s.io/api/core/v1" @@ -43,28 +43,6 @@ import ( "k8s.io/apimachinery/pkg/labels" ) -func UnBindDevopsProject(workspace string, devops string) error { - db := mysql.Client() - return db.Delete(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error -} - -func CreateDevopsProject(username string, workspace string, devops *models.DevopsProject) (*models.DevopsProject, error) { - - created, err := kubesphere.Client().CreateDevopsProject(username, devops) - - if err != nil { - return nil, err - } - - err = BindingDevopsProject(workspace, created.ProjectId) - - if err != nil { - return nil, err - } - - return created, nil -} - func Namespaces(workspaceName string) ([]*core.Namespace, error) { namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister() namespaces, err := namespaceLister.List(labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: workspaceName})) @@ -86,11 +64,6 @@ func Namespaces(workspaceName string) ([]*core.Namespace, error) { return out, nil } -func BindingDevopsProject(workspace string, devops string) error { - db := mysql.Client() - return db.Create(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error -} - func DeleteNamespace(workspace string, namespaceName string) error { namespace, err := k8s.Client().CoreV1().Namespaces().Get(namespaceName, meta_v1.GetOptions{}) if err != nil { @@ -184,18 +157,17 @@ func DeleteWorkspaceRoleBinding(workspace, username string, role string) error { func GetDevOpsProjects(workspaceName string) ([]string, error) { - db := mysql.Client() + dbconn := devops_mysql.OpenDatabase() - var workspaceDOPBindings []models.WorkspaceDPBinding - - if err := db.Where("workspace = ?", workspaceName).Find(&workspaceDOPBindings).Error; err != nil { - return nil, err - } + query := dbconn.Select(devops.DevOpsProjectIdColumn). + From(devops.DevOpsProjectTableName). + Where(db.And(db.Eq(devops.DevOpsProjectWorkSpaceColumn, workspaceName), + db.Eq(devops.StatusColumn, devops.StatusActive))) devOpsProjects := make([]string, 0) - for _, workspaceDOPBinding := range workspaceDOPBindings { - devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject) + if _, err := query.Load(&devOpsProjects); err != nil { + return nil, err } return devOpsProjects, nil } @@ -249,12 +221,18 @@ func GetAllProjectNums() (int, error) { } func GetAllDevOpsProjectsNums() (int, error) { - db := mysql.Client() - var count int - if err := db.Model(&models.WorkspaceDPBinding{}).Count(&count).Error; err != nil { + dbconn := devops_mysql.OpenDatabase() + + query := dbconn.Select(devops.DevOpsProjectIdColumn). + From(devops.DevOpsProjectTableName). + Where(db.Eq(devops.StatusColumn, devops.StatusActive)) + + devOpsProjects := make([]string, 0) + + if _, err := query.Load(&devOpsProjects); err != nil { return 0, err } - return count, nil + return len(devOpsProjects), nil } func GetAllAccountNums() (int, error) { diff --git a/pkg/simple/client/admin_jenkins/jenkins.go b/pkg/simple/client/admin_jenkins/jenkins.go new file mode 100644 index 000000000..9609af0fe --- /dev/null +++ b/pkg/simple/client/admin_jenkins/jenkins.go @@ -0,0 +1,63 @@ +package admin_jenkins + +import ( + "flag" + "github.com/golang/glog" + "kubesphere.io/kubesphere/pkg/gojenkins" + "sync" +) + +var ( + jenkinsClientOnce sync.Once + jenkinsClient *gojenkins.Jenkins + jenkinsAdminAddress string + jenkinsAdminUsername string + jenkinsAdminPassword string + jenkinsMaxConn int +) + +const ( + JenkinsAllUserRoleName = "kubesphere-user" +) + +func init() { + flag.StringVar(&jenkinsAdminAddress, "jenkins-address", "http://ks-jenkins.kubesphere-devops-system.svc/", "data source name") + flag.StringVar(&jenkinsAdminUsername, "jenkins-username", "admin", "username of jenkins") + flag.StringVar(&jenkinsAdminPassword, "jenkins-password", "passw0rd", "password of jenkins") + flag.IntVar(&jenkinsMaxConn, "jenkins-max-conn", 20, "max conn to jenkins") +} + +func Client() *gojenkins.Jenkins { + jenkinsClientOnce.Do(func() { + jenkins := gojenkins.CreateJenkins(nil, jenkinsAdminAddress, jenkinsMaxConn, jenkinsAdminUsername, jenkinsAdminPassword) + jenkins, err := jenkins.Init() + if err != nil { + glog.Error("failed to connect jenkins") + return + } + jenkinsClient = jenkins + globalRole, err := jenkins.GetGlobalRole(JenkinsAllUserRoleName) + if err != nil { + glog.Error("failed to get jenkins role") + } + if globalRole == nil { + _, err := jenkins.AddGlobalRole(JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{ + GlobalRead: true, + }, true) + if err != nil { + glog.Error("failed to create jenkins global role") + return + } + } + _, err = jenkins.AddProjectRole(JenkinsAllUserRoleName, "\\n\\s*\\r", gojenkins.ProjectPermissionIds{ + SCMTag: true, + }, true) + if err != nil { + glog.Error("failed to create jenkins project role") + return + } + }) + + return jenkinsClient + +} diff --git a/pkg/simple/client/devops_mysql/mysql.go b/pkg/simple/client/devops_mysql/mysql.go new file mode 100644 index 000000000..ce441bb9c --- /dev/null +++ b/pkg/simple/client/devops_mysql/mysql.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package devops_mysql + +import ( + "flag" + "github.com/gocraft/dbr" + "github.com/golang/glog" + + "kubesphere.io/kubesphere/pkg/db" + "sync" + "time" +) + +var ( + dbClientOnce sync.Once + dsn string + dbClient *db.Database +) + +func init() { + flag.StringVar(&dsn, "devops-database-connection", "root@tcp(127.0.0.1:3306)/devops", "data source name") +} + +var defaultEventReceiver = db.EventReceiver{} + +func OpenDatabase() *db.Database { + dbClientOnce.Do(func() { + conn, err := dbr.Open("mysql", dsn+"?parseTime=1&multiStatements=1&charset=utf8mb4&collation=utf8mb4_unicode_ci", &defaultEventReceiver) + if err != nil { + glog.Fatal(err) + } + conn.SetMaxIdleConns(100) + conn.SetMaxOpenConns(100) + conn.SetConnMaxLifetime(10 * time.Second) + dbClient = &db.Database{ + Session: conn.NewSession(nil), + } + err = dbClient.Ping() + if err != nil { + glog.Error(err) + } + }) + return dbClient +} diff --git a/pkg/simple/client/mysql/dbclient.go b/pkg/simple/client/mysql/dbclient.go deleted file mode 100644 index f4934469e..000000000 --- a/pkg/simple/client/mysql/dbclient.go +++ /dev/null @@ -1,60 +0,0 @@ -/* - - Copyright 2019 The KubeSphere Authors. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - -*/ - -package mysql - -import ( - "flag" - "log" - "os" - "os/signal" - "sync" - "syscall" - - _ "github.com/go-sql-driver/mysql" - "github.com/jinzhu/gorm" -) - -var ( - dbClientOnce sync.Once - dbClient *gorm.DB - dsn string -) - -func init() { - flag.StringVar(&dsn, "database-connection", "root@tcp(localhost:3306)/kubesphere?charset=utf8&parseTime=True", "data source name") -} - -func Client() *gorm.DB { - dbClientOnce.Do(func() { - var err error - dbClient, err = gorm.Open("mysql", dsn) - if err != nil { - log.Fatalln(err) - } - c := make(chan os.Signal, 0) - signal.Notify(c, os.Interrupt, syscall.SIGTERM) - go func() { - <-c - dbClient.Close() - }() - }) - - return dbClient - -} diff --git a/pkg/simple/client/sonarqube/sonar.go b/pkg/simple/client/sonarqube/sonar.go new file mode 100644 index 000000000..20664337e --- /dev/null +++ b/pkg/simple/client/sonarqube/sonar.go @@ -0,0 +1,49 @@ +package sonarqube + +import ( + "flag" + "github.com/golang/glog" + "github.com/kubesphere/sonargo/sonar" + "strings" + "sync" +) + +var ( + sonarAddress string + sonarToken string + sonarOnce sync.Once + sonarClient *sonargo.Client +) + +func init() { + flag.StringVar(&sonarAddress, "sonar-address", "", "sonar server host") + flag.StringVar(&sonarToken, "sonar-token", "", "sonar token") +} + +func Client() *sonargo.Client { + + sonarOnce.Do(func() { + if sonarAddress == "" { + sonarClient = nil + glog.Info("skip sonar init") + return + } + if !strings.HasSuffix(sonarAddress, "/") { + sonarAddress += "/" + } + client, err := sonargo.NewClientWithToken(sonarAddress+"api/", sonarToken) + if err != nil { + glog.Error("failed to connect to sonar") + return + } + _, _, err = client.Projects.Search(nil) + if err != nil { + glog.Errorf("failed to search sonar projects [%+v]", err) + return + } + glog.Info("init sonar client success") + sonarClient = client + }) + + return sonarClient +} diff --git a/pkg/utils/idutils/id_utils.go b/pkg/utils/idutils/id_utils.go new file mode 100644 index 000000000..6afc0216c --- /dev/null +++ b/pkg/utils/idutils/id_utils.go @@ -0,0 +1,94 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package idutils + +import ( + "errors" + "net" + "os" + + "github.com/golang/example/stringutil" + "github.com/sony/sonyflake" + hashids "github.com/speps/go-hashids" + + "kubesphere.io/kubesphere/pkg/utils/stringutils" +) + +var sf *sonyflake.Sonyflake + +func init() { + var st sonyflake.Settings + if len(os.Getenv("DEVOPSPHERE_IP")) != 0 { + st.MachineID = machineID + } + sf = sonyflake.NewSonyflake(st) + if sf == nil { + panic("failed to initialize sonyflake") + } +} + +func GetIntId() uint64 { + id, err := sf.NextID() + if err != nil { + panic(err) + } + return id +} + +// format likes: B6BZVN3mOPvx +func GetUuid(prefix string) string { + id := GetIntId() + hd := hashids.NewData() + h, err := hashids.NewWithData(hd) + if err != nil { + panic(err) + } + i, err := h.Encode([]int{int(id)}) + if err != nil { + panic(err) + } + + return prefix + stringutils.Reverse(i) +} + +const Alphabet36 = "abcdefghijklmnopqrstuvwxyz1234567890" + +// format likes: 300m50zn91nwz5 +func GetUuid36(prefix string) string { + id := GetIntId() + hd := hashids.NewData() + hd.Alphabet = Alphabet36 + h, err := hashids.NewWithData(hd) + if err != nil { + panic(err) + } + i, err := h.Encode([]int{int(id)}) + if err != nil { + panic(err) + } + + return prefix + stringutil.Reverse(i) +} + +func machineID() (uint16, error) { + ipStr := os.Getenv("DEVOPSPHERE_IP") + if len(ipStr) == 0 { + return 0, errors.New("'DEVOPSPHERE_IP' environment variable not set") + } + ip := net.ParseIP(ipStr) + if len(ip) < 4 { + return 0, errors.New("invalid IP") + } + return uint16(ip[2])<<8 + uint16(ip[3]), nil +} diff --git a/pkg/utils/idutils/id_utils_test.go b/pkg/utils/idutils/id_utils_test.go new file mode 100644 index 000000000..5cb25562c --- /dev/null +++ b/pkg/utils/idutils/id_utils_test.go @@ -0,0 +1,24 @@ +package idutils + +import ( + "fmt" + "sort" + "testing" +) + +func TestGetUuid(t *testing.T) { + fmt.Println(GetUuid("")) +} + +func TestGetUuid36(t *testing.T) { + fmt.Println(GetUuid36("")) +} + +func TestGetManyUuid(t *testing.T) { + var strSlice []string + for i := 0; i < 10000; i++ { + testId := GetUuid("") + strSlice = append(strSlice, testId) + } + sort.Strings(strSlice) +} diff --git a/pkg/utils/reflectutils/reflect.go b/pkg/utils/reflectutils/reflect.go new file mode 100644 index 000000000..9d8e1269e --- /dev/null +++ b/pkg/utils/reflectutils/reflect.go @@ -0,0 +1,35 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package reflectutils + +import "reflect" + +func In(value interface{}, container interface{}) bool { + containerValue := reflect.ValueOf(container) + switch reflect.TypeOf(container).Kind() { + case reflect.Slice, reflect.Array: + for i := 0; i < containerValue.Len(); i++ { + if containerValue.Index(i).Interface() == value { + return true + } + } + case reflect.Map: + if containerValue.MapIndex(reflect.ValueOf(value)).IsValid() { + return true + } + default: + return false + } + return false +} diff --git a/pkg/utils/stringutils/string.go b/pkg/utils/stringutils/string.go new file mode 100644 index 000000000..ff391b70a --- /dev/null +++ b/pkg/utils/stringutils/string.go @@ -0,0 +1,77 @@ +/* +Copyright 2018 The KubeSphere Authors. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package stringutils + +import ( + "unicode/utf8" + + "github.com/asaskevich/govalidator" +) + +// Creates an slice of slice values not included in the other given slice. +func Diff(base, exclude []string) (result []string) { + excludeMap := make(map[string]bool) + for _, s := range exclude { + excludeMap[s] = true + } + for _, s := range base { + if !excludeMap[s] { + result = append(result, s) + } + } + return result +} + +func Unique(ss []string) (result []string) { + smap := make(map[string]bool) + for _, s := range ss { + smap[s] = true + } + for s := range smap { + result = append(result, s) + } + return result +} + +func CamelCaseToUnderscore(str string) string { + return govalidator.CamelCaseToUnderscore(str) +} + +func UnderscoreToCamelCase(str string) string { + return govalidator.UnderscoreToCamelCase(str) +} + +func FindString(array []string, str string) int { + for index, s := range array { + if str == s { + return index + } + } + return -1 +} + +func StringIn(str string, array []string) bool { + return FindString(array, str) > -1 +} + +func Reverse(s string) string { + size := len(s) + buf := make([]byte, size) + for start := 0; start < size; { + r, n := utf8.DecodeRuneInString(s[start:]) + start += n + utf8.EncodeRune(buf[size-start:], r) + } + return string(buf) +} diff --git a/vendor/github.com/asaskevich/govalidator/LICENSE b/vendor/github.com/asaskevich/govalidator/LICENSE new file mode 100644 index 000000000..2f9a31fad --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Alex Saskevich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/asaskevich/govalidator/arrays.go b/vendor/github.com/asaskevich/govalidator/arrays.go new file mode 100644 index 000000000..5bace2654 --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/arrays.go @@ -0,0 +1,58 @@ +package govalidator + +// Iterator is the function that accepts element of slice/array and its index +type Iterator func(interface{}, int) + +// ResultIterator is the function that accepts element of slice/array and its index and returns any result +type ResultIterator func(interface{}, int) interface{} + +// ConditionIterator is the function that accepts element of slice/array and its index and returns boolean +type ConditionIterator func(interface{}, int) bool + +// Each iterates over the slice and apply Iterator to every item +func Each(array []interface{}, iterator Iterator) { + for index, data := range array { + iterator(data, index) + } +} + +// Map iterates over the slice and apply ResultIterator to every item. Returns new slice as a result. +func Map(array []interface{}, iterator ResultIterator) []interface{} { + var result = make([]interface{}, len(array)) + for index, data := range array { + result[index] = iterator(data, index) + } + return result +} + +// Find iterates over the slice and apply ConditionIterator to every item. Returns first item that meet ConditionIterator or nil otherwise. +func Find(array []interface{}, iterator ConditionIterator) interface{} { + for index, data := range array { + if iterator(data, index) { + return data + } + } + return nil +} + +// Filter iterates over the slice and apply ConditionIterator to every item. Returns new slice. +func Filter(array []interface{}, iterator ConditionIterator) []interface{} { + var result = make([]interface{}, 0) + for index, data := range array { + if iterator(data, index) { + result = append(result, data) + } + } + return result +} + +// Count iterates over the slice and apply ConditionIterator to every item. Returns count of items that meets ConditionIterator. +func Count(array []interface{}, iterator ConditionIterator) int { + count := 0 + for index, data := range array { + if iterator(data, index) { + count = count + 1 + } + } + return count +} diff --git a/vendor/github.com/asaskevich/govalidator/converter.go b/vendor/github.com/asaskevich/govalidator/converter.go new file mode 100644 index 000000000..cf1e5d569 --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/converter.go @@ -0,0 +1,64 @@ +package govalidator + +import ( + "encoding/json" + "fmt" + "reflect" + "strconv" +) + +// ToString convert the input to a string. +func ToString(obj interface{}) string { + res := fmt.Sprintf("%v", obj) + return string(res) +} + +// ToJSON convert the input to a valid JSON string +func ToJSON(obj interface{}) (string, error) { + res, err := json.Marshal(obj) + if err != nil { + res = []byte("") + } + return string(res), err +} + +// ToFloat convert the input string to a float, or 0.0 if the input is not a float. +func ToFloat(str string) (float64, error) { + res, err := strconv.ParseFloat(str, 64) + if err != nil { + res = 0.0 + } + return res, err +} + +// ToInt convert the input string or any int type to an integer type 64, or 0 if the input is not an integer. +func ToInt(value interface{}) (res int64, err error) { + val := reflect.ValueOf(value) + + switch value.(type) { + case int, int8, int16, int32, int64: + res = val.Int() + case uint, uint8, uint16, uint32, uint64: + res = int64(val.Uint()) + case string: + if IsInt(val.String()) { + res, err = strconv.ParseInt(val.String(), 0, 64) + if err != nil { + res = 0 + } + } else { + err = fmt.Errorf("math: square root of negative number %g", value) + res = 0 + } + default: + err = fmt.Errorf("math: square root of negative number %g", value) + res = 0 + } + + return +} + +// ToBoolean convert the input string to a boolean. +func ToBoolean(str string) (bool, error) { + return strconv.ParseBool(str) +} diff --git a/vendor/github.com/asaskevich/govalidator/error.go b/vendor/github.com/asaskevich/govalidator/error.go new file mode 100644 index 000000000..b9c32079b --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/error.go @@ -0,0 +1,36 @@ +package govalidator + +import "strings" + +// Errors is an array of multiple errors and conforms to the error interface. +type Errors []error + +// Errors returns itself. +func (es Errors) Errors() []error { + return es +} + +func (es Errors) Error() string { + var errs []string + for _, e := range es { + errs = append(errs, e.Error()) + } + return strings.Join(errs, ";") +} + +// Error encapsulates a name, an error and whether there's a custom error message or not. +type Error struct { + Name string + Err error + CustomErrorMessageExists bool + + // Validator indicates the name of the validator that failed + Validator string +} + +func (e Error) Error() string { + if e.CustomErrorMessageExists { + return e.Err.Error() + } + return e.Name + ": " + e.Err.Error() +} diff --git a/vendor/github.com/asaskevich/govalidator/numerics.go b/vendor/github.com/asaskevich/govalidator/numerics.go new file mode 100644 index 000000000..7e6c652e1 --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/numerics.go @@ -0,0 +1,97 @@ +package govalidator + +import ( + "math" + "reflect" +) + +// Abs returns absolute value of number +func Abs(value float64) float64 { + return math.Abs(value) +} + +// Sign returns signum of number: 1 in case of value > 0, -1 in case of value < 0, 0 otherwise +func Sign(value float64) float64 { + if value > 0 { + return 1 + } else if value < 0 { + return -1 + } else { + return 0 + } +} + +// IsNegative returns true if value < 0 +func IsNegative(value float64) bool { + return value < 0 +} + +// IsPositive returns true if value > 0 +func IsPositive(value float64) bool { + return value > 0 +} + +// IsNonNegative returns true if value >= 0 +func IsNonNegative(value float64) bool { + return value >= 0 +} + +// IsNonPositive returns true if value <= 0 +func IsNonPositive(value float64) bool { + return value <= 0 +} + +// InRange returns true if value lies between left and right border +func InRangeInt(value, left, right interface{}) bool { + value64, _ := ToInt(value) + left64, _ := ToInt(left) + right64, _ := ToInt(right) + if left64 > right64 { + left64, right64 = right64, left64 + } + return value64 >= left64 && value64 <= right64 +} + +// InRange returns true if value lies between left and right border +func InRangeFloat32(value, left, right float32) bool { + if left > right { + left, right = right, left + } + return value >= left && value <= right +} + +// InRange returns true if value lies between left and right border +func InRangeFloat64(value, left, right float64) bool { + if left > right { + left, right = right, left + } + return value >= left && value <= right +} + +// InRange returns true if value lies between left and right border, generic type to handle int, float32 or float64, all types must the same type +func InRange(value interface{}, left interface{}, right interface{}) bool { + + reflectValue := reflect.TypeOf(value).Kind() + reflectLeft := reflect.TypeOf(left).Kind() + reflectRight := reflect.TypeOf(right).Kind() + + if reflectValue == reflect.Int && reflectLeft == reflect.Int && reflectRight == reflect.Int { + return InRangeInt(value.(int), left.(int), right.(int)) + } else if reflectValue == reflect.Float32 && reflectLeft == reflect.Float32 && reflectRight == reflect.Float32 { + return InRangeFloat32(value.(float32), left.(float32), right.(float32)) + } else if reflectValue == reflect.Float64 && reflectLeft == reflect.Float64 && reflectRight == reflect.Float64 { + return InRangeFloat64(value.(float64), left.(float64), right.(float64)) + } else { + return false + } +} + +// IsWhole returns true if value is whole number +func IsWhole(value float64) bool { + return math.Remainder(value, 1) == 0 +} + +// IsNatural returns true if value is natural number (positive and whole) +func IsNatural(value float64) bool { + return IsWhole(value) && IsPositive(value) +} diff --git a/vendor/github.com/asaskevich/govalidator/patterns.go b/vendor/github.com/asaskevich/govalidator/patterns.go new file mode 100644 index 000000000..8609cd22f --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/patterns.go @@ -0,0 +1,97 @@ +package govalidator + +import "regexp" + +// Basic regular expressions for validating strings +const ( + //Email string = "^(((([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|\\.|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])([a-zA-Z]|\\d|-|_|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*([a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$" + CreditCard string = "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$" + ISBN10 string = "^(?:[0-9]{9}X|[0-9]{10})$" + ISBN13 string = "^(?:[0-9]{13})$" + UUID3 string = "^[0-9a-f]{8}-[0-9a-f]{4}-3[0-9a-f]{3}-[0-9a-f]{4}-[0-9a-f]{12}$" + UUID4 string = "^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + UUID5 string = "^[0-9a-f]{8}-[0-9a-f]{4}-5[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" + UUID string = "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" + Alpha string = "^[a-zA-Z]+$" + Alphanumeric string = "^[a-zA-Z0-9]+$" + Numeric string = "^[0-9]+$" + Int string = "^(?:[-+]?(?:0|[1-9][0-9]*))$" + Float string = "^(?:[-+]?(?:[0-9]+))?(?:\\.[0-9]*)?(?:[eE][\\+\\-]?(?:[0-9]+))?$" + Hexadecimal string = "^[0-9a-fA-F]+$" + Hexcolor string = "^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$" + RGBcolor string = "^rgb\\(\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*,\\s*(0|[1-9]\\d?|1\\d\\d?|2[0-4]\\d|25[0-5])\\s*\\)$" + ASCII string = "^[\x00-\x7F]+$" + Multibyte string = "[^\x00-\x7F]" + FullWidth string = "[^\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" + HalfWidth string = "[\u0020-\u007E\uFF61-\uFF9F\uFFA0-\uFFDC\uFFE8-\uFFEE0-9a-zA-Z]" + Base64 string = "^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$" + PrintableASCII string = "^[\x20-\x7E]+$" + DataURI string = "^data:.+\\/(.+);base64$" + Latitude string = "^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$" + Longitude string = "^[-+]?(180(\\.0+)?|((1[0-7]\\d)|([1-9]?\\d))(\\.\\d+)?)$" + DNSName string = `^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$` + IP string = `(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))` + URLSchema string = `((ftp|tcp|udp|wss?|https?):\/\/)` + URLUsername string = `(\S+(:\S*)?@)` + URLPath string = `((\/|\?|#)[^\s]*)` + URLPort string = `(:(\d{1,5}))` + URLIP string = `([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))` + URLSubdomain string = `((www\.)|([a-zA-Z0-9]([-\.][-\._a-zA-Z0-9]+)*))` + URL string = `^` + URLSchema + `?` + URLUsername + `?` + `((` + URLIP + `|(\[` + IP + `\])|(([a-zA-Z0-9]([a-zA-Z0-9-_]+)?[a-zA-Z0-9]([-\.][a-zA-Z0-9]+)*)|(` + URLSubdomain + `?))?(([a-zA-Z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-zA-Z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-zA-Z\x{00a1}-\x{ffff}]{1,}))?))\.?` + URLPort + `?` + URLPath + `?$` + SSN string = `^\d{3}[- ]?\d{2}[- ]?\d{4}$` + WinPath string = `^[a-zA-Z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*$` + UnixPath string = `^(/[^/\x00]*)+/?$` + Semver string = "^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$" + tagName string = "valid" + hasLowerCase string = ".*[[:lower:]]" + hasUpperCase string = ".*[[:upper:]]" +) + +// Used by IsFilePath func +const ( + // Unknown is unresolved OS type + Unknown = iota + // Win is Windows type + Win + // Unix is *nix OS types + Unix +) + +var ( + userRegexp = regexp.MustCompile("^[a-zA-Z0-9!#$%&'*+/=?^_`{|}~.-]+$") + hostRegexp = regexp.MustCompile("^[^\\s]+\\.[^\\s]+$") + userDotRegexp = regexp.MustCompile("(^[.]{1})|([.]{1}$)|([.]{2,})") + //rxEmail = regexp.MustCompile(Email) + rxCreditCard = regexp.MustCompile(CreditCard) + rxISBN10 = regexp.MustCompile(ISBN10) + rxISBN13 = regexp.MustCompile(ISBN13) + rxUUID3 = regexp.MustCompile(UUID3) + rxUUID4 = regexp.MustCompile(UUID4) + rxUUID5 = regexp.MustCompile(UUID5) + rxUUID = regexp.MustCompile(UUID) + rxAlpha = regexp.MustCompile(Alpha) + rxAlphanumeric = regexp.MustCompile(Alphanumeric) + rxNumeric = regexp.MustCompile(Numeric) + rxInt = regexp.MustCompile(Int) + rxFloat = regexp.MustCompile(Float) + rxHexadecimal = regexp.MustCompile(Hexadecimal) + rxHexcolor = regexp.MustCompile(Hexcolor) + rxRGBcolor = regexp.MustCompile(RGBcolor) + rxASCII = regexp.MustCompile(ASCII) + rxPrintableASCII = regexp.MustCompile(PrintableASCII) + rxMultibyte = regexp.MustCompile(Multibyte) + rxFullWidth = regexp.MustCompile(FullWidth) + rxHalfWidth = regexp.MustCompile(HalfWidth) + rxBase64 = regexp.MustCompile(Base64) + rxDataURI = regexp.MustCompile(DataURI) + rxLatitude = regexp.MustCompile(Latitude) + rxLongitude = regexp.MustCompile(Longitude) + rxDNSName = regexp.MustCompile(DNSName) + rxURL = regexp.MustCompile(URL) + rxSSN = regexp.MustCompile(SSN) + rxWinPath = regexp.MustCompile(WinPath) + rxUnixPath = regexp.MustCompile(UnixPath) + rxSemver = regexp.MustCompile(Semver) + rxHasLowerCase = regexp.MustCompile(hasLowerCase) + rxHasUpperCase = regexp.MustCompile(hasUpperCase) +) diff --git a/vendor/github.com/asaskevich/govalidator/types.go b/vendor/github.com/asaskevich/govalidator/types.go new file mode 100644 index 000000000..ddd30b122 --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/types.go @@ -0,0 +1,616 @@ +package govalidator + +import ( + "reflect" + "regexp" + "sync" +) + +// Validator is a wrapper for a validator function that returns bool and accepts string. +type Validator func(str string) bool + +// CustomTypeValidator is a wrapper for validator functions that returns bool and accepts any type. +// The second parameter should be the context (in the case of validating a struct: the whole object being validated). +type CustomTypeValidator func(i interface{}, o interface{}) bool + +// ParamValidator is a wrapper for validator functions that accepts additional parameters. +type ParamValidator func(str string, params ...string) bool +type tagOptionsMap map[string]string + +// UnsupportedTypeError is a wrapper for reflect.Type +type UnsupportedTypeError struct { + Type reflect.Type +} + +// stringValues is a slice of reflect.Value holding *reflect.StringValue. +// It implements the methods to sort by string. +type stringValues []reflect.Value + +// ParamTagMap is a map of functions accept variants parameters +var ParamTagMap = map[string]ParamValidator{ + "length": ByteLength, + "range": Range, + "runelength": RuneLength, + "stringlength": StringLength, + "matches": StringMatches, + "in": isInRaw, + "rsapub": IsRsaPub, +} + +// ParamTagRegexMap maps param tags to their respective regexes. +var ParamTagRegexMap = map[string]*regexp.Regexp{ + "range": regexp.MustCompile("^range\\((\\d+)\\|(\\d+)\\)$"), + "length": regexp.MustCompile("^length\\((\\d+)\\|(\\d+)\\)$"), + "runelength": regexp.MustCompile("^runelength\\((\\d+)\\|(\\d+)\\)$"), + "stringlength": regexp.MustCompile("^stringlength\\((\\d+)\\|(\\d+)\\)$"), + "in": regexp.MustCompile(`^in\((.*)\)`), + "matches": regexp.MustCompile(`^matches\((.+)\)$`), + "rsapub": regexp.MustCompile("^rsapub\\((\\d+)\\)$"), +} + +type customTypeTagMap struct { + validators map[string]CustomTypeValidator + + sync.RWMutex +} + +func (tm *customTypeTagMap) Get(name string) (CustomTypeValidator, bool) { + tm.RLock() + defer tm.RUnlock() + v, ok := tm.validators[name] + return v, ok +} + +func (tm *customTypeTagMap) Set(name string, ctv CustomTypeValidator) { + tm.Lock() + defer tm.Unlock() + tm.validators[name] = ctv +} + +// CustomTypeTagMap is a map of functions that can be used as tags for ValidateStruct function. +// Use this to validate compound or custom types that need to be handled as a whole, e.g. +// `type UUID [16]byte` (this would be handled as an array of bytes). +var CustomTypeTagMap = &customTypeTagMap{validators: make(map[string]CustomTypeValidator)} + +// TagMap is a map of functions, that can be used as tags for ValidateStruct function. +var TagMap = map[string]Validator{ + "email": IsEmail, + "url": IsURL, + "dialstring": IsDialString, + "requrl": IsRequestURL, + "requri": IsRequestURI, + "alpha": IsAlpha, + "utfletter": IsUTFLetter, + "alphanum": IsAlphanumeric, + "utfletternum": IsUTFLetterNumeric, + "numeric": IsNumeric, + "utfnumeric": IsUTFNumeric, + "utfdigit": IsUTFDigit, + "hexadecimal": IsHexadecimal, + "hexcolor": IsHexcolor, + "rgbcolor": IsRGBcolor, + "lowercase": IsLowerCase, + "uppercase": IsUpperCase, + "int": IsInt, + "float": IsFloat, + "null": IsNull, + "uuid": IsUUID, + "uuidv3": IsUUIDv3, + "uuidv4": IsUUIDv4, + "uuidv5": IsUUIDv5, + "creditcard": IsCreditCard, + "isbn10": IsISBN10, + "isbn13": IsISBN13, + "json": IsJSON, + "multibyte": IsMultibyte, + "ascii": IsASCII, + "printableascii": IsPrintableASCII, + "fullwidth": IsFullWidth, + "halfwidth": IsHalfWidth, + "variablewidth": IsVariableWidth, + "base64": IsBase64, + "datauri": IsDataURI, + "ip": IsIP, + "port": IsPort, + "ipv4": IsIPv4, + "ipv6": IsIPv6, + "dns": IsDNSName, + "host": IsHost, + "mac": IsMAC, + "latitude": IsLatitude, + "longitude": IsLongitude, + "ssn": IsSSN, + "semver": IsSemver, + "rfc3339": IsRFC3339, + "rfc3339WithoutZone": IsRFC3339WithoutZone, + "ISO3166Alpha2": IsISO3166Alpha2, + "ISO3166Alpha3": IsISO3166Alpha3, + "ISO4217": IsISO4217, +} + +// ISO3166Entry stores country codes +type ISO3166Entry struct { + EnglishShortName string + FrenchShortName string + Alpha2Code string + Alpha3Code string + Numeric string +} + +//ISO3166List based on https://www.iso.org/obp/ui/#search/code/ Code Type "Officially Assigned Codes" +var ISO3166List = []ISO3166Entry{ + {"Afghanistan", "Afghanistan (l')", "AF", "AFG", "004"}, + {"Albania", "Albanie (l')", "AL", "ALB", "008"}, + {"Antarctica", "Antarctique (l')", "AQ", "ATA", "010"}, + {"Algeria", "Algérie (l')", "DZ", "DZA", "012"}, + {"American Samoa", "Samoa américaines (les)", "AS", "ASM", "016"}, + {"Andorra", "Andorre (l')", "AD", "AND", "020"}, + {"Angola", "Angola (l')", "AO", "AGO", "024"}, + {"Antigua and Barbuda", "Antigua-et-Barbuda", "AG", "ATG", "028"}, + {"Azerbaijan", "Azerbaïdjan (l')", "AZ", "AZE", "031"}, + {"Argentina", "Argentine (l')", "AR", "ARG", "032"}, + {"Australia", "Australie (l')", "AU", "AUS", "036"}, + {"Austria", "Autriche (l')", "AT", "AUT", "040"}, + {"Bahamas (the)", "Bahamas (les)", "BS", "BHS", "044"}, + {"Bahrain", "Bahreïn", "BH", "BHR", "048"}, + {"Bangladesh", "Bangladesh (le)", "BD", "BGD", "050"}, + {"Armenia", "Arménie (l')", "AM", "ARM", "051"}, + {"Barbados", "Barbade (la)", "BB", "BRB", "052"}, + {"Belgium", "Belgique (la)", "BE", "BEL", "056"}, + {"Bermuda", "Bermudes (les)", "BM", "BMU", "060"}, + {"Bhutan", "Bhoutan (le)", "BT", "BTN", "064"}, + {"Bolivia (Plurinational State of)", "Bolivie (État plurinational de)", "BO", "BOL", "068"}, + {"Bosnia and Herzegovina", "Bosnie-Herzégovine (la)", "BA", "BIH", "070"}, + {"Botswana", "Botswana (le)", "BW", "BWA", "072"}, + {"Bouvet Island", "Bouvet (l'Île)", "BV", "BVT", "074"}, + {"Brazil", "Brésil (le)", "BR", "BRA", "076"}, + {"Belize", "Belize (le)", "BZ", "BLZ", "084"}, + {"British Indian Ocean Territory (the)", "Indien (le Territoire britannique de l'océan)", "IO", "IOT", "086"}, + {"Solomon Islands", "Salomon (Îles)", "SB", "SLB", "090"}, + {"Virgin Islands (British)", "Vierges britanniques (les Îles)", "VG", "VGB", "092"}, + {"Brunei Darussalam", "Brunéi Darussalam (le)", "BN", "BRN", "096"}, + {"Bulgaria", "Bulgarie (la)", "BG", "BGR", "100"}, + {"Myanmar", "Myanmar (le)", "MM", "MMR", "104"}, + {"Burundi", "Burundi (le)", "BI", "BDI", "108"}, + {"Belarus", "Bélarus (le)", "BY", "BLR", "112"}, + {"Cambodia", "Cambodge (le)", "KH", "KHM", "116"}, + {"Cameroon", "Cameroun (le)", "CM", "CMR", "120"}, + {"Canada", "Canada (le)", "CA", "CAN", "124"}, + {"Cabo Verde", "Cabo Verde", "CV", "CPV", "132"}, + {"Cayman Islands (the)", "Caïmans (les Îles)", "KY", "CYM", "136"}, + {"Central African Republic (the)", "République centrafricaine (la)", "CF", "CAF", "140"}, + {"Sri Lanka", "Sri Lanka", "LK", "LKA", "144"}, + {"Chad", "Tchad (le)", "TD", "TCD", "148"}, + {"Chile", "Chili (le)", "CL", "CHL", "152"}, + {"China", "Chine (la)", "CN", "CHN", "156"}, + {"Taiwan (Province of China)", "Taïwan (Province de Chine)", "TW", "TWN", "158"}, + {"Christmas Island", "Christmas (l'Île)", "CX", "CXR", "162"}, + {"Cocos (Keeling) Islands (the)", "Cocos (les Îles)/ Keeling (les Îles)", "CC", "CCK", "166"}, + {"Colombia", "Colombie (la)", "CO", "COL", "170"}, + {"Comoros (the)", "Comores (les)", "KM", "COM", "174"}, + {"Mayotte", "Mayotte", "YT", "MYT", "175"}, + {"Congo (the)", "Congo (le)", "CG", "COG", "178"}, + {"Congo (the Democratic Republic of the)", "Congo (la République démocratique du)", "CD", "COD", "180"}, + {"Cook Islands (the)", "Cook (les Îles)", "CK", "COK", "184"}, + {"Costa Rica", "Costa Rica (le)", "CR", "CRI", "188"}, + {"Croatia", "Croatie (la)", "HR", "HRV", "191"}, + {"Cuba", "Cuba", "CU", "CUB", "192"}, + {"Cyprus", "Chypre", "CY", "CYP", "196"}, + {"Czech Republic (the)", "tchèque (la République)", "CZ", "CZE", "203"}, + {"Benin", "Bénin (le)", "BJ", "BEN", "204"}, + {"Denmark", "Danemark (le)", "DK", "DNK", "208"}, + {"Dominica", "Dominique (la)", "DM", "DMA", "212"}, + {"Dominican Republic (the)", "dominicaine (la République)", "DO", "DOM", "214"}, + {"Ecuador", "Équateur (l')", "EC", "ECU", "218"}, + {"El Salvador", "El Salvador", "SV", "SLV", "222"}, + {"Equatorial Guinea", "Guinée équatoriale (la)", "GQ", "GNQ", "226"}, + {"Ethiopia", "Éthiopie (l')", "ET", "ETH", "231"}, + {"Eritrea", "Érythrée (l')", "ER", "ERI", "232"}, + {"Estonia", "Estonie (l')", "EE", "EST", "233"}, + {"Faroe Islands (the)", "Féroé (les Îles)", "FO", "FRO", "234"}, + {"Falkland Islands (the) [Malvinas]", "Falkland (les Îles)/Malouines (les Îles)", "FK", "FLK", "238"}, + {"South Georgia and the South Sandwich Islands", "Géorgie du Sud-et-les Îles Sandwich du Sud (la)", "GS", "SGS", "239"}, + {"Fiji", "Fidji (les)", "FJ", "FJI", "242"}, + {"Finland", "Finlande (la)", "FI", "FIN", "246"}, + {"Åland Islands", "Åland(les Îles)", "AX", "ALA", "248"}, + {"France", "France (la)", "FR", "FRA", "250"}, + {"French Guiana", "Guyane française (la )", "GF", "GUF", "254"}, + {"French Polynesia", "Polynésie française (la)", "PF", "PYF", "258"}, + {"French Southern Territories (the)", "Terres australes françaises (les)", "TF", "ATF", "260"}, + {"Djibouti", "Djibouti", "DJ", "DJI", "262"}, + {"Gabon", "Gabon (le)", "GA", "GAB", "266"}, + {"Georgia", "Géorgie (la)", "GE", "GEO", "268"}, + {"Gambia (the)", "Gambie (la)", "GM", "GMB", "270"}, + {"Palestine, State of", "Palestine, État de", "PS", "PSE", "275"}, + {"Germany", "Allemagne (l')", "DE", "DEU", "276"}, + {"Ghana", "Ghana (le)", "GH", "GHA", "288"}, + {"Gibraltar", "Gibraltar", "GI", "GIB", "292"}, + {"Kiribati", "Kiribati", "KI", "KIR", "296"}, + {"Greece", "Grèce (la)", "GR", "GRC", "300"}, + {"Greenland", "Groenland (le)", "GL", "GRL", "304"}, + {"Grenada", "Grenade (la)", "GD", "GRD", "308"}, + {"Guadeloupe", "Guadeloupe (la)", "GP", "GLP", "312"}, + {"Guam", "Guam", "GU", "GUM", "316"}, + {"Guatemala", "Guatemala (le)", "GT", "GTM", "320"}, + {"Guinea", "Guinée (la)", "GN", "GIN", "324"}, + {"Guyana", "Guyana (le)", "GY", "GUY", "328"}, + {"Haiti", "Haïti", "HT", "HTI", "332"}, + {"Heard Island and McDonald Islands", "Heard-et-Îles MacDonald (l'Île)", "HM", "HMD", "334"}, + {"Holy See (the)", "Saint-Siège (le)", "VA", "VAT", "336"}, + {"Honduras", "Honduras (le)", "HN", "HND", "340"}, + {"Hong Kong", "Hong Kong", "HK", "HKG", "344"}, + {"Hungary", "Hongrie (la)", "HU", "HUN", "348"}, + {"Iceland", "Islande (l')", "IS", "ISL", "352"}, + {"India", "Inde (l')", "IN", "IND", "356"}, + {"Indonesia", "Indonésie (l')", "ID", "IDN", "360"}, + {"Iran (Islamic Republic of)", "Iran (République Islamique d')", "IR", "IRN", "364"}, + {"Iraq", "Iraq (l')", "IQ", "IRQ", "368"}, + {"Ireland", "Irlande (l')", "IE", "IRL", "372"}, + {"Israel", "Israël", "IL", "ISR", "376"}, + {"Italy", "Italie (l')", "IT", "ITA", "380"}, + {"Côte d'Ivoire", "Côte d'Ivoire (la)", "CI", "CIV", "384"}, + {"Jamaica", "Jamaïque (la)", "JM", "JAM", "388"}, + {"Japan", "Japon (le)", "JP", "JPN", "392"}, + {"Kazakhstan", "Kazakhstan (le)", "KZ", "KAZ", "398"}, + {"Jordan", "Jordanie (la)", "JO", "JOR", "400"}, + {"Kenya", "Kenya (le)", "KE", "KEN", "404"}, + {"Korea (the Democratic People's Republic of)", "Corée (la République populaire démocratique de)", "KP", "PRK", "408"}, + {"Korea (the Republic of)", "Corée (la République de)", "KR", "KOR", "410"}, + {"Kuwait", "Koweït (le)", "KW", "KWT", "414"}, + {"Kyrgyzstan", "Kirghizistan (le)", "KG", "KGZ", "417"}, + {"Lao People's Democratic Republic (the)", "Lao, République démocratique populaire", "LA", "LAO", "418"}, + {"Lebanon", "Liban (le)", "LB", "LBN", "422"}, + {"Lesotho", "Lesotho (le)", "LS", "LSO", "426"}, + {"Latvia", "Lettonie (la)", "LV", "LVA", "428"}, + {"Liberia", "Libéria (le)", "LR", "LBR", "430"}, + {"Libya", "Libye (la)", "LY", "LBY", "434"}, + {"Liechtenstein", "Liechtenstein (le)", "LI", "LIE", "438"}, + {"Lithuania", "Lituanie (la)", "LT", "LTU", "440"}, + {"Luxembourg", "Luxembourg (le)", "LU", "LUX", "442"}, + {"Macao", "Macao", "MO", "MAC", "446"}, + {"Madagascar", "Madagascar", "MG", "MDG", "450"}, + {"Malawi", "Malawi (le)", "MW", "MWI", "454"}, + {"Malaysia", "Malaisie (la)", "MY", "MYS", "458"}, + {"Maldives", "Maldives (les)", "MV", "MDV", "462"}, + {"Mali", "Mali (le)", "ML", "MLI", "466"}, + {"Malta", "Malte", "MT", "MLT", "470"}, + {"Martinique", "Martinique (la)", "MQ", "MTQ", "474"}, + {"Mauritania", "Mauritanie (la)", "MR", "MRT", "478"}, + {"Mauritius", "Maurice", "MU", "MUS", "480"}, + {"Mexico", "Mexique (le)", "MX", "MEX", "484"}, + {"Monaco", "Monaco", "MC", "MCO", "492"}, + {"Mongolia", "Mongolie (la)", "MN", "MNG", "496"}, + {"Moldova (the Republic of)", "Moldova , République de", "MD", "MDA", "498"}, + {"Montenegro", "Monténégro (le)", "ME", "MNE", "499"}, + {"Montserrat", "Montserrat", "MS", "MSR", "500"}, + {"Morocco", "Maroc (le)", "MA", "MAR", "504"}, + {"Mozambique", "Mozambique (le)", "MZ", "MOZ", "508"}, + {"Oman", "Oman", "OM", "OMN", "512"}, + {"Namibia", "Namibie (la)", "NA", "NAM", "516"}, + {"Nauru", "Nauru", "NR", "NRU", "520"}, + {"Nepal", "Népal (le)", "NP", "NPL", "524"}, + {"Netherlands (the)", "Pays-Bas (les)", "NL", "NLD", "528"}, + {"Curaçao", "Curaçao", "CW", "CUW", "531"}, + {"Aruba", "Aruba", "AW", "ABW", "533"}, + {"Sint Maarten (Dutch part)", "Saint-Martin (partie néerlandaise)", "SX", "SXM", "534"}, + {"Bonaire, Sint Eustatius and Saba", "Bonaire, Saint-Eustache et Saba", "BQ", "BES", "535"}, + {"New Caledonia", "Nouvelle-Calédonie (la)", "NC", "NCL", "540"}, + {"Vanuatu", "Vanuatu (le)", "VU", "VUT", "548"}, + {"New Zealand", "Nouvelle-Zélande (la)", "NZ", "NZL", "554"}, + {"Nicaragua", "Nicaragua (le)", "NI", "NIC", "558"}, + {"Niger (the)", "Niger (le)", "NE", "NER", "562"}, + {"Nigeria", "Nigéria (le)", "NG", "NGA", "566"}, + {"Niue", "Niue", "NU", "NIU", "570"}, + {"Norfolk Island", "Norfolk (l'Île)", "NF", "NFK", "574"}, + {"Norway", "Norvège (la)", "NO", "NOR", "578"}, + {"Northern Mariana Islands (the)", "Mariannes du Nord (les Îles)", "MP", "MNP", "580"}, + {"United States Minor Outlying Islands (the)", "Îles mineures éloignées des États-Unis (les)", "UM", "UMI", "581"}, + {"Micronesia (Federated States of)", "Micronésie (États fédérés de)", "FM", "FSM", "583"}, + {"Marshall Islands (the)", "Marshall (Îles)", "MH", "MHL", "584"}, + {"Palau", "Palaos (les)", "PW", "PLW", "585"}, + {"Pakistan", "Pakistan (le)", "PK", "PAK", "586"}, + {"Panama", "Panama (le)", "PA", "PAN", "591"}, + {"Papua New Guinea", "Papouasie-Nouvelle-Guinée (la)", "PG", "PNG", "598"}, + {"Paraguay", "Paraguay (le)", "PY", "PRY", "600"}, + {"Peru", "Pérou (le)", "PE", "PER", "604"}, + {"Philippines (the)", "Philippines (les)", "PH", "PHL", "608"}, + {"Pitcairn", "Pitcairn", "PN", "PCN", "612"}, + {"Poland", "Pologne (la)", "PL", "POL", "616"}, + {"Portugal", "Portugal (le)", "PT", "PRT", "620"}, + {"Guinea-Bissau", "Guinée-Bissau (la)", "GW", "GNB", "624"}, + {"Timor-Leste", "Timor-Leste (le)", "TL", "TLS", "626"}, + {"Puerto Rico", "Porto Rico", "PR", "PRI", "630"}, + {"Qatar", "Qatar (le)", "QA", "QAT", "634"}, + {"Réunion", "Réunion (La)", "RE", "REU", "638"}, + {"Romania", "Roumanie (la)", "RO", "ROU", "642"}, + {"Russian Federation (the)", "Russie (la Fédération de)", "RU", "RUS", "643"}, + {"Rwanda", "Rwanda (le)", "RW", "RWA", "646"}, + {"Saint Barthélemy", "Saint-Barthélemy", "BL", "BLM", "652"}, + {"Saint Helena, Ascension and Tristan da Cunha", "Sainte-Hélène, Ascension et Tristan da Cunha", "SH", "SHN", "654"}, + {"Saint Kitts and Nevis", "Saint-Kitts-et-Nevis", "KN", "KNA", "659"}, + {"Anguilla", "Anguilla", "AI", "AIA", "660"}, + {"Saint Lucia", "Sainte-Lucie", "LC", "LCA", "662"}, + {"Saint Martin (French part)", "Saint-Martin (partie française)", "MF", "MAF", "663"}, + {"Saint Pierre and Miquelon", "Saint-Pierre-et-Miquelon", "PM", "SPM", "666"}, + {"Saint Vincent and the Grenadines", "Saint-Vincent-et-les Grenadines", "VC", "VCT", "670"}, + {"San Marino", "Saint-Marin", "SM", "SMR", "674"}, + {"Sao Tome and Principe", "Sao Tomé-et-Principe", "ST", "STP", "678"}, + {"Saudi Arabia", "Arabie saoudite (l')", "SA", "SAU", "682"}, + {"Senegal", "Sénégal (le)", "SN", "SEN", "686"}, + {"Serbia", "Serbie (la)", "RS", "SRB", "688"}, + {"Seychelles", "Seychelles (les)", "SC", "SYC", "690"}, + {"Sierra Leone", "Sierra Leone (la)", "SL", "SLE", "694"}, + {"Singapore", "Singapour", "SG", "SGP", "702"}, + {"Slovakia", "Slovaquie (la)", "SK", "SVK", "703"}, + {"Viet Nam", "Viet Nam (le)", "VN", "VNM", "704"}, + {"Slovenia", "Slovénie (la)", "SI", "SVN", "705"}, + {"Somalia", "Somalie (la)", "SO", "SOM", "706"}, + {"South Africa", "Afrique du Sud (l')", "ZA", "ZAF", "710"}, + {"Zimbabwe", "Zimbabwe (le)", "ZW", "ZWE", "716"}, + {"Spain", "Espagne (l')", "ES", "ESP", "724"}, + {"South Sudan", "Soudan du Sud (le)", "SS", "SSD", "728"}, + {"Sudan (the)", "Soudan (le)", "SD", "SDN", "729"}, + {"Western Sahara*", "Sahara occidental (le)*", "EH", "ESH", "732"}, + {"Suriname", "Suriname (le)", "SR", "SUR", "740"}, + {"Svalbard and Jan Mayen", "Svalbard et l'Île Jan Mayen (le)", "SJ", "SJM", "744"}, + {"Swaziland", "Swaziland (le)", "SZ", "SWZ", "748"}, + {"Sweden", "Suède (la)", "SE", "SWE", "752"}, + {"Switzerland", "Suisse (la)", "CH", "CHE", "756"}, + {"Syrian Arab Republic", "République arabe syrienne (la)", "SY", "SYR", "760"}, + {"Tajikistan", "Tadjikistan (le)", "TJ", "TJK", "762"}, + {"Thailand", "Thaïlande (la)", "TH", "THA", "764"}, + {"Togo", "Togo (le)", "TG", "TGO", "768"}, + {"Tokelau", "Tokelau (les)", "TK", "TKL", "772"}, + {"Tonga", "Tonga (les)", "TO", "TON", "776"}, + {"Trinidad and Tobago", "Trinité-et-Tobago (la)", "TT", "TTO", "780"}, + {"United Arab Emirates (the)", "Émirats arabes unis (les)", "AE", "ARE", "784"}, + {"Tunisia", "Tunisie (la)", "TN", "TUN", "788"}, + {"Turkey", "Turquie (la)", "TR", "TUR", "792"}, + {"Turkmenistan", "Turkménistan (le)", "TM", "TKM", "795"}, + {"Turks and Caicos Islands (the)", "Turks-et-Caïcos (les Îles)", "TC", "TCA", "796"}, + {"Tuvalu", "Tuvalu (les)", "TV", "TUV", "798"}, + {"Uganda", "Ouganda (l')", "UG", "UGA", "800"}, + {"Ukraine", "Ukraine (l')", "UA", "UKR", "804"}, + {"Macedonia (the former Yugoslav Republic of)", "Macédoine (l'ex‑République yougoslave de)", "MK", "MKD", "807"}, + {"Egypt", "Égypte (l')", "EG", "EGY", "818"}, + {"United Kingdom of Great Britain and Northern Ireland (the)", "Royaume-Uni de Grande-Bretagne et d'Irlande du Nord (le)", "GB", "GBR", "826"}, + {"Guernsey", "Guernesey", "GG", "GGY", "831"}, + {"Jersey", "Jersey", "JE", "JEY", "832"}, + {"Isle of Man", "Île de Man", "IM", "IMN", "833"}, + {"Tanzania, United Republic of", "Tanzanie, République-Unie de", "TZ", "TZA", "834"}, + {"United States of America (the)", "États-Unis d'Amérique (les)", "US", "USA", "840"}, + {"Virgin Islands (U.S.)", "Vierges des États-Unis (les Îles)", "VI", "VIR", "850"}, + {"Burkina Faso", "Burkina Faso (le)", "BF", "BFA", "854"}, + {"Uruguay", "Uruguay (l')", "UY", "URY", "858"}, + {"Uzbekistan", "Ouzbékistan (l')", "UZ", "UZB", "860"}, + {"Venezuela (Bolivarian Republic of)", "Venezuela (République bolivarienne du)", "VE", "VEN", "862"}, + {"Wallis and Futuna", "Wallis-et-Futuna", "WF", "WLF", "876"}, + {"Samoa", "Samoa (le)", "WS", "WSM", "882"}, + {"Yemen", "Yémen (le)", "YE", "YEM", "887"}, + {"Zambia", "Zambie (la)", "ZM", "ZMB", "894"}, +} + +// ISO4217List is the list of ISO currency codes +var ISO4217List = []string{ + "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", + "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BOV", "BRL", "BSD", "BTN", "BWP", "BYN", "BZD", + "CAD", "CDF", "CHE", "CHF", "CHW", "CLF", "CLP", "CNY", "COP", "COU", "CRC", "CUC", "CUP", "CVE", "CZK", + "DJF", "DKK", "DOP", "DZD", + "EGP", "ERN", "ETB", "EUR", + "FJD", "FKP", + "GBP", "GEL", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", + "HKD", "HNL", "HRK", "HTG", "HUF", + "IDR", "ILS", "INR", "IQD", "IRR", "ISK", + "JMD", "JOD", "JPY", + "KES", "KGS", "KHR", "KMF", "KPW", "KRW", "KWD", "KYD", "KZT", + "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", + "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRO", "MUR", "MVR", "MWK", "MXN", "MXV", "MYR", "MZN", + "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", + "OMR", + "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", + "QAR", + "RON", "RSD", "RUB", "RWF", + "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLL", "SOS", "SRD", "SSP", "STD", "SVC", "SYP", "SZL", + "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TWD", "TZS", + "UAH", "UGX", "USD", "USN", "UYI", "UYU", "UZS", + "VEF", "VND", "VUV", + "WST", + "XAF", "XAG", "XAU", "XBA", "XBB", "XBC", "XBD", "XCD", "XDR", "XOF", "XPD", "XPF", "XPT", "XSU", "XTS", "XUA", "XXX", + "YER", + "ZAR", "ZMW", "ZWL", +} + +// ISO693Entry stores ISO language codes +type ISO693Entry struct { + Alpha3bCode string + Alpha2Code string + English string +} + +//ISO693List based on http://data.okfn.org/data/core/language-codes/r/language-codes-3b2.json +var ISO693List = []ISO693Entry{ + {Alpha3bCode: "aar", Alpha2Code: "aa", English: "Afar"}, + {Alpha3bCode: "abk", Alpha2Code: "ab", English: "Abkhazian"}, + {Alpha3bCode: "afr", Alpha2Code: "af", English: "Afrikaans"}, + {Alpha3bCode: "aka", Alpha2Code: "ak", English: "Akan"}, + {Alpha3bCode: "alb", Alpha2Code: "sq", English: "Albanian"}, + {Alpha3bCode: "amh", Alpha2Code: "am", English: "Amharic"}, + {Alpha3bCode: "ara", Alpha2Code: "ar", English: "Arabic"}, + {Alpha3bCode: "arg", Alpha2Code: "an", English: "Aragonese"}, + {Alpha3bCode: "arm", Alpha2Code: "hy", English: "Armenian"}, + {Alpha3bCode: "asm", Alpha2Code: "as", English: "Assamese"}, + {Alpha3bCode: "ava", Alpha2Code: "av", English: "Avaric"}, + {Alpha3bCode: "ave", Alpha2Code: "ae", English: "Avestan"}, + {Alpha3bCode: "aym", Alpha2Code: "ay", English: "Aymara"}, + {Alpha3bCode: "aze", Alpha2Code: "az", English: "Azerbaijani"}, + {Alpha3bCode: "bak", Alpha2Code: "ba", English: "Bashkir"}, + {Alpha3bCode: "bam", Alpha2Code: "bm", English: "Bambara"}, + {Alpha3bCode: "baq", Alpha2Code: "eu", English: "Basque"}, + {Alpha3bCode: "bel", Alpha2Code: "be", English: "Belarusian"}, + {Alpha3bCode: "ben", Alpha2Code: "bn", English: "Bengali"}, + {Alpha3bCode: "bih", Alpha2Code: "bh", English: "Bihari languages"}, + {Alpha3bCode: "bis", Alpha2Code: "bi", English: "Bislama"}, + {Alpha3bCode: "bos", Alpha2Code: "bs", English: "Bosnian"}, + {Alpha3bCode: "bre", Alpha2Code: "br", English: "Breton"}, + {Alpha3bCode: "bul", Alpha2Code: "bg", English: "Bulgarian"}, + {Alpha3bCode: "bur", Alpha2Code: "my", English: "Burmese"}, + {Alpha3bCode: "cat", Alpha2Code: "ca", English: "Catalan; Valencian"}, + {Alpha3bCode: "cha", Alpha2Code: "ch", English: "Chamorro"}, + {Alpha3bCode: "che", Alpha2Code: "ce", English: "Chechen"}, + {Alpha3bCode: "chi", Alpha2Code: "zh", English: "Chinese"}, + {Alpha3bCode: "chu", Alpha2Code: "cu", English: "Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic"}, + {Alpha3bCode: "chv", Alpha2Code: "cv", English: "Chuvash"}, + {Alpha3bCode: "cor", Alpha2Code: "kw", English: "Cornish"}, + {Alpha3bCode: "cos", Alpha2Code: "co", English: "Corsican"}, + {Alpha3bCode: "cre", Alpha2Code: "cr", English: "Cree"}, + {Alpha3bCode: "cze", Alpha2Code: "cs", English: "Czech"}, + {Alpha3bCode: "dan", Alpha2Code: "da", English: "Danish"}, + {Alpha3bCode: "div", Alpha2Code: "dv", English: "Divehi; Dhivehi; Maldivian"}, + {Alpha3bCode: "dut", Alpha2Code: "nl", English: "Dutch; Flemish"}, + {Alpha3bCode: "dzo", Alpha2Code: "dz", English: "Dzongkha"}, + {Alpha3bCode: "eng", Alpha2Code: "en", English: "English"}, + {Alpha3bCode: "epo", Alpha2Code: "eo", English: "Esperanto"}, + {Alpha3bCode: "est", Alpha2Code: "et", English: "Estonian"}, + {Alpha3bCode: "ewe", Alpha2Code: "ee", English: "Ewe"}, + {Alpha3bCode: "fao", Alpha2Code: "fo", English: "Faroese"}, + {Alpha3bCode: "fij", Alpha2Code: "fj", English: "Fijian"}, + {Alpha3bCode: "fin", Alpha2Code: "fi", English: "Finnish"}, + {Alpha3bCode: "fre", Alpha2Code: "fr", English: "French"}, + {Alpha3bCode: "fry", Alpha2Code: "fy", English: "Western Frisian"}, + {Alpha3bCode: "ful", Alpha2Code: "ff", English: "Fulah"}, + {Alpha3bCode: "geo", Alpha2Code: "ka", English: "Georgian"}, + {Alpha3bCode: "ger", Alpha2Code: "de", English: "German"}, + {Alpha3bCode: "gla", Alpha2Code: "gd", English: "Gaelic; Scottish Gaelic"}, + {Alpha3bCode: "gle", Alpha2Code: "ga", English: "Irish"}, + {Alpha3bCode: "glg", Alpha2Code: "gl", English: "Galician"}, + {Alpha3bCode: "glv", Alpha2Code: "gv", English: "Manx"}, + {Alpha3bCode: "gre", Alpha2Code: "el", English: "Greek, Modern (1453-)"}, + {Alpha3bCode: "grn", Alpha2Code: "gn", English: "Guarani"}, + {Alpha3bCode: "guj", Alpha2Code: "gu", English: "Gujarati"}, + {Alpha3bCode: "hat", Alpha2Code: "ht", English: "Haitian; Haitian Creole"}, + {Alpha3bCode: "hau", Alpha2Code: "ha", English: "Hausa"}, + {Alpha3bCode: "heb", Alpha2Code: "he", English: "Hebrew"}, + {Alpha3bCode: "her", Alpha2Code: "hz", English: "Herero"}, + {Alpha3bCode: "hin", Alpha2Code: "hi", English: "Hindi"}, + {Alpha3bCode: "hmo", Alpha2Code: "ho", English: "Hiri Motu"}, + {Alpha3bCode: "hrv", Alpha2Code: "hr", English: "Croatian"}, + {Alpha3bCode: "hun", Alpha2Code: "hu", English: "Hungarian"}, + {Alpha3bCode: "ibo", Alpha2Code: "ig", English: "Igbo"}, + {Alpha3bCode: "ice", Alpha2Code: "is", English: "Icelandic"}, + {Alpha3bCode: "ido", Alpha2Code: "io", English: "Ido"}, + {Alpha3bCode: "iii", Alpha2Code: "ii", English: "Sichuan Yi; Nuosu"}, + {Alpha3bCode: "iku", Alpha2Code: "iu", English: "Inuktitut"}, + {Alpha3bCode: "ile", Alpha2Code: "ie", English: "Interlingue; Occidental"}, + {Alpha3bCode: "ina", Alpha2Code: "ia", English: "Interlingua (International Auxiliary Language Association)"}, + {Alpha3bCode: "ind", Alpha2Code: "id", English: "Indonesian"}, + {Alpha3bCode: "ipk", Alpha2Code: "ik", English: "Inupiaq"}, + {Alpha3bCode: "ita", Alpha2Code: "it", English: "Italian"}, + {Alpha3bCode: "jav", Alpha2Code: "jv", English: "Javanese"}, + {Alpha3bCode: "jpn", Alpha2Code: "ja", English: "Japanese"}, + {Alpha3bCode: "kal", Alpha2Code: "kl", English: "Kalaallisut; Greenlandic"}, + {Alpha3bCode: "kan", Alpha2Code: "kn", English: "Kannada"}, + {Alpha3bCode: "kas", Alpha2Code: "ks", English: "Kashmiri"}, + {Alpha3bCode: "kau", Alpha2Code: "kr", English: "Kanuri"}, + {Alpha3bCode: "kaz", Alpha2Code: "kk", English: "Kazakh"}, + {Alpha3bCode: "khm", Alpha2Code: "km", English: "Central Khmer"}, + {Alpha3bCode: "kik", Alpha2Code: "ki", English: "Kikuyu; Gikuyu"}, + {Alpha3bCode: "kin", Alpha2Code: "rw", English: "Kinyarwanda"}, + {Alpha3bCode: "kir", Alpha2Code: "ky", English: "Kirghiz; Kyrgyz"}, + {Alpha3bCode: "kom", Alpha2Code: "kv", English: "Komi"}, + {Alpha3bCode: "kon", Alpha2Code: "kg", English: "Kongo"}, + {Alpha3bCode: "kor", Alpha2Code: "ko", English: "Korean"}, + {Alpha3bCode: "kua", Alpha2Code: "kj", English: "Kuanyama; Kwanyama"}, + {Alpha3bCode: "kur", Alpha2Code: "ku", English: "Kurdish"}, + {Alpha3bCode: "lao", Alpha2Code: "lo", English: "Lao"}, + {Alpha3bCode: "lat", Alpha2Code: "la", English: "Latin"}, + {Alpha3bCode: "lav", Alpha2Code: "lv", English: "Latvian"}, + {Alpha3bCode: "lim", Alpha2Code: "li", English: "Limburgan; Limburger; Limburgish"}, + {Alpha3bCode: "lin", Alpha2Code: "ln", English: "Lingala"}, + {Alpha3bCode: "lit", Alpha2Code: "lt", English: "Lithuanian"}, + {Alpha3bCode: "ltz", Alpha2Code: "lb", English: "Luxembourgish; Letzeburgesch"}, + {Alpha3bCode: "lub", Alpha2Code: "lu", English: "Luba-Katanga"}, + {Alpha3bCode: "lug", Alpha2Code: "lg", English: "Ganda"}, + {Alpha3bCode: "mac", Alpha2Code: "mk", English: "Macedonian"}, + {Alpha3bCode: "mah", Alpha2Code: "mh", English: "Marshallese"}, + {Alpha3bCode: "mal", Alpha2Code: "ml", English: "Malayalam"}, + {Alpha3bCode: "mao", Alpha2Code: "mi", English: "Maori"}, + {Alpha3bCode: "mar", Alpha2Code: "mr", English: "Marathi"}, + {Alpha3bCode: "may", Alpha2Code: "ms", English: "Malay"}, + {Alpha3bCode: "mlg", Alpha2Code: "mg", English: "Malagasy"}, + {Alpha3bCode: "mlt", Alpha2Code: "mt", English: "Maltese"}, + {Alpha3bCode: "mon", Alpha2Code: "mn", English: "Mongolian"}, + {Alpha3bCode: "nau", Alpha2Code: "na", English: "Nauru"}, + {Alpha3bCode: "nav", Alpha2Code: "nv", English: "Navajo; Navaho"}, + {Alpha3bCode: "nbl", Alpha2Code: "nr", English: "Ndebele, South; South Ndebele"}, + {Alpha3bCode: "nde", Alpha2Code: "nd", English: "Ndebele, North; North Ndebele"}, + {Alpha3bCode: "ndo", Alpha2Code: "ng", English: "Ndonga"}, + {Alpha3bCode: "nep", Alpha2Code: "ne", English: "Nepali"}, + {Alpha3bCode: "nno", Alpha2Code: "nn", English: "Norwegian Nynorsk; Nynorsk, Norwegian"}, + {Alpha3bCode: "nob", Alpha2Code: "nb", English: "Bokmål, Norwegian; Norwegian Bokmål"}, + {Alpha3bCode: "nor", Alpha2Code: "no", English: "Norwegian"}, + {Alpha3bCode: "nya", Alpha2Code: "ny", English: "Chichewa; Chewa; Nyanja"}, + {Alpha3bCode: "oci", Alpha2Code: "oc", English: "Occitan (post 1500); Provençal"}, + {Alpha3bCode: "oji", Alpha2Code: "oj", English: "Ojibwa"}, + {Alpha3bCode: "ori", Alpha2Code: "or", English: "Oriya"}, + {Alpha3bCode: "orm", Alpha2Code: "om", English: "Oromo"}, + {Alpha3bCode: "oss", Alpha2Code: "os", English: "Ossetian; Ossetic"}, + {Alpha3bCode: "pan", Alpha2Code: "pa", English: "Panjabi; Punjabi"}, + {Alpha3bCode: "per", Alpha2Code: "fa", English: "Persian"}, + {Alpha3bCode: "pli", Alpha2Code: "pi", English: "Pali"}, + {Alpha3bCode: "pol", Alpha2Code: "pl", English: "Polish"}, + {Alpha3bCode: "por", Alpha2Code: "pt", English: "Portuguese"}, + {Alpha3bCode: "pus", Alpha2Code: "ps", English: "Pushto; Pashto"}, + {Alpha3bCode: "que", Alpha2Code: "qu", English: "Quechua"}, + {Alpha3bCode: "roh", Alpha2Code: "rm", English: "Romansh"}, + {Alpha3bCode: "rum", Alpha2Code: "ro", English: "Romanian; Moldavian; Moldovan"}, + {Alpha3bCode: "run", Alpha2Code: "rn", English: "Rundi"}, + {Alpha3bCode: "rus", Alpha2Code: "ru", English: "Russian"}, + {Alpha3bCode: "sag", Alpha2Code: "sg", English: "Sango"}, + {Alpha3bCode: "san", Alpha2Code: "sa", English: "Sanskrit"}, + {Alpha3bCode: "sin", Alpha2Code: "si", English: "Sinhala; Sinhalese"}, + {Alpha3bCode: "slo", Alpha2Code: "sk", English: "Slovak"}, + {Alpha3bCode: "slv", Alpha2Code: "sl", English: "Slovenian"}, + {Alpha3bCode: "sme", Alpha2Code: "se", English: "Northern Sami"}, + {Alpha3bCode: "smo", Alpha2Code: "sm", English: "Samoan"}, + {Alpha3bCode: "sna", Alpha2Code: "sn", English: "Shona"}, + {Alpha3bCode: "snd", Alpha2Code: "sd", English: "Sindhi"}, + {Alpha3bCode: "som", Alpha2Code: "so", English: "Somali"}, + {Alpha3bCode: "sot", Alpha2Code: "st", English: "Sotho, Southern"}, + {Alpha3bCode: "spa", Alpha2Code: "es", English: "Spanish; Castilian"}, + {Alpha3bCode: "srd", Alpha2Code: "sc", English: "Sardinian"}, + {Alpha3bCode: "srp", Alpha2Code: "sr", English: "Serbian"}, + {Alpha3bCode: "ssw", Alpha2Code: "ss", English: "Swati"}, + {Alpha3bCode: "sun", Alpha2Code: "su", English: "Sundanese"}, + {Alpha3bCode: "swa", Alpha2Code: "sw", English: "Swahili"}, + {Alpha3bCode: "swe", Alpha2Code: "sv", English: "Swedish"}, + {Alpha3bCode: "tah", Alpha2Code: "ty", English: "Tahitian"}, + {Alpha3bCode: "tam", Alpha2Code: "ta", English: "Tamil"}, + {Alpha3bCode: "tat", Alpha2Code: "tt", English: "Tatar"}, + {Alpha3bCode: "tel", Alpha2Code: "te", English: "Telugu"}, + {Alpha3bCode: "tgk", Alpha2Code: "tg", English: "Tajik"}, + {Alpha3bCode: "tgl", Alpha2Code: "tl", English: "Tagalog"}, + {Alpha3bCode: "tha", Alpha2Code: "th", English: "Thai"}, + {Alpha3bCode: "tib", Alpha2Code: "bo", English: "Tibetan"}, + {Alpha3bCode: "tir", Alpha2Code: "ti", English: "Tigrinya"}, + {Alpha3bCode: "ton", Alpha2Code: "to", English: "Tonga (Tonga Islands)"}, + {Alpha3bCode: "tsn", Alpha2Code: "tn", English: "Tswana"}, + {Alpha3bCode: "tso", Alpha2Code: "ts", English: "Tsonga"}, + {Alpha3bCode: "tuk", Alpha2Code: "tk", English: "Turkmen"}, + {Alpha3bCode: "tur", Alpha2Code: "tr", English: "Turkish"}, + {Alpha3bCode: "twi", Alpha2Code: "tw", English: "Twi"}, + {Alpha3bCode: "uig", Alpha2Code: "ug", English: "Uighur; Uyghur"}, + {Alpha3bCode: "ukr", Alpha2Code: "uk", English: "Ukrainian"}, + {Alpha3bCode: "urd", Alpha2Code: "ur", English: "Urdu"}, + {Alpha3bCode: "uzb", Alpha2Code: "uz", English: "Uzbek"}, + {Alpha3bCode: "ven", Alpha2Code: "ve", English: "Venda"}, + {Alpha3bCode: "vie", Alpha2Code: "vi", English: "Vietnamese"}, + {Alpha3bCode: "vol", Alpha2Code: "vo", English: "Volapük"}, + {Alpha3bCode: "wel", Alpha2Code: "cy", English: "Welsh"}, + {Alpha3bCode: "wln", Alpha2Code: "wa", English: "Walloon"}, + {Alpha3bCode: "wol", Alpha2Code: "wo", English: "Wolof"}, + {Alpha3bCode: "xho", Alpha2Code: "xh", English: "Xhosa"}, + {Alpha3bCode: "yid", Alpha2Code: "yi", English: "Yiddish"}, + {Alpha3bCode: "yor", Alpha2Code: "yo", English: "Yoruba"}, + {Alpha3bCode: "zha", Alpha2Code: "za", English: "Zhuang; Chuang"}, + {Alpha3bCode: "zul", Alpha2Code: "zu", English: "Zulu"}, +} diff --git a/vendor/github.com/asaskevich/govalidator/utils.go b/vendor/github.com/asaskevich/govalidator/utils.go new file mode 100644 index 000000000..6a8871c1c --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/utils.go @@ -0,0 +1,264 @@ +package govalidator + +import ( + "errors" + "fmt" + "html" + "math" + "path" + "regexp" + "strings" + "unicode" + "unicode/utf8" +) + +// Contains check if the string contains the substring. +func Contains(str, substring string) bool { + return strings.Contains(str, substring) +} + +// Matches check if string matches the pattern (pattern is regular expression) +// In case of error return false +func Matches(str, pattern string) bool { + match, _ := regexp.MatchString(pattern, str) + return match +} + +// LeftTrim trim characters from the left-side of the input. +// If second argument is empty, it's will be remove leading spaces. +func LeftTrim(str, chars string) string { + if chars == "" { + return strings.TrimLeftFunc(str, unicode.IsSpace) + } + r, _ := regexp.Compile("^[" + chars + "]+") + return r.ReplaceAllString(str, "") +} + +// RightTrim trim characters from the right-side of the input. +// If second argument is empty, it's will be remove spaces. +func RightTrim(str, chars string) string { + if chars == "" { + return strings.TrimRightFunc(str, unicode.IsSpace) + } + r, _ := regexp.Compile("[" + chars + "]+$") + return r.ReplaceAllString(str, "") +} + +// Trim trim characters from both sides of the input. +// If second argument is empty, it's will be remove spaces. +func Trim(str, chars string) string { + return LeftTrim(RightTrim(str, chars), chars) +} + +// WhiteList remove characters that do not appear in the whitelist. +func WhiteList(str, chars string) string { + pattern := "[^" + chars + "]+" + r, _ := regexp.Compile(pattern) + return r.ReplaceAllString(str, "") +} + +// BlackList remove characters that appear in the blacklist. +func BlackList(str, chars string) string { + pattern := "[" + chars + "]+" + r, _ := regexp.Compile(pattern) + return r.ReplaceAllString(str, "") +} + +// StripLow remove characters with a numerical value < 32 and 127, mostly control characters. +// If keep_new_lines is true, newline characters are preserved (\n and \r, hex 0xA and 0xD). +func StripLow(str string, keepNewLines bool) string { + chars := "" + if keepNewLines { + chars = "\x00-\x09\x0B\x0C\x0E-\x1F\x7F" + } else { + chars = "\x00-\x1F\x7F" + } + return BlackList(str, chars) +} + +// ReplacePattern replace regular expression pattern in string +func ReplacePattern(str, pattern, replace string) string { + r, _ := regexp.Compile(pattern) + return r.ReplaceAllString(str, replace) +} + +// Escape replace <, >, & and " with HTML entities. +var Escape = html.EscapeString + +func addSegment(inrune, segment []rune) []rune { + if len(segment) == 0 { + return inrune + } + if len(inrune) != 0 { + inrune = append(inrune, '_') + } + inrune = append(inrune, segment...) + return inrune +} + +// UnderscoreToCamelCase converts from underscore separated form to camel case form. +// Ex.: my_func => MyFunc +func UnderscoreToCamelCase(s string) string { + return strings.Replace(strings.Title(strings.Replace(strings.ToLower(s), "_", " ", -1)), " ", "", -1) +} + +// CamelCaseToUnderscore converts from camel case form to underscore separated form. +// Ex.: MyFunc => my_func +func CamelCaseToUnderscore(str string) string { + var output []rune + var segment []rune + for _, r := range str { + + // not treat number as separate segment + if !unicode.IsLower(r) && string(r) != "_" && !unicode.IsNumber(r) { + output = addSegment(output, segment) + segment = nil + } + segment = append(segment, unicode.ToLower(r)) + } + output = addSegment(output, segment) + return string(output) +} + +// Reverse return reversed string +func Reverse(s string) string { + r := []rune(s) + for i, j := 0, len(r)-1; i < j; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r) +} + +// GetLines split string by "\n" and return array of lines +func GetLines(s string) []string { + return strings.Split(s, "\n") +} + +// GetLine return specified line of multiline string +func GetLine(s string, index int) (string, error) { + lines := GetLines(s) + if index < 0 || index >= len(lines) { + return "", errors.New("line index out of bounds") + } + return lines[index], nil +} + +// RemoveTags remove all tags from HTML string +func RemoveTags(s string) string { + return ReplacePattern(s, "<[^>]*>", "") +} + +// SafeFileName return safe string that can be used in file names +func SafeFileName(str string) string { + name := strings.ToLower(str) + name = path.Clean(path.Base(name)) + name = strings.Trim(name, " ") + separators, err := regexp.Compile(`[ &_=+:]`) + if err == nil { + name = separators.ReplaceAllString(name, "-") + } + legal, err := regexp.Compile(`[^[:alnum:]-.]`) + if err == nil { + name = legal.ReplaceAllString(name, "") + } + for strings.Contains(name, "--") { + name = strings.Replace(name, "--", "-", -1) + } + return name +} + +// NormalizeEmail canonicalize an email address. +// The local part of the email address is lowercased for all domains; the hostname is always lowercased and +// the local part of the email address is always lowercased for hosts that are known to be case-insensitive (currently only GMail). +// Normalization follows special rules for known providers: currently, GMail addresses have dots removed in the local part and +// are stripped of tags (e.g. some.one+tag@gmail.com becomes someone@gmail.com) and all @googlemail.com addresses are +// normalized to @gmail.com. +func NormalizeEmail(str string) (string, error) { + if !IsEmail(str) { + return "", fmt.Errorf("%s is not an email", str) + } + parts := strings.Split(str, "@") + parts[0] = strings.ToLower(parts[0]) + parts[1] = strings.ToLower(parts[1]) + if parts[1] == "gmail.com" || parts[1] == "googlemail.com" { + parts[1] = "gmail.com" + parts[0] = strings.Split(ReplacePattern(parts[0], `\.`, ""), "+")[0] + } + return strings.Join(parts, "@"), nil +} + +// Truncate a string to the closest length without breaking words. +func Truncate(str string, length int, ending string) string { + var aftstr, befstr string + if len(str) > length { + words := strings.Fields(str) + before, present := 0, 0 + for i := range words { + befstr = aftstr + before = present + aftstr = aftstr + words[i] + " " + present = len(aftstr) + if present > length && i != 0 { + if (length - before) < (present - length) { + return Trim(befstr, " /\\.,\"'#!?&@+-") + ending + } + return Trim(aftstr, " /\\.,\"'#!?&@+-") + ending + } + } + } + + return str +} + +// PadLeft pad left side of string if size of string is less then indicated pad length +func PadLeft(str string, padStr string, padLen int) string { + return buildPadStr(str, padStr, padLen, true, false) +} + +// PadRight pad right side of string if size of string is less then indicated pad length +func PadRight(str string, padStr string, padLen int) string { + return buildPadStr(str, padStr, padLen, false, true) +} + +// PadBoth pad sides of string if size of string is less then indicated pad length +func PadBoth(str string, padStr string, padLen int) string { + return buildPadStr(str, padStr, padLen, true, true) +} + +// PadString either left, right or both sides, not the padding string can be unicode and more then one +// character +func buildPadStr(str string, padStr string, padLen int, padLeft bool, padRight bool) string { + + // When padded length is less then the current string size + if padLen < utf8.RuneCountInString(str) { + return str + } + + padLen -= utf8.RuneCountInString(str) + + targetLen := padLen + + targetLenLeft := targetLen + targetLenRight := targetLen + if padLeft && padRight { + targetLenLeft = padLen / 2 + targetLenRight = padLen - targetLenLeft + } + + strToRepeatLen := utf8.RuneCountInString(padStr) + + repeatTimes := int(math.Ceil(float64(targetLen) / float64(strToRepeatLen))) + repeatedString := strings.Repeat(padStr, repeatTimes) + + leftSide := "" + if padLeft { + leftSide = repeatedString[0:targetLenLeft] + } + + rightSide := "" + if padRight { + rightSide = repeatedString[0:targetLenRight] + } + + return leftSide + str + rightSide +} diff --git a/vendor/github.com/asaskevich/govalidator/validator.go b/vendor/github.com/asaskevich/govalidator/validator.go new file mode 100644 index 000000000..071f43c09 --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator/validator.go @@ -0,0 +1,1213 @@ +// Package govalidator is package of validators and sanitizers for strings, structs and collections. +package govalidator + +import ( + "bytes" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/json" + "encoding/pem" + "fmt" + "io/ioutil" + "net" + "net/url" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "time" + "unicode" + "unicode/utf8" +) + +var ( + fieldsRequiredByDefault bool + notNumberRegexp = regexp.MustCompile("[^0-9]+") + whiteSpacesAndMinus = regexp.MustCompile("[\\s-]+") + paramsRegexp = regexp.MustCompile("\\(.*\\)$") +) + +const maxURLRuneCount = 2083 +const minURLRuneCount = 3 +const RF3339WithoutZone = "2006-01-02T15:04:05" + +// SetFieldsRequiredByDefault causes validation to fail when struct fields +// do not include validations or are not explicitly marked as exempt (using `valid:"-"` or `valid:"email,optional"`). +// This struct definition will fail govalidator.ValidateStruct() (and the field values do not matter): +// type exampleStruct struct { +// Name string `` +// Email string `valid:"email"` +// This, however, will only fail when Email is empty or an invalid email address: +// type exampleStruct2 struct { +// Name string `valid:"-"` +// Email string `valid:"email"` +// Lastly, this will only fail when Email is an invalid email address but not when it's empty: +// type exampleStruct2 struct { +// Name string `valid:"-"` +// Email string `valid:"email,optional"` +func SetFieldsRequiredByDefault(value bool) { + fieldsRequiredByDefault = value +} + +// IsEmail check if the string is an email. +func IsEmail(email string) bool { + if len(email) < 6 || len(email) > 254 { + return false + } + at := strings.LastIndex(email, "@") + if at <= 0 || at > len(email)-3 { + return false + } + user := email[:at] + host := email[at+1:] + if len(user) > 64 { + return false + } + if userDotRegexp.MatchString(user) || !userRegexp.MatchString(user) || !hostRegexp.MatchString(host) { + return false + } + switch host { + case "localhost", "example.com": + return true + } + if _, err := net.LookupMX(host); err != nil { + if _, err := net.LookupIP(host); err != nil { + return false + } + } + + return true +} + +// IsURL check if the string is an URL. +func IsURL(str string) bool { + if str == "" || utf8.RuneCountInString(str) >= maxURLRuneCount || len(str) <= minURLRuneCount || strings.HasPrefix(str, ".") { + return false + } + strTemp := str + if strings.Index(str, ":") >= 0 && strings.Index(str, "://") == -1 { + // support no indicated urlscheme but with colon for port number + // http:// is appended so url.Parse will succeed, strTemp used so it does not impact rxURL.MatchString + strTemp = "http://" + str + } + u, err := url.Parse(strTemp) + if err != nil { + return false + } + if strings.HasPrefix(u.Host, ".") { + return false + } + if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) { + return false + } + return rxURL.MatchString(str) +} + +// IsRequestURL check if the string rawurl, assuming +// it was received in an HTTP request, is a valid +// URL confirm to RFC 3986 +func IsRequestURL(rawurl string) bool { + url, err := url.ParseRequestURI(rawurl) + if err != nil { + return false //Couldn't even parse the rawurl + } + if len(url.Scheme) == 0 { + return false //No Scheme found + } + return true +} + +// IsRequestURI check if the string rawurl, assuming +// it was received in an HTTP request, is an +// absolute URI or an absolute path. +func IsRequestURI(rawurl string) bool { + _, err := url.ParseRequestURI(rawurl) + return err == nil +} + +// IsAlpha check if the string contains only letters (a-zA-Z). Empty string is valid. +func IsAlpha(str string) bool { + if IsNull(str) { + return true + } + return rxAlpha.MatchString(str) +} + +//IsUTFLetter check if the string contains only unicode letter characters. +//Similar to IsAlpha but for all languages. Empty string is valid. +func IsUTFLetter(str string) bool { + if IsNull(str) { + return true + } + + for _, c := range str { + if !unicode.IsLetter(c) { + return false + } + } + return true + +} + +// IsAlphanumeric check if the string contains only letters and numbers. Empty string is valid. +func IsAlphanumeric(str string) bool { + if IsNull(str) { + return true + } + return rxAlphanumeric.MatchString(str) +} + +// IsUTFLetterNumeric check if the string contains only unicode letters and numbers. Empty string is valid. +func IsUTFLetterNumeric(str string) bool { + if IsNull(str) { + return true + } + for _, c := range str { + if !unicode.IsLetter(c) && !unicode.IsNumber(c) { //letters && numbers are ok + return false + } + } + return true + +} + +// IsNumeric check if the string contains only numbers. Empty string is valid. +func IsNumeric(str string) bool { + if IsNull(str) { + return true + } + return rxNumeric.MatchString(str) +} + +// IsUTFNumeric check if the string contains only unicode numbers of any kind. +// Numbers can be 0-9 but also Fractions ¾,Roman Ⅸ and Hangzhou 〩. Empty string is valid. +func IsUTFNumeric(str string) bool { + if IsNull(str) { + return true + } + if strings.IndexAny(str, "+-") > 0 { + return false + } + if len(str) > 1 { + str = strings.TrimPrefix(str, "-") + str = strings.TrimPrefix(str, "+") + } + for _, c := range str { + if unicode.IsNumber(c) == false { //numbers && minus sign are ok + return false + } + } + return true + +} + +// IsUTFDigit check if the string contains only unicode radix-10 decimal digits. Empty string is valid. +func IsUTFDigit(str string) bool { + if IsNull(str) { + return true + } + if strings.IndexAny(str, "+-") > 0 { + return false + } + if len(str) > 1 { + str = strings.TrimPrefix(str, "-") + str = strings.TrimPrefix(str, "+") + } + for _, c := range str { + if !unicode.IsDigit(c) { //digits && minus sign are ok + return false + } + } + return true + +} + +// IsHexadecimal check if the string is a hexadecimal number. +func IsHexadecimal(str string) bool { + return rxHexadecimal.MatchString(str) +} + +// IsHexcolor check if the string is a hexadecimal color. +func IsHexcolor(str string) bool { + return rxHexcolor.MatchString(str) +} + +// IsRGBcolor check if the string is a valid RGB color in form rgb(RRR, GGG, BBB). +func IsRGBcolor(str string) bool { + return rxRGBcolor.MatchString(str) +} + +// IsLowerCase check if the string is lowercase. Empty string is valid. +func IsLowerCase(str string) bool { + if IsNull(str) { + return true + } + return str == strings.ToLower(str) +} + +// IsUpperCase check if the string is uppercase. Empty string is valid. +func IsUpperCase(str string) bool { + if IsNull(str) { + return true + } + return str == strings.ToUpper(str) +} + +// HasLowerCase check if the string contains at least 1 lowercase. Empty string is valid. +func HasLowerCase(str string) bool { + if IsNull(str) { + return true + } + return rxHasLowerCase.MatchString(str) +} + +// HasUpperCase check if the string contians as least 1 uppercase. Empty string is valid. +func HasUpperCase(str string) bool { + if IsNull(str) { + return true + } + return rxHasUpperCase.MatchString(str) +} + +// IsInt check if the string is an integer. Empty string is valid. +func IsInt(str string) bool { + if IsNull(str) { + return true + } + return rxInt.MatchString(str) +} + +// IsFloat check if the string is a float. +func IsFloat(str string) bool { + return str != "" && rxFloat.MatchString(str) +} + +// IsDivisibleBy check if the string is a number that's divisible by another. +// If second argument is not valid integer or zero, it's return false. +// Otherwise, if first argument is not valid integer or zero, it's return true (Invalid string converts to zero). +func IsDivisibleBy(str, num string) bool { + f, _ := ToFloat(str) + p := int64(f) + q, _ := ToInt(num) + if q == 0 { + return false + } + return (p == 0) || (p%q == 0) +} + +// IsNull check if the string is null. +func IsNull(str string) bool { + return len(str) == 0 +} + +// IsByteLength check if the string's length (in bytes) falls in a range. +func IsByteLength(str string, min, max int) bool { + return len(str) >= min && len(str) <= max +} + +// IsUUIDv3 check if the string is a UUID version 3. +func IsUUIDv3(str string) bool { + return rxUUID3.MatchString(str) +} + +// IsUUIDv4 check if the string is a UUID version 4. +func IsUUIDv4(str string) bool { + return rxUUID4.MatchString(str) +} + +// IsUUIDv5 check if the string is a UUID version 5. +func IsUUIDv5(str string) bool { + return rxUUID5.MatchString(str) +} + +// IsUUID check if the string is a UUID (version 3, 4 or 5). +func IsUUID(str string) bool { + return rxUUID.MatchString(str) +} + +// IsCreditCard check if the string is a credit card. +func IsCreditCard(str string) bool { + sanitized := notNumberRegexp.ReplaceAllString(str, "") + if !rxCreditCard.MatchString(sanitized) { + return false + } + var sum int64 + var digit string + var tmpNum int64 + var shouldDouble bool + for i := len(sanitized) - 1; i >= 0; i-- { + digit = sanitized[i:(i + 1)] + tmpNum, _ = ToInt(digit) + if shouldDouble { + tmpNum *= 2 + if tmpNum >= 10 { + sum += ((tmpNum % 10) + 1) + } else { + sum += tmpNum + } + } else { + sum += tmpNum + } + shouldDouble = !shouldDouble + } + + if sum%10 == 0 { + return true + } + return false +} + +// IsISBN10 check if the string is an ISBN version 10. +func IsISBN10(str string) bool { + return IsISBN(str, 10) +} + +// IsISBN13 check if the string is an ISBN version 13. +func IsISBN13(str string) bool { + return IsISBN(str, 13) +} + +// IsISBN check if the string is an ISBN (version 10 or 13). +// If version value is not equal to 10 or 13, it will be check both variants. +func IsISBN(str string, version int) bool { + sanitized := whiteSpacesAndMinus.ReplaceAllString(str, "") + var checksum int32 + var i int32 + if version == 10 { + if !rxISBN10.MatchString(sanitized) { + return false + } + for i = 0; i < 9; i++ { + checksum += (i + 1) * int32(sanitized[i]-'0') + } + if sanitized[9] == 'X' { + checksum += 10 * 10 + } else { + checksum += 10 * int32(sanitized[9]-'0') + } + if checksum%11 == 0 { + return true + } + return false + } else if version == 13 { + if !rxISBN13.MatchString(sanitized) { + return false + } + factor := []int32{1, 3} + for i = 0; i < 12; i++ { + checksum += factor[i%2] * int32(sanitized[i]-'0') + } + if (int32(sanitized[12]-'0'))-((10-(checksum%10))%10) == 0 { + return true + } + return false + } + return IsISBN(str, 10) || IsISBN(str, 13) +} + +// IsJSON check if the string is valid JSON (note: uses json.Unmarshal). +func IsJSON(str string) bool { + var js json.RawMessage + return json.Unmarshal([]byte(str), &js) == nil +} + +// IsMultibyte check if the string contains one or more multibyte chars. Empty string is valid. +func IsMultibyte(str string) bool { + if IsNull(str) { + return true + } + return rxMultibyte.MatchString(str) +} + +// IsASCII check if the string contains ASCII chars only. Empty string is valid. +func IsASCII(str string) bool { + if IsNull(str) { + return true + } + return rxASCII.MatchString(str) +} + +// IsPrintableASCII check if the string contains printable ASCII chars only. Empty string is valid. +func IsPrintableASCII(str string) bool { + if IsNull(str) { + return true + } + return rxPrintableASCII.MatchString(str) +} + +// IsFullWidth check if the string contains any full-width chars. Empty string is valid. +func IsFullWidth(str string) bool { + if IsNull(str) { + return true + } + return rxFullWidth.MatchString(str) +} + +// IsHalfWidth check if the string contains any half-width chars. Empty string is valid. +func IsHalfWidth(str string) bool { + if IsNull(str) { + return true + } + return rxHalfWidth.MatchString(str) +} + +// IsVariableWidth check if the string contains a mixture of full and half-width chars. Empty string is valid. +func IsVariableWidth(str string) bool { + if IsNull(str) { + return true + } + return rxHalfWidth.MatchString(str) && rxFullWidth.MatchString(str) +} + +// IsBase64 check if a string is base64 encoded. +func IsBase64(str string) bool { + return rxBase64.MatchString(str) +} + +// IsFilePath check is a string is Win or Unix file path and returns it's type. +func IsFilePath(str string) (bool, int) { + if rxWinPath.MatchString(str) { + //check windows path limit see: + // http://msdn.microsoft.com/en-us/library/aa365247(VS.85).aspx#maxpath + if len(str[3:]) > 32767 { + return false, Win + } + return true, Win + } else if rxUnixPath.MatchString(str) { + return true, Unix + } + return false, Unknown +} + +// IsDataURI checks if a string is base64 encoded data URI such as an image +func IsDataURI(str string) bool { + dataURI := strings.Split(str, ",") + if !rxDataURI.MatchString(dataURI[0]) { + return false + } + return IsBase64(dataURI[1]) +} + +// IsISO3166Alpha2 checks if a string is valid two-letter country code +func IsISO3166Alpha2(str string) bool { + for _, entry := range ISO3166List { + if str == entry.Alpha2Code { + return true + } + } + return false +} + +// IsISO3166Alpha3 checks if a string is valid three-letter country code +func IsISO3166Alpha3(str string) bool { + for _, entry := range ISO3166List { + if str == entry.Alpha3Code { + return true + } + } + return false +} + +// IsISO693Alpha2 checks if a string is valid two-letter language code +func IsISO693Alpha2(str string) bool { + for _, entry := range ISO693List { + if str == entry.Alpha2Code { + return true + } + } + return false +} + +// IsISO693Alpha3b checks if a string is valid three-letter language code +func IsISO693Alpha3b(str string) bool { + for _, entry := range ISO693List { + if str == entry.Alpha3bCode { + return true + } + } + return false +} + +// IsDNSName will validate the given string as a DNS name +func IsDNSName(str string) bool { + if str == "" || len(strings.Replace(str, ".", "", -1)) > 255 { + // constraints already violated + return false + } + return !IsIP(str) && rxDNSName.MatchString(str) +} + +// IsHash checks if a string is a hash of type algorithm. +// Algorithm is one of ['md4', 'md5', 'sha1', 'sha256', 'sha384', 'sha512', 'ripemd128', 'ripemd160', 'tiger128', 'tiger160', 'tiger192', 'crc32', 'crc32b'] +func IsHash(str string, algorithm string) bool { + len := "0" + algo := strings.ToLower(algorithm) + + if algo == "crc32" || algo == "crc32b" { + len = "8" + } else if algo == "md5" || algo == "md4" || algo == "ripemd128" || algo == "tiger128" { + len = "32" + } else if algo == "sha1" || algo == "ripemd160" || algo == "tiger160" { + len = "40" + } else if algo == "tiger192" { + len = "48" + } else if algo == "sha256" { + len = "64" + } else if algo == "sha384" { + len = "96" + } else if algo == "sha512" { + len = "128" + } else { + return false + } + + return Matches(str, "^[a-f0-9]{"+len+"}$") +} + +// IsDialString validates the given string for usage with the various Dial() functions +func IsDialString(str string) bool { + + if h, p, err := net.SplitHostPort(str); err == nil && h != "" && p != "" && (IsDNSName(h) || IsIP(h)) && IsPort(p) { + return true + } + + return false +} + +// IsIP checks if a string is either IP version 4 or 6. +func IsIP(str string) bool { + return net.ParseIP(str) != nil +} + +// IsPort checks if a string represents a valid port +func IsPort(str string) bool { + if i, err := strconv.Atoi(str); err == nil && i > 0 && i < 65536 { + return true + } + return false +} + +// IsIPv4 check if the string is an IP version 4. +func IsIPv4(str string) bool { + ip := net.ParseIP(str) + return ip != nil && strings.Contains(str, ".") +} + +// IsIPv6 check if the string is an IP version 6. +func IsIPv6(str string) bool { + ip := net.ParseIP(str) + return ip != nil && strings.Contains(str, ":") +} + +// IsCIDR check if the string is an valid CIDR notiation (IPV4 & IPV6) +func IsCIDR(str string) bool { + _, _, err := net.ParseCIDR(str) + return err == nil +} + +// IsMAC check if a string is valid MAC address. +// Possible MAC formats: +// 01:23:45:67:89:ab +// 01:23:45:67:89:ab:cd:ef +// 01-23-45-67-89-ab +// 01-23-45-67-89-ab-cd-ef +// 0123.4567.89ab +// 0123.4567.89ab.cdef +func IsMAC(str string) bool { + _, err := net.ParseMAC(str) + return err == nil +} + +// IsHost checks if the string is a valid IP (both v4 and v6) or a valid DNS name +func IsHost(str string) bool { + return IsIP(str) || IsDNSName(str) +} + +// IsMongoID check if the string is a valid hex-encoded representation of a MongoDB ObjectId. +func IsMongoID(str string) bool { + return rxHexadecimal.MatchString(str) && (len(str) == 24) +} + +// IsLatitude check if a string is valid latitude. +func IsLatitude(str string) bool { + return rxLatitude.MatchString(str) +} + +// IsLongitude check if a string is valid longitude. +func IsLongitude(str string) bool { + return rxLongitude.MatchString(str) +} + +// IsRsaPublicKey check if a string is valid public key with provided length +func IsRsaPublicKey(str string, keylen int) bool { + bb := bytes.NewBufferString(str) + pemBytes, err := ioutil.ReadAll(bb) + if err != nil { + return false + } + block, _ := pem.Decode(pemBytes) + if block != nil && block.Type != "PUBLIC KEY" { + return false + } + var der []byte + + if block != nil { + der = block.Bytes + } else { + der, err = base64.StdEncoding.DecodeString(str) + if err != nil { + return false + } + } + + key, err := x509.ParsePKIXPublicKey(der) + if err != nil { + return false + } + pubkey, ok := key.(*rsa.PublicKey) + if !ok { + return false + } + bitlen := len(pubkey.N.Bytes()) * 8 + return bitlen == int(keylen) +} + +func toJSONName(tag string) string { + if tag == "" { + return "" + } + + // JSON name always comes first. If there's no options then split[0] is + // JSON name, if JSON name is not set, then split[0] is an empty string. + split := strings.SplitN(tag, ",", 2) + + name := split[0] + + // However it is possible that the field is skipped when + // (de-)serializing from/to JSON, in which case assume that there is no + // tag name to use + if name == "-" { + return "" + } + return name +} + +// ValidateStruct use tags for fields. +// result will be equal to `false` if there are any errors. +func ValidateStruct(s interface{}) (bool, error) { + if s == nil { + return true, nil + } + result := true + var err error + val := reflect.ValueOf(s) + if val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr { + val = val.Elem() + } + // we only accept structs + if val.Kind() != reflect.Struct { + return false, fmt.Errorf("function only accepts structs; got %s", val.Kind()) + } + var errs Errors + for i := 0; i < val.NumField(); i++ { + valueField := val.Field(i) + typeField := val.Type().Field(i) + if typeField.PkgPath != "" { + continue // Private field + } + structResult := true + if (valueField.Kind() == reflect.Struct || + (valueField.Kind() == reflect.Ptr && valueField.Elem().Kind() == reflect.Struct)) && + typeField.Tag.Get(tagName) != "-" { + var err error + structResult, err = ValidateStruct(valueField.Interface()) + if err != nil { + errs = append(errs, err) + } + } + resultField, err2 := typeCheck(valueField, typeField, val, nil) + if err2 != nil { + + // Replace structure name with JSON name if there is a tag on the variable + jsonTag := toJSONName(typeField.Tag.Get("json")) + if jsonTag != "" { + switch jsonError := err2.(type) { + case Error: + jsonError.Name = jsonTag + err2 = jsonError + case Errors: + for i2, err3 := range jsonError { + switch customErr := err3.(type) { + case Error: + customErr.Name = jsonTag + jsonError[i2] = customErr + } + } + + err2 = jsonError + } + } + + errs = append(errs, err2) + } + result = result && resultField && structResult + } + if len(errs) > 0 { + err = errs + } + return result, err +} + +// parseTagIntoMap parses a struct tag `valid:required~Some error message,length(2|3)` into map[string]string{"required": "Some error message", "length(2|3)": ""} +func parseTagIntoMap(tag string) tagOptionsMap { + optionsMap := make(tagOptionsMap) + options := strings.Split(tag, ",") + + for _, option := range options { + option = strings.TrimSpace(option) + + validationOptions := strings.Split(option, "~") + if !isValidTag(validationOptions[0]) { + continue + } + if len(validationOptions) == 2 { + optionsMap[validationOptions[0]] = validationOptions[1] + } else { + optionsMap[validationOptions[0]] = "" + } + } + return optionsMap +} + +func isValidTag(s string) bool { + if s == "" { + return false + } + for _, c := range s { + switch { + case strings.ContainsRune("\\'\"!#$%&()*+-./:<=>?@[]^_{|}~ ", c): + // Backslash and quote chars are reserved, but + // otherwise any punctuation chars are allowed + // in a tag name. + default: + if !unicode.IsLetter(c) && !unicode.IsDigit(c) { + return false + } + } + } + return true +} + +// IsSSN will validate the given string as a U.S. Social Security Number +func IsSSN(str string) bool { + if str == "" || len(str) != 11 { + return false + } + return rxSSN.MatchString(str) +} + +// IsSemver check if string is valid semantic version +func IsSemver(str string) bool { + return rxSemver.MatchString(str) +} + +// IsTime check if string is valid according to given format +func IsTime(str string, format string) bool { + _, err := time.Parse(format, str) + return err == nil +} + +// IsRFC3339 check if string is valid timestamp value according to RFC3339 +func IsRFC3339(str string) bool { + return IsTime(str, time.RFC3339) +} + +// IsRFC3339WithoutZone check if string is valid timestamp value according to RFC3339 which excludes the timezone. +func IsRFC3339WithoutZone(str string) bool { + return IsTime(str, RF3339WithoutZone) +} + +// IsISO4217 check if string is valid ISO currency code +func IsISO4217(str string) bool { + for _, currency := range ISO4217List { + if str == currency { + return true + } + } + + return false +} + +// ByteLength check string's length +func ByteLength(str string, params ...string) bool { + if len(params) == 2 { + min, _ := ToInt(params[0]) + max, _ := ToInt(params[1]) + return len(str) >= int(min) && len(str) <= int(max) + } + + return false +} + +// RuneLength check string's length +// Alias for StringLength +func RuneLength(str string, params ...string) bool { + return StringLength(str, params...) +} + +// IsRsaPub check whether string is valid RSA key +// Alias for IsRsaPublicKey +func IsRsaPub(str string, params ...string) bool { + if len(params) == 1 { + len, _ := ToInt(params[0]) + return IsRsaPublicKey(str, int(len)) + } + + return false +} + +// StringMatches checks if a string matches a given pattern. +func StringMatches(s string, params ...string) bool { + if len(params) == 1 { + pattern := params[0] + return Matches(s, pattern) + } + return false +} + +// StringLength check string's length (including multi byte strings) +func StringLength(str string, params ...string) bool { + + if len(params) == 2 { + strLength := utf8.RuneCountInString(str) + min, _ := ToInt(params[0]) + max, _ := ToInt(params[1]) + return strLength >= int(min) && strLength <= int(max) + } + + return false +} + +// Range check string's length +func Range(str string, params ...string) bool { + if len(params) == 2 { + value, _ := ToFloat(str) + min, _ := ToFloat(params[0]) + max, _ := ToFloat(params[1]) + return InRange(value, min, max) + } + + return false +} + +func isInRaw(str string, params ...string) bool { + if len(params) == 1 { + rawParams := params[0] + + parsedParams := strings.Split(rawParams, "|") + + return IsIn(str, parsedParams...) + } + + return false +} + +// IsIn check if string str is a member of the set of strings params +func IsIn(str string, params ...string) bool { + for _, param := range params { + if str == param { + return true + } + } + + return false +} + +func checkRequired(v reflect.Value, t reflect.StructField, options tagOptionsMap) (bool, error) { + if requiredOption, isRequired := options["required"]; isRequired { + if len(requiredOption) > 0 { + return false, Error{t.Name, fmt.Errorf(requiredOption), true, "required"} + } + return false, Error{t.Name, fmt.Errorf("non zero value required"), false, "required"} + } else if _, isOptional := options["optional"]; fieldsRequiredByDefault && !isOptional { + return false, Error{t.Name, fmt.Errorf("Missing required field"), false, "required"} + } + // not required and empty is valid + return true, nil +} + +func typeCheck(v reflect.Value, t reflect.StructField, o reflect.Value, options tagOptionsMap) (isValid bool, resultErr error) { + if !v.IsValid() { + return false, nil + } + + tag := t.Tag.Get(tagName) + + // Check if the field should be ignored + switch tag { + case "": + if !fieldsRequiredByDefault { + return true, nil + } + return false, Error{t.Name, fmt.Errorf("All fields are required to at least have one validation defined"), false, "required"} + case "-": + return true, nil + } + + isRootType := false + if options == nil { + isRootType = true + options = parseTagIntoMap(tag) + } + + if isEmptyValue(v) { + // an empty value is not validated, check only required + return checkRequired(v, t, options) + } + + var customTypeErrors Errors + for validatorName, customErrorMessage := range options { + if validatefunc, ok := CustomTypeTagMap.Get(validatorName); ok { + delete(options, validatorName) + + if result := validatefunc(v.Interface(), o.Interface()); !result { + if len(customErrorMessage) > 0 { + customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf(customErrorMessage), CustomErrorMessageExists: true, Validator: stripParams(validatorName)}) + continue + } + customTypeErrors = append(customTypeErrors, Error{Name: t.Name, Err: fmt.Errorf("%s does not validate as %s", fmt.Sprint(v), validatorName), CustomErrorMessageExists: false, Validator: stripParams(validatorName)}) + } + } + } + + if len(customTypeErrors.Errors()) > 0 { + return false, customTypeErrors + } + + if isRootType { + // Ensure that we've checked the value by all specified validators before report that the value is valid + defer func() { + delete(options, "optional") + delete(options, "required") + + if isValid && resultErr == nil && len(options) != 0 { + for validator := range options { + isValid = false + resultErr = Error{t.Name, fmt.Errorf( + "The following validator is invalid or can't be applied to the field: %q", validator), false, stripParams(validator)} + return + } + } + }() + } + + switch v.Kind() { + case reflect.Bool, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, + reflect.Float32, reflect.Float64, + reflect.String: + // for each tag option check the map of validator functions + for validatorSpec, customErrorMessage := range options { + var negate bool + validator := validatorSpec + customMsgExists := len(customErrorMessage) > 0 + + // Check whether the tag looks like '!something' or 'something' + if validator[0] == '!' { + validator = validator[1:] + negate = true + } + + // Check for param validators + for key, value := range ParamTagRegexMap { + ps := value.FindStringSubmatch(validator) + if len(ps) == 0 { + continue + } + + validatefunc, ok := ParamTagMap[key] + if !ok { + continue + } + + delete(options, validatorSpec) + + switch v.Kind() { + case reflect.String, + reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float32, reflect.Float64: + + field := fmt.Sprint(v) // make value into string, then validate with regex + if result := validatefunc(field, ps[1:]...); (!result && !negate) || (result && negate) { + if customMsgExists { + return false, Error{t.Name, fmt.Errorf(customErrorMessage), customMsgExists, stripParams(validatorSpec)} + } + if negate { + return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)} + } + return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)} + } + default: + // type not yet supported, fail + return false, Error{t.Name, fmt.Errorf("Validator %s doesn't support kind %s", validator, v.Kind()), false, stripParams(validatorSpec)} + } + } + + if validatefunc, ok := TagMap[validator]; ok { + delete(options, validatorSpec) + + switch v.Kind() { + case reflect.String: + field := fmt.Sprint(v) // make value into string, then validate with regex + if result := validatefunc(field); !result && !negate || result && negate { + if customMsgExists { + return false, Error{t.Name, fmt.Errorf(customErrorMessage), customMsgExists, stripParams(validatorSpec)} + } + if negate { + return false, Error{t.Name, fmt.Errorf("%s does validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)} + } + return false, Error{t.Name, fmt.Errorf("%s does not validate as %s", field, validator), customMsgExists, stripParams(validatorSpec)} + } + default: + //Not Yet Supported Types (Fail here!) + err := fmt.Errorf("Validator %s doesn't support kind %s for value %v", validator, v.Kind(), v) + return false, Error{t.Name, err, false, stripParams(validatorSpec)} + } + } + } + return true, nil + case reflect.Map: + if v.Type().Key().Kind() != reflect.String { + return false, &UnsupportedTypeError{v.Type()} + } + var sv stringValues + sv = v.MapKeys() + sort.Sort(sv) + result := true + for _, k := range sv { + var resultItem bool + var err error + if v.MapIndex(k).Kind() != reflect.Struct { + resultItem, err = typeCheck(v.MapIndex(k), t, o, options) + if err != nil { + return false, err + } + } else { + resultItem, err = ValidateStruct(v.MapIndex(k).Interface()) + if err != nil { + return false, err + } + } + result = result && resultItem + } + return result, nil + case reflect.Slice, reflect.Array: + result := true + for i := 0; i < v.Len(); i++ { + var resultItem bool + var err error + if v.Index(i).Kind() != reflect.Struct { + resultItem, err = typeCheck(v.Index(i), t, o, options) + if err != nil { + return false, err + } + } else { + resultItem, err = ValidateStruct(v.Index(i).Interface()) + if err != nil { + return false, err + } + } + result = result && resultItem + } + return result, nil + case reflect.Interface: + // If the value is an interface then encode its element + if v.IsNil() { + return true, nil + } + return ValidateStruct(v.Interface()) + case reflect.Ptr: + // If the value is a pointer then check its element + if v.IsNil() { + return true, nil + } + return typeCheck(v.Elem(), t, o, options) + case reflect.Struct: + return ValidateStruct(v.Interface()) + default: + return false, &UnsupportedTypeError{v.Type()} + } +} + +func stripParams(validatorString string) string { + return paramsRegexp.ReplaceAllString(validatorString, "") +} + +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.String, reflect.Array: + return v.Len() == 0 + case reflect.Map, reflect.Slice: + return v.Len() == 0 || v.IsNil() + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + return reflect.DeepEqual(v.Interface(), reflect.Zero(v.Type()).Interface()) +} + +// ErrorByField returns error for specified field of the struct +// validated by ValidateStruct or empty string if there are no errors +// or this field doesn't exists or doesn't have any errors. +func ErrorByField(e error, field string) string { + if e == nil { + return "" + } + return ErrorsByField(e)[field] +} + +// ErrorsByField returns map of errors of the struct validated +// by ValidateStruct or empty map if there are no errors. +func ErrorsByField(e error) map[string]string { + m := make(map[string]string) + if e == nil { + return m + } + // prototype for ValidateStruct + + switch e.(type) { + case Error: + m[e.(Error).Name] = e.(Error).Err.Error() + case Errors: + for _, item := range e.(Errors).Errors() { + n := ErrorsByField(item) + for k, v := range n { + m[k] = v + } + } + } + + return m +} + +// Error returns string equivalent for reflect.Type +func (e *UnsupportedTypeError) Error() string { + return "validator: unsupported type: " + e.Type.String() +} + +func (sv stringValues) Len() int { return len(sv) } +func (sv stringValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] } +func (sv stringValues) Less(i, j int) bool { return sv.get(i) < sv.get(j) } +func (sv stringValues) get(i int) string { return sv[i].String() } diff --git a/vendor/github.com/fatih/structs/LICENSE b/vendor/github.com/fatih/structs/LICENSE new file mode 100644 index 000000000..34504e4b3 --- /dev/null +++ b/vendor/github.com/fatih/structs/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Fatih Arslan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/fatih/structs/field.go b/vendor/github.com/fatih/structs/field.go new file mode 100644 index 000000000..e69783230 --- /dev/null +++ b/vendor/github.com/fatih/structs/field.go @@ -0,0 +1,141 @@ +package structs + +import ( + "errors" + "fmt" + "reflect" +) + +var ( + errNotExported = errors.New("field is not exported") + errNotSettable = errors.New("field is not settable") +) + +// Field represents a single struct field that encapsulates high level +// functions around the field. +type Field struct { + value reflect.Value + field reflect.StructField + defaultTag string +} + +// Tag returns the value associated with key in the tag string. If there is no +// such key in the tag, Tag returns the empty string. +func (f *Field) Tag(key string) string { + return f.field.Tag.Get(key) +} + +// Value returns the underlying value of the field. It panics if the field +// is not exported. +func (f *Field) Value() interface{} { + return f.value.Interface() +} + +// IsEmbedded returns true if the given field is an anonymous field (embedded) +func (f *Field) IsEmbedded() bool { + return f.field.Anonymous +} + +// IsExported returns true if the given field is exported. +func (f *Field) IsExported() bool { + return f.field.PkgPath == "" +} + +// IsZero returns true if the given field is not initialized (has a zero value). +// It panics if the field is not exported. +func (f *Field) IsZero() bool { + zero := reflect.Zero(f.value.Type()).Interface() + current := f.Value() + + return reflect.DeepEqual(current, zero) +} + +// Name returns the name of the given field +func (f *Field) Name() string { + return f.field.Name +} + +// Kind returns the fields kind, such as "string", "map", "bool", etc .. +func (f *Field) Kind() reflect.Kind { + return f.value.Kind() +} + +// Set sets the field to given value v. It returns an error if the field is not +// settable (not addressable or not exported) or if the given value's type +// doesn't match the fields type. +func (f *Field) Set(val interface{}) error { + // we can't set unexported fields, so be sure this field is exported + if !f.IsExported() { + return errNotExported + } + + // do we get here? not sure... + if !f.value.CanSet() { + return errNotSettable + } + + given := reflect.ValueOf(val) + + if f.value.Kind() != given.Kind() { + return fmt.Errorf("wrong kind. got: %s want: %s", given.Kind(), f.value.Kind()) + } + + f.value.Set(given) + return nil +} + +// Zero sets the field to its zero value. It returns an error if the field is not +// settable (not addressable or not exported). +func (f *Field) Zero() error { + zero := reflect.Zero(f.value.Type()).Interface() + return f.Set(zero) +} + +// Fields returns a slice of Fields. This is particular handy to get the fields +// of a nested struct . A struct tag with the content of "-" ignores the +// checking of that particular field. Example: +// +// // Field is ignored by this package. +// Field *http.Request `structs:"-"` +// +// It panics if field is not exported or if field's kind is not struct +func (f *Field) Fields() []*Field { + return getFields(f.value, f.defaultTag) +} + +// Field returns the field from a nested struct. It panics if the nested struct +// is not exported or if the field was not found. +func (f *Field) Field(name string) *Field { + field, ok := f.FieldOk(name) + if !ok { + panic("field not found") + } + + return field +} + +// FieldOk returns the field from a nested struct. The boolean returns whether +// the field was found (true) or not (false). +func (f *Field) FieldOk(name string) (*Field, bool) { + value := &f.value + // value must be settable so we need to make sure it holds the address of the + // variable and not a copy, so we can pass the pointer to strctVal instead of a + // copy (which is not assigned to any variable, hence not settable). + // see "https://blog.golang.org/laws-of-reflection#TOC_8." + if f.value.Kind() != reflect.Ptr { + a := f.value.Addr() + value = &a + } + v := strctVal(value.Interface()) + t := v.Type() + + field, ok := t.FieldByName(name) + if !ok { + return nil, false + } + + return &Field{ + field: field, + value: v.FieldByName(name), + }, true +} diff --git a/vendor/github.com/fatih/structs/structs.go b/vendor/github.com/fatih/structs/structs.go new file mode 100644 index 000000000..3a8770652 --- /dev/null +++ b/vendor/github.com/fatih/structs/structs.go @@ -0,0 +1,584 @@ +// Package structs contains various utilities functions to work with structs. +package structs + +import ( + "fmt" + + "reflect" +) + +var ( + // DefaultTagName is the default tag name for struct fields which provides + // a more granular to tweak certain structs. Lookup the necessary functions + // for more info. + DefaultTagName = "structs" // struct's field default tag name +) + +// Struct encapsulates a struct type to provide several high level functions +// around the struct. +type Struct struct { + raw interface{} + value reflect.Value + TagName string +} + +// New returns a new *Struct with the struct s. It panics if the s's kind is +// not struct. +func New(s interface{}) *Struct { + return &Struct{ + raw: s, + value: strctVal(s), + TagName: DefaultTagName, + } +} + +// Map converts the given struct to a map[string]interface{}, where the keys +// of the map are the field names and the values of the map the associated +// values of the fields. The default key string is the struct field name but +// can be changed in the struct field's tag value. The "structs" key in the +// struct's field tag value is the key name. Example: +// +// // Field appears in map as key "myName". +// Name string `structs:"myName"` +// +// A tag value with the content of "-" ignores that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// A tag value with the content of "string" uses the stringer to get the value. Example: +// +// // The value will be output of Animal's String() func. +// // Map will panic if Animal does not implement String(). +// Field *Animal `structs:"field,string"` +// +// A tag value with the option of "flatten" used in a struct field is to flatten its fields +// in the output map. Example: +// +// // The FieldStruct's fields will be flattened into the output map. +// FieldStruct time.Time `structs:",flatten"` +// +// A tag value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Field is not processed further by this package. +// Field time.Time `structs:"myName,omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// A tag value with the option of "omitempty" ignores that particular field if +// the field value is empty. Example: +// +// // Field appears in map as key "myName", but the field is +// // skipped if empty. +// Field string `structs:"myName,omitempty"` +// +// // Field appears in map as key "Field" (the default), but +// // the field is skipped if empty. +// Field string `structs:",omitempty"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. +func (s *Struct) Map() map[string]interface{} { + out := make(map[string]interface{}) + s.FillMap(out) + return out +} + +// FillMap is the same as Map. Instead of returning the output, it fills the +// given map. +func (s *Struct) FillMap(out map[string]interface{}) { + if out == nil { + return + } + + fields := s.structFields() + + for _, field := range fields { + name := field.Name + val := s.value.FieldByName(name) + isSubStruct := false + var finalVal interface{} + + tagName, tagOpts := parseTag(field.Tag.Get(s.TagName)) + if tagName != "" { + name = tagName + } + + // if the value is a zero value and the field is marked as omitempty do + // not include + if tagOpts.Has("omitempty") { + zero := reflect.Zero(val.Type()).Interface() + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + continue + } + } + + if !tagOpts.Has("omitnested") { + finalVal = s.nested(val) + + v := reflect.ValueOf(val.Interface()) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + switch v.Kind() { + case reflect.Map, reflect.Struct: + isSubStruct = true + } + } else { + finalVal = val.Interface() + } + + if tagOpts.Has("string") { + s, ok := val.Interface().(fmt.Stringer) + if ok { + out[name] = s.String() + } + continue + } + + if isSubStruct && (tagOpts.Has("flatten")) { + for k := range finalVal.(map[string]interface{}) { + out[k] = finalVal.(map[string]interface{})[k] + } + } else { + out[name] = finalVal + } + } +} + +// Values converts the given s struct's field values to a []interface{}. A +// struct tag with the content of "-" ignores the that particular field. +// Example: +// +// // Field is ignored by this package. +// Field int `structs:"-"` +// +// A value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Fields is not processed further by this package. +// Field time.Time `structs:",omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// A tag value with the option of "omitempty" ignores that particular field and +// is not added to the values if the field value is empty. Example: +// +// // Field is skipped if empty +// Field string `structs:",omitempty"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. +func (s *Struct) Values() []interface{} { + fields := s.structFields() + + var t []interface{} + + for _, field := range fields { + val := s.value.FieldByName(field.Name) + + _, tagOpts := parseTag(field.Tag.Get(s.TagName)) + + // if the value is a zero value and the field is marked as omitempty do + // not include + if tagOpts.Has("omitempty") { + zero := reflect.Zero(val.Type()).Interface() + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + continue + } + } + + if tagOpts.Has("string") { + s, ok := val.Interface().(fmt.Stringer) + if ok { + t = append(t, s.String()) + } + continue + } + + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { + // look out for embedded structs, and convert them to a + // []interface{} to be added to the final values slice + t = append(t, Values(val.Interface())...) + } else { + t = append(t, val.Interface()) + } + } + + return t +} + +// Fields returns a slice of Fields. A struct tag with the content of "-" +// ignores the checking of that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// It panics if s's kind is not struct. +func (s *Struct) Fields() []*Field { + return getFields(s.value, s.TagName) +} + +// Names returns a slice of field names. A struct tag with the content of "-" +// ignores the checking of that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// It panics if s's kind is not struct. +func (s *Struct) Names() []string { + fields := getFields(s.value, s.TagName) + + names := make([]string, len(fields)) + + for i, field := range fields { + names[i] = field.Name() + } + + return names +} + +func getFields(v reflect.Value, tagName string) []*Field { + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + t := v.Type() + + var fields []*Field + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + + if tag := field.Tag.Get(tagName); tag == "-" { + continue + } + + f := &Field{ + field: field, + value: v.FieldByName(field.Name), + } + + fields = append(fields, f) + + } + + return fields +} + +// Field returns a new Field struct that provides several high level functions +// around a single struct field entity. It panics if the field is not found. +func (s *Struct) Field(name string) *Field { + f, ok := s.FieldOk(name) + if !ok { + panic("field not found") + } + + return f +} + +// FieldOk returns a new Field struct that provides several high level functions +// around a single struct field entity. The boolean returns true if the field +// was found. +func (s *Struct) FieldOk(name string) (*Field, bool) { + t := s.value.Type() + + field, ok := t.FieldByName(name) + if !ok { + return nil, false + } + + return &Field{ + field: field, + value: s.value.FieldByName(name), + defaultTag: s.TagName, + }, true +} + +// IsZero returns true if all fields in a struct is a zero value (not +// initialized) A struct tag with the content of "-" ignores the checking of +// that particular field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// A value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Field is not processed further by this package. +// Field time.Time `structs:"myName,omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. It panics if s's kind is not struct. +func (s *Struct) IsZero() bool { + fields := s.structFields() + + for _, field := range fields { + val := s.value.FieldByName(field.Name) + + _, tagOpts := parseTag(field.Tag.Get(s.TagName)) + + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { + ok := IsZero(val.Interface()) + if !ok { + return false + } + + continue + } + + // zero value of the given field, such as "" for string, 0 for int + zero := reflect.Zero(val.Type()).Interface() + + // current value of the given field + current := val.Interface() + + if !reflect.DeepEqual(current, zero) { + return false + } + } + + return true +} + +// HasZero returns true if a field in a struct is not initialized (zero value). +// A struct tag with the content of "-" ignores the checking of that particular +// field. Example: +// +// // Field is ignored by this package. +// Field bool `structs:"-"` +// +// A value with the option of "omitnested" stops iterating further if the type +// is a struct. Example: +// +// // Field is not processed further by this package. +// Field time.Time `structs:"myName,omitnested"` +// Field *http.Request `structs:",omitnested"` +// +// Note that only exported fields of a struct can be accessed, non exported +// fields will be neglected. It panics if s's kind is not struct. +func (s *Struct) HasZero() bool { + fields := s.structFields() + + for _, field := range fields { + val := s.value.FieldByName(field.Name) + + _, tagOpts := parseTag(field.Tag.Get(s.TagName)) + + if IsStruct(val.Interface()) && !tagOpts.Has("omitnested") { + ok := HasZero(val.Interface()) + if ok { + return true + } + + continue + } + + // zero value of the given field, such as "" for string, 0 for int + zero := reflect.Zero(val.Type()).Interface() + + // current value of the given field + current := val.Interface() + + if reflect.DeepEqual(current, zero) { + return true + } + } + + return false +} + +// Name returns the structs's type name within its package. For more info refer +// to Name() function. +func (s *Struct) Name() string { + return s.value.Type().Name() +} + +// structFields returns the exported struct fields for a given s struct. This +// is a convenient helper method to avoid duplicate code in some of the +// functions. +func (s *Struct) structFields() []reflect.StructField { + t := s.value.Type() + + var f []reflect.StructField + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + // we can't access the value of unexported fields + if field.PkgPath != "" { + continue + } + + // don't check if it's omitted + if tag := field.Tag.Get(s.TagName); tag == "-" { + continue + } + + f = append(f, field) + } + + return f +} + +func strctVal(s interface{}) reflect.Value { + v := reflect.ValueOf(s) + + // if pointer get the underlying element≤ + for v.Kind() == reflect.Ptr { + v = v.Elem() + } + + if v.Kind() != reflect.Struct { + panic("not struct") + } + + return v +} + +// Map converts the given struct to a map[string]interface{}. For more info +// refer to Struct types Map() method. It panics if s's kind is not struct. +func Map(s interface{}) map[string]interface{} { + return New(s).Map() +} + +// FillMap is the same as Map. Instead of returning the output, it fills the +// given map. +func FillMap(s interface{}, out map[string]interface{}) { + New(s).FillMap(out) +} + +// Values converts the given struct to a []interface{}. For more info refer to +// Struct types Values() method. It panics if s's kind is not struct. +func Values(s interface{}) []interface{} { + return New(s).Values() +} + +// Fields returns a slice of *Field. For more info refer to Struct types +// Fields() method. It panics if s's kind is not struct. +func Fields(s interface{}) []*Field { + return New(s).Fields() +} + +// Names returns a slice of field names. For more info refer to Struct types +// Names() method. It panics if s's kind is not struct. +func Names(s interface{}) []string { + return New(s).Names() +} + +// IsZero returns true if all fields is equal to a zero value. For more info +// refer to Struct types IsZero() method. It panics if s's kind is not struct. +func IsZero(s interface{}) bool { + return New(s).IsZero() +} + +// HasZero returns true if any field is equal to a zero value. For more info +// refer to Struct types HasZero() method. It panics if s's kind is not struct. +func HasZero(s interface{}) bool { + return New(s).HasZero() +} + +// IsStruct returns true if the given variable is a struct or a pointer to +// struct. +func IsStruct(s interface{}) bool { + v := reflect.ValueOf(s) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + // uninitialized zero value of a struct + if v.Kind() == reflect.Invalid { + return false + } + + return v.Kind() == reflect.Struct +} + +// Name returns the structs's type name within its package. It returns an +// empty string for unnamed types. It panics if s's kind is not struct. +func Name(s interface{}) string { + return New(s).Name() +} + +// nested retrieves recursively all types for the given value and returns the +// nested value. +func (s *Struct) nested(val reflect.Value) interface{} { + var finalVal interface{} + + v := reflect.ValueOf(val.Interface()) + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + + switch v.Kind() { + case reflect.Struct: + n := New(val.Interface()) + n.TagName = s.TagName + m := n.Map() + + // do not add the converted value if there are no exported fields, ie: + // time.Time + if len(m) == 0 { + finalVal = val.Interface() + } else { + finalVal = m + } + case reflect.Map: + // get the element type of the map + mapElem := val.Type() + switch val.Type().Kind() { + case reflect.Ptr, reflect.Array, reflect.Map, + reflect.Slice, reflect.Chan: + mapElem = val.Type().Elem() + if mapElem.Kind() == reflect.Ptr { + mapElem = mapElem.Elem() + } + } + + // only iterate over struct types, ie: map[string]StructType, + // map[string][]StructType, + if mapElem.Kind() == reflect.Struct || + (mapElem.Kind() == reflect.Slice && + mapElem.Elem().Kind() == reflect.Struct) { + m := make(map[string]interface{}, val.Len()) + for _, k := range val.MapKeys() { + m[k.String()] = s.nested(val.MapIndex(k)) + } + finalVal = m + break + } + + // TODO(arslan): should this be optional? + finalVal = val.Interface() + case reflect.Slice, reflect.Array: + if val.Type().Kind() == reflect.Interface { + finalVal = val.Interface() + break + } + + // TODO(arslan): should this be optional? + // do not iterate of non struct types, just pass the value. Ie: []int, + // []string, co... We only iterate further if it's a struct. + // i.e []foo or []*foo + if val.Type().Elem().Kind() != reflect.Struct && + !(val.Type().Elem().Kind() == reflect.Ptr && + val.Type().Elem().Elem().Kind() == reflect.Struct) { + finalVal = val.Interface() + break + } + + slices := make([]interface{}, val.Len()) + for x := 0; x < val.Len(); x++ { + slices[x] = s.nested(val.Index(x)) + } + finalVal = slices + default: + finalVal = val.Interface() + } + + return finalVal +} diff --git a/vendor/github.com/fatih/structs/tags.go b/vendor/github.com/fatih/structs/tags.go new file mode 100644 index 000000000..136a31eba --- /dev/null +++ b/vendor/github.com/fatih/structs/tags.go @@ -0,0 +1,32 @@ +package structs + +import "strings" + +// tagOptions contains a slice of tag options +type tagOptions []string + +// Has returns true if the given option is available in tagOptions +func (t tagOptions) Has(opt string) bool { + for _, tagOpt := range t { + if tagOpt == opt { + return true + } + } + + return false +} + +// parseTag splits a struct field's tag into its name and a list of options +// which comes after a name. A tag is in the form of: "name,option1,option2". +// The name can be neglectected. +func parseTag(tag string) (string, tagOptions) { + // tag is one of followings: + // "" + // "name" + // "name,opt" + // "name,opt,opt2" + // ",opt" + + res := strings.Split(tag, ",") + return res[0], res[1:] +} diff --git a/vendor/github.com/gocraft/dbr/LICENSE b/vendor/github.com/gocraft/dbr/LICENSE new file mode 100644 index 000000000..895d04a35 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014-2017 Jonathan Novak, Tai-Lin Chu + +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. diff --git a/vendor/github.com/gocraft/dbr/buffer.go b/vendor/github.com/gocraft/dbr/buffer.go new file mode 100644 index 000000000..72b885de1 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/buffer.go @@ -0,0 +1,29 @@ +package dbr + +import "bytes" + +type Buffer interface { + WriteString(s string) (n int, err error) + String() string + + WriteValue(v ...interface{}) (err error) + Value() []interface{} +} + +type buffer struct { + bytes.Buffer + v []interface{} +} + +func NewBuffer() Buffer { + return &buffer{} +} + +func (b *buffer) WriteValue(v ...interface{}) error { + b.v = append(b.v, v...) + return nil +} + +func (b *buffer) Value() []interface{} { + return b.v +} diff --git a/vendor/github.com/gocraft/dbr/builder.go b/vendor/github.com/gocraft/dbr/builder.go new file mode 100644 index 000000000..625ceb060 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/builder.go @@ -0,0 +1,13 @@ +package dbr + +// Builder builds sql in one dialect like MySQL/PostgreSQL +// e.g. XxxBuilder +type Builder interface { + Build(Dialect, Buffer) error +} + +type BuildFunc func(Dialect, Buffer) error + +func (b BuildFunc) Build(d Dialect, buf Buffer) error { + return b(d, buf) +} diff --git a/vendor/github.com/gocraft/dbr/condition.go b/vendor/github.com/gocraft/dbr/condition.go new file mode 100644 index 000000000..ff299793c --- /dev/null +++ b/vendor/github.com/gocraft/dbr/condition.go @@ -0,0 +1,119 @@ +package dbr + +import "reflect" + +func buildCond(d Dialect, buf Buffer, pred string, cond ...Builder) error { + for i, c := range cond { + if i > 0 { + buf.WriteString(" ") + buf.WriteString(pred) + buf.WriteString(" ") + } + buf.WriteString("(") + err := c.Build(d, buf) + if err != nil { + return err + } + buf.WriteString(")") + } + return nil +} + +// And creates AND from a list of conditions +func And(cond ...Builder) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + return buildCond(d, buf, "AND", cond...) + }) +} + +// Or creates OR from a list of conditions +func Or(cond ...Builder) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + return buildCond(d, buf, "OR", cond...) + }) +} + +func buildCmp(d Dialect, buf Buffer, pred string, column string, value interface{}) error { + buf.WriteString(d.QuoteIdent(column)) + buf.WriteString(" ") + buf.WriteString(pred) + buf.WriteString(" ") + buf.WriteString(placeholder) + + buf.WriteValue(value) + return nil +} + +// Eq is `=`. +// When value is nil, it will be translated to `IS NULL`. +// When value is a slice, it will be translated to `IN`. +// Otherwise it will be translated to `=`. +func Eq(column string, value interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + if value == nil { + buf.WriteString(d.QuoteIdent(column)) + buf.WriteString(" IS NULL") + return nil + } + v := reflect.ValueOf(value) + if v.Kind() == reflect.Slice { + if v.Len() == 0 { + buf.WriteString(d.EncodeBool(false)) + return nil + } + return buildCmp(d, buf, "IN", column, value) + } + return buildCmp(d, buf, "=", column, value) + }) +} + +// Neq is `!=`. +// When value is nil, it will be translated to `IS NOT NULL`. +// When value is a slice, it will be translated to `NOT IN`. +// Otherwise it will be translated to `!=`. +func Neq(column string, value interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + if value == nil { + buf.WriteString(d.QuoteIdent(column)) + buf.WriteString(" IS NOT NULL") + return nil + } + v := reflect.ValueOf(value) + if v.Kind() == reflect.Slice { + if v.Len() == 0 { + buf.WriteString(d.EncodeBool(true)) + return nil + } + return buildCmp(d, buf, "NOT IN", column, value) + } + return buildCmp(d, buf, "!=", column, value) + }) +} + +// Gt is `>`. +func Gt(column string, value interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + return buildCmp(d, buf, ">", column, value) + }) +} + +// Gte is '>='. +func Gte(column string, value interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + return buildCmp(d, buf, ">=", column, value) + }) +} + +// Lt is '<'. +func Lt(column string, value interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + return buildCmp(d, buf, "<", column, value) + }) +} + +// Lte is `<=`. +func Lte(column string, value interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + return buildCmp(d, buf, "<=", column, value) + }) +} diff --git a/vendor/github.com/gocraft/dbr/dbr.go b/vendor/github.com/gocraft/dbr/dbr.go new file mode 100644 index 000000000..3e3cf91a3 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/dbr.go @@ -0,0 +1,174 @@ +package dbr + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/gocraft/dbr/dialect" +) + +// Open instantiates a Connection for a given database/sql connection +// and event receiver +func Open(driver, dsn string, log EventReceiver) (*Connection, error) { + if log == nil { + log = nullReceiver + } + conn, err := sql.Open(driver, dsn) + if err != nil { + return nil, err + } + var d Dialect + switch driver { + case "mysql": + d = dialect.MySQL + case "postgres": + d = dialect.PostgreSQL + case "sqlite3": + d = dialect.SQLite3 + default: + return nil, ErrNotSupported + } + return &Connection{DB: conn, EventReceiver: log, Dialect: d}, nil +} + +const ( + placeholder = "?" +) + +// Connection is a connection to the database with an EventReceiver +// to send events, errors, and timings to +type Connection struct { + *sql.DB + Dialect Dialect + EventReceiver +} + +// Session represents a business unit of execution for some connection +type Session struct { + *Connection + EventReceiver + Timeout time.Duration +} + +func (s *Session) GetTimeout() time.Duration { + return s.Timeout +} + +// NewSession instantiates a Session for the Connection +func (conn *Connection) NewSession(log EventReceiver) *Session { + if log == nil { + log = conn.EventReceiver // Use parent instrumentation + } + return &Session{Connection: conn, EventReceiver: log} +} + +// Ensure that tx and session are session runner +var ( + _ SessionRunner = (*Tx)(nil) + _ SessionRunner = (*Session)(nil) +) + +// SessionRunner can do anything that a Session can except start a transaction. +type SessionRunner interface { + Select(column ...string) *SelectBuilder + SelectBySql(query string, value ...interface{}) *SelectBuilder + + InsertInto(table string) *InsertBuilder + InsertBySql(query string, value ...interface{}) *InsertBuilder + + Update(table string) *UpdateBuilder + UpdateBySql(query string, value ...interface{}) *UpdateBuilder + + DeleteFrom(table string) *DeleteBuilder + DeleteBySql(query string, value ...interface{}) *DeleteBuilder +} + +type runner interface { + GetTimeout() time.Duration + ExecContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) + QueryContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) +} + +func exec(ctx context.Context, runner runner, log EventReceiver, builder Builder, d Dialect) (sql.Result, error) { + timeout := runner.GetTimeout() + if timeout > 0 { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + } + + i := interpolator{ + Buffer: NewBuffer(), + Dialect: d, + IgnoreBinary: true, + } + err := i.interpolate(placeholder, []interface{}{builder}) + query, value := i.String(), i.Value() + if err != nil { + return nil, log.EventErrKv("dbr.exec.interpolate", err, kvs{ + "sql": query, + "args": fmt.Sprint(value), + }) + } + + startTime := time.Now() + defer func() { + log.TimingKv("dbr.exec", time.Since(startTime).Nanoseconds(), kvs{ + "sql": query, + }) + }() + + result, err := runner.ExecContext(ctx, query, value...) + if err != nil { + return result, log.EventErrKv("dbr.exec.exec", err, kvs{ + "sql": query, + }) + } + return result, nil +} + +func query(ctx context.Context, runner runner, log EventReceiver, builder Builder, d Dialect, dest interface{}) (int, error) { + timeout := runner.GetTimeout() + if timeout > 0 { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, timeout) + defer cancel() + } + + i := interpolator{ + Buffer: NewBuffer(), + Dialect: d, + IgnoreBinary: true, + } + err := i.interpolate(placeholder, []interface{}{builder}) + query, value := i.String(), i.Value() + if err != nil { + return 0, log.EventErrKv("dbr.select.interpolate", err, kvs{ + "sql": query, + "args": fmt.Sprint(value), + }) + } + + startTime := time.Now() + defer func() { + log.TimingKv("dbr.select", time.Since(startTime).Nanoseconds(), kvs{ + "sql": query, + }) + }() + + rows, err := runner.QueryContext(ctx, query, value...) + if err != nil { + return 0, log.EventErrKv("dbr.select.load.query", err, kvs{ + "sql": query, + }) + } + count, err := Load(rows, dest) + if err != nil { + return 0, log.EventErrKv("dbr.select.load.scan", err, kvs{ + "sql": query, + }) + } + return count, nil +} diff --git a/vendor/github.com/gocraft/dbr/delete.go b/vendor/github.com/gocraft/dbr/delete.go new file mode 100644 index 000000000..5b62b30f9 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/delete.go @@ -0,0 +1,61 @@ +package dbr + +// DeleteStmt builds `DELETE ...` +type DeleteStmt struct { + raw + + Table string + + WhereCond []Builder +} + +// Build builds `DELETE ...` in dialect +func (b *DeleteStmt) Build(d Dialect, buf Buffer) error { + if b.raw.Query != "" { + return b.raw.Build(d, buf) + } + + if b.Table == "" { + return ErrTableNotSpecified + } + + buf.WriteString("DELETE FROM ") + buf.WriteString(d.QuoteIdent(b.Table)) + + if len(b.WhereCond) > 0 { + buf.WriteString(" WHERE ") + err := And(b.WhereCond...).Build(d, buf) + if err != nil { + return err + } + } + return nil +} + +// DeleteFrom creates a DeleteStmt +func DeleteFrom(table string) *DeleteStmt { + return &DeleteStmt{ + Table: table, + } +} + +// DeleteBySql creates a DeleteStmt from raw query +func DeleteBySql(query string, value ...interface{}) *DeleteStmt { + return &DeleteStmt{ + raw: raw{ + Query: query, + Value: value, + }, + } +} + +// Where adds a where condition +func (b *DeleteStmt) Where(query interface{}, value ...interface{}) *DeleteStmt { + switch query := query.(type) { + case string: + b.WhereCond = append(b.WhereCond, Expr(query, value...)) + case Builder: + b.WhereCond = append(b.WhereCond, query) + } + return b +} diff --git a/vendor/github.com/gocraft/dbr/delete_builder.go b/vendor/github.com/gocraft/dbr/delete_builder.go new file mode 100644 index 000000000..fe2b89588 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/delete_builder.go @@ -0,0 +1,87 @@ +package dbr + +import ( + "context" + "database/sql" + "fmt" +) + +type DeleteBuilder struct { + runner + EventReceiver + Dialect Dialect + + *DeleteStmt + + LimitCount int64 +} + +func (sess *Session) DeleteFrom(table string) *DeleteBuilder { + return &DeleteBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + DeleteStmt: DeleteFrom(table), + LimitCount: -1, + } +} + +func (tx *Tx) DeleteFrom(table string) *DeleteBuilder { + return &DeleteBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + DeleteStmt: DeleteFrom(table), + LimitCount: -1, + } +} + +func (sess *Session) DeleteBySql(query string, value ...interface{}) *DeleteBuilder { + return &DeleteBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + DeleteStmt: DeleteBySql(query, value...), + LimitCount: -1, + } +} + +func (tx *Tx) DeleteBySql(query string, value ...interface{}) *DeleteBuilder { + return &DeleteBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + DeleteStmt: DeleteBySql(query, value...), + LimitCount: -1, + } +} + +func (b *DeleteBuilder) Exec() (sql.Result, error) { + return b.ExecContext(context.Background()) +} + +func (b *DeleteBuilder) ExecContext(ctx context.Context) (sql.Result, error) { + return exec(ctx, b.runner, b.EventReceiver, b, b.Dialect) +} + +func (b *DeleteBuilder) Where(query interface{}, value ...interface{}) *DeleteBuilder { + b.DeleteStmt.Where(query, value...) + return b +} + +func (b *DeleteBuilder) Limit(n uint64) *DeleteBuilder { + b.LimitCount = int64(n) + return b +} + +func (b *DeleteBuilder) Build(d Dialect, buf Buffer) error { + err := b.DeleteStmt.Build(b.Dialect, buf) + if err != nil { + return err + } + if b.LimitCount >= 0 { + buf.WriteString(" LIMIT ") + buf.WriteString(fmt.Sprint(b.LimitCount)) + } + return nil +} diff --git a/vendor/github.com/gocraft/dbr/dialect.go b/vendor/github.com/gocraft/dbr/dialect.go new file mode 100644 index 000000000..136b7284e --- /dev/null +++ b/vendor/github.com/gocraft/dbr/dialect.go @@ -0,0 +1,15 @@ +package dbr + +import "time" + +// Dialect abstracts database differences +type Dialect interface { + QuoteIdent(id string) string + + EncodeString(s string) string + EncodeBool(b bool) string + EncodeTime(t time.Time) string + EncodeBytes(b []byte) string + + Placeholder(n int) string +} diff --git a/vendor/github.com/gocraft/dbr/dialect/dialect.go b/vendor/github.com/gocraft/dbr/dialect/dialect.go new file mode 100644 index 000000000..7f7bdfffd --- /dev/null +++ b/vendor/github.com/gocraft/dbr/dialect/dialect.go @@ -0,0 +1,24 @@ +package dialect + +import "strings" + +var ( + // MySQL dialect + MySQL = mysql{} + // PostgreSQL dialect + PostgreSQL = postgreSQL{} + // SQLite3 dialect + SQLite3 = sqlite3{} +) + +const ( + timeFormat = "2006-01-02 15:04:05.000000" +) + +func quoteIdent(s, quote string) string { + part := strings.SplitN(s, ".", 2) + if len(part) == 2 { + return quoteIdent(part[0], quote) + "." + quoteIdent(part[1], quote) + } + return quote + s + quote +} diff --git a/vendor/github.com/gocraft/dbr/dialect/mysql.go b/vendor/github.com/gocraft/dbr/dialect/mysql.go new file mode 100644 index 000000000..a29f65a8a --- /dev/null +++ b/vendor/github.com/gocraft/dbr/dialect/mysql.go @@ -0,0 +1,66 @@ +package dialect + +import ( + "bytes" + "fmt" + "time" +) + +type mysql struct{} + +func (d mysql) QuoteIdent(s string) string { + return quoteIdent(s, "`") +} + +func (d mysql) EncodeString(s string) string { + buf := new(bytes.Buffer) + + buf.WriteRune('\'') + // https://dev.mysql.com/doc/refman/5.7/en/string-literals.html + for i := 0; i < len(s); i++ { + switch s[i] { + case 0: + buf.WriteString(`\0`) + case '\'': + buf.WriteString(`\'`) + case '"': + buf.WriteString(`\"`) + case '\b': + buf.WriteString(`\b`) + case '\n': + buf.WriteString(`\n`) + case '\r': + buf.WriteString(`\r`) + case '\t': + buf.WriteString(`\t`) + case 26: + buf.WriteString(`\Z`) + case '\\': + buf.WriteString(`\\`) + default: + buf.WriteByte(s[i]) + } + } + + buf.WriteRune('\'') + return buf.String() +} + +func (d mysql) EncodeBool(b bool) string { + if b { + return "1" + } + return "0" +} + +func (d mysql) EncodeTime(t time.Time) string { + return `'` + t.UTC().Format(timeFormat) + `'` +} + +func (d mysql) EncodeBytes(b []byte) string { + return fmt.Sprintf(`0x%x`, b) +} + +func (d mysql) Placeholder(_ int) string { + return "?" +} diff --git a/vendor/github.com/gocraft/dbr/dialect/postgresql.go b/vendor/github.com/gocraft/dbr/dialect/postgresql.go new file mode 100644 index 000000000..fa52155c3 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/dialect/postgresql.go @@ -0,0 +1,37 @@ +package dialect + +import ( + "fmt" + "strings" + "time" +) + +type postgreSQL struct{} + +func (d postgreSQL) QuoteIdent(s string) string { + return quoteIdent(s, `"`) +} + +func (d postgreSQL) EncodeString(s string) string { + // http://www.postgresql.org/docs/9.2/static/sql-syntax-lexical.html + return `'` + strings.Replace(s, `'`, `''`, -1) + `'` +} + +func (d postgreSQL) EncodeBool(b bool) string { + if b { + return "TRUE" + } + return "FALSE" +} + +func (d postgreSQL) EncodeTime(t time.Time) string { + return MySQL.EncodeTime(t) +} + +func (d postgreSQL) EncodeBytes(b []byte) string { + return fmt.Sprintf(`E'\\x%x'`, b) +} + +func (d postgreSQL) Placeholder(n int) string { + return fmt.Sprintf("$%d", n+1) +} diff --git a/vendor/github.com/gocraft/dbr/dialect/sqlite3.go b/vendor/github.com/gocraft/dbr/dialect/sqlite3.go new file mode 100644 index 000000000..0567dd460 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/dialect/sqlite3.go @@ -0,0 +1,40 @@ +package dialect + +import ( + "fmt" + "strings" + "time" +) + +type sqlite3 struct{} + +func (d sqlite3) QuoteIdent(s string) string { + return quoteIdent(s, `"`) +} + +func (d sqlite3) EncodeString(s string) string { + // https://www.sqlite.org/faq.html + return `'` + strings.Replace(s, `'`, `''`, -1) + `'` +} + +func (d sqlite3) EncodeBool(b bool) string { + // https://www.sqlite.org/lang_expr.html + if b { + return "1" + } + return "0" +} + +func (d sqlite3) EncodeTime(t time.Time) string { + // https://www.sqlite.org/lang_datefunc.html + return MySQL.EncodeTime(t) +} + +func (d sqlite3) EncodeBytes(b []byte) string { + // https://www.sqlite.org/lang_expr.html + return fmt.Sprintf(`X'%x'`, b) +} + +func (d sqlite3) Placeholder(_ int) string { + return "?" +} diff --git a/vendor/github.com/gocraft/dbr/errors.go b/vendor/github.com/gocraft/dbr/errors.go new file mode 100644 index 000000000..b53661c3a --- /dev/null +++ b/vendor/github.com/gocraft/dbr/errors.go @@ -0,0 +1,16 @@ +package dbr + +import "errors" + +// package errors +var ( + ErrNotFound = errors.New("dbr: not found") + ErrNotSupported = errors.New("dbr: not supported") + ErrTableNotSpecified = errors.New("dbr: table not specified") + ErrColumnNotSpecified = errors.New("dbr: column not specified") + ErrInvalidPointer = errors.New("dbr: attempt to load into an invalid pointer") + ErrPlaceholderCount = errors.New("dbr: wrong placeholder count") + ErrInvalidSliceLength = errors.New("dbr: length of slice is 0. length must be >= 1") + ErrCantConvertToTime = errors.New("dbr: can't convert to time.Time") + ErrInvalidTimestring = errors.New("dbr: invalid time string") +) diff --git a/vendor/github.com/gocraft/dbr/event.go b/vendor/github.com/gocraft/dbr/event.go new file mode 100644 index 000000000..983e123f4 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/event.go @@ -0,0 +1,40 @@ +package dbr + +// EventReceiver gets events from dbr methods for logging purposes +type EventReceiver interface { + Event(eventName string) + EventKv(eventName string, kvs map[string]string) + EventErr(eventName string, err error) error + EventErrKv(eventName string, err error, kvs map[string]string) error + Timing(eventName string, nanoseconds int64) + TimingKv(eventName string, nanoseconds int64, kvs map[string]string) +} + +type kvs map[string]string + +var nullReceiver = &NullEventReceiver{} + +// NullEventReceiver is a sentinel EventReceiver; use it if the caller doesn't supply one +type NullEventReceiver struct{} + +// Event receives a simple notification when various events occur +func (n *NullEventReceiver) Event(eventName string) {} + +// EventKv receives a notification when various events occur along with +// optional key/value data +func (n *NullEventReceiver) EventKv(eventName string, kvs map[string]string) {} + +// EventErr receives a notification of an error if one occurs +func (n *NullEventReceiver) EventErr(eventName string, err error) error { return err } + +// EventErrKv receives a notification of an error if one occurs along with +// optional key/value data +func (n *NullEventReceiver) EventErrKv(eventName string, err error, kvs map[string]string) error { + return err +} + +// Timing receives the time an event took to happen +func (n *NullEventReceiver) Timing(eventName string, nanoseconds int64) {} + +// TimingKv receives the time an event took to happen along with optional key/value data +func (n *NullEventReceiver) TimingKv(eventName string, nanoseconds int64, kvs map[string]string) {} diff --git a/vendor/github.com/gocraft/dbr/expr.go b/vendor/github.com/gocraft/dbr/expr.go new file mode 100644 index 000000000..ecc0e1f35 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/expr.go @@ -0,0 +1,18 @@ +package dbr + +// XxxBuilders all support raw query +type raw struct { + Query string + Value []interface{} +} + +// Expr should be used when sql syntax is not supported +func Expr(query string, value ...interface{}) Builder { + return &raw{Query: query, Value: value} +} + +func (raw *raw) Build(_ Dialect, buf Buffer) error { + buf.WriteString(raw.Query) + buf.WriteValue(raw.Value...) + return nil +} diff --git a/vendor/github.com/gocraft/dbr/ident.go b/vendor/github.com/gocraft/dbr/ident.go new file mode 100644 index 000000000..42d90a053 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/ident.go @@ -0,0 +1,24 @@ +package dbr + +// identifier is a type of string +type I string + +func (i I) Build(d Dialect, buf Buffer) error { + buf.WriteString(d.QuoteIdent(string(i))) + return nil +} + +// As creates an alias for expr. e.g. SELECT `a1` AS `a2` +func (i I) As(alias string) Builder { + return as(i, alias) +} + +func as(expr interface{}, alias string) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + buf.WriteString(placeholder) + buf.WriteValue(expr) + buf.WriteString(" AS ") + buf.WriteString(d.QuoteIdent(alias)) + return nil + }) +} diff --git a/vendor/github.com/gocraft/dbr/insert.go b/vendor/github.com/gocraft/dbr/insert.go new file mode 100644 index 000000000..6b6928f80 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/insert.go @@ -0,0 +1,123 @@ +package dbr + +import ( + "bytes" + "reflect" +) + +// InsertStmt builds `INSERT INTO ...` +type InsertStmt struct { + raw + + Table string + Column []string + Value [][]interface{} + ReturnColumn []string +} + +// Build builds `INSERT INTO ...` in dialect +func (b *InsertStmt) Build(d Dialect, buf Buffer) error { + if b.raw.Query != "" { + return b.raw.Build(d, buf) + } + + if b.Table == "" { + return ErrTableNotSpecified + } + + if len(b.Column) == 0 { + return ErrColumnNotSpecified + } + + buf.WriteString("INSERT INTO ") + buf.WriteString(d.QuoteIdent(b.Table)) + + placeholderBuf := new(bytes.Buffer) + placeholderBuf.WriteString("(") + buf.WriteString(" (") + for i, col := range b.Column { + if i > 0 { + buf.WriteString(",") + placeholderBuf.WriteString(",") + } + buf.WriteString(d.QuoteIdent(col)) + placeholderBuf.WriteString(placeholder) + } + buf.WriteString(") VALUES ") + placeholderBuf.WriteString(")") + placeholderStr := placeholderBuf.String() + + for i, tuple := range b.Value { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(placeholderStr) + + buf.WriteValue(tuple...) + } + + if len(b.ReturnColumn) > 0 { + buf.WriteString(" RETURNING ") + for i, col := range b.ReturnColumn { + if i > 0 { + buf.WriteString(",") + } + buf.WriteString(d.QuoteIdent(col)) + } + } + + return nil +} + +// InsertInto creates an InsertStmt +func InsertInto(table string) *InsertStmt { + return &InsertStmt{ + Table: table, + } +} + +// InsertBySql creates an InsertStmt from raw query +func InsertBySql(query string, value ...interface{}) *InsertStmt { + return &InsertStmt{ + raw: raw{ + Query: query, + Value: value, + }, + } +} + +// Columns adds columns +func (b *InsertStmt) Columns(column ...string) *InsertStmt { + b.Column = column + return b +} + +// Values adds a tuple for columns +func (b *InsertStmt) Values(value ...interface{}) *InsertStmt { + b.Value = append(b.Value, value) + return b +} + +// Record adds a tuple for columns from a struct +func (b *InsertStmt) Record(structValue interface{}) *InsertStmt { + v := reflect.Indirect(reflect.ValueOf(structValue)) + + if v.Kind() == reflect.Struct { + var value []interface{} + m := structMap(v) + for _, key := range b.Column { + if val, ok := m[key]; ok { + value = append(value, val.Interface()) + } else { + value = append(value, nil) + } + } + b.Values(value...) + } + return b +} + +func (b *InsertStmt) Returning(column ...string) *InsertStmt { + b.ReturnColumn = column + return b +} diff --git a/vendor/github.com/gocraft/dbr/insert_builder.go b/vendor/github.com/gocraft/dbr/insert_builder.go new file mode 100644 index 000000000..1afdab912 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/insert_builder.go @@ -0,0 +1,126 @@ +package dbr + +import ( + "context" + "database/sql" + "reflect" +) + +type InsertBuilder struct { + runner + EventReceiver + Dialect Dialect + + RecordID reflect.Value + + *InsertStmt +} + +func (sess *Session) InsertInto(table string) *InsertBuilder { + return &InsertBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + InsertStmt: InsertInto(table), + } +} + +func (tx *Tx) InsertInto(table string) *InsertBuilder { + return &InsertBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + InsertStmt: InsertInto(table), + } +} + +func (sess *Session) InsertBySql(query string, value ...interface{}) *InsertBuilder { + return &InsertBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + InsertStmt: InsertBySql(query, value...), + } +} + +func (tx *Tx) InsertBySql(query string, value ...interface{}) *InsertBuilder { + return &InsertBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + InsertStmt: InsertBySql(query, value...), + } +} + +func (b *InsertBuilder) Pair(column string, value interface{}) *InsertBuilder { + b.Column = append(b.Column, column) + switch len(b.Value) { + case 0: + b.InsertStmt.Values(value) + case 1: + b.Value[0] = append(b.Value[0], value) + default: + panic("pair only allows one record to insert") + } + return b +} + +func (b *InsertBuilder) Exec() (sql.Result, error) { + return b.ExecContext(context.Background()) +} + +func (b *InsertBuilder) ExecContext(ctx context.Context) (sql.Result, error) { + result, err := exec(ctx, b.runner, b.EventReceiver, b, b.Dialect) + if err != nil { + return nil, err + } + + if b.RecordID.IsValid() { + if id, err := result.LastInsertId(); err == nil { + b.RecordID.SetInt(id) + } + } + + return result, nil +} + +func (b *InsertBuilder) LoadContext(ctx context.Context, value interface{}) error { + _, err := query(ctx, b.runner, b.EventReceiver, b, b.Dialect, value) + return err +} + +func (b *InsertBuilder) Load(value interface{}) error { + return b.LoadContext(context.Background(), value) +} + +func (b *InsertBuilder) Columns(column ...string) *InsertBuilder { + b.InsertStmt.Columns(column...) + return b +} + +func (b *InsertBuilder) Returning(column ...string) *InsertBuilder { + b.InsertStmt.Returning(column...) + return b +} + +func (b *InsertBuilder) Record(structValue interface{}) *InsertBuilder { + v := reflect.Indirect(reflect.ValueOf(structValue)) + if v.Kind() == reflect.Struct && v.CanSet() { + // ID is recommended by golint here + for _, name := range []string{"Id", "ID"} { + field := v.FieldByName(name) + if field.IsValid() && field.Kind() == reflect.Int64 { + b.RecordID = field + break + } + } + } + + b.InsertStmt.Record(structValue) + return b +} + +func (b *InsertBuilder) Values(value ...interface{}) *InsertBuilder { + b.InsertStmt.Values(value...) + return b +} diff --git a/vendor/github.com/gocraft/dbr/interpolate.go b/vendor/github.com/gocraft/dbr/interpolate.go new file mode 100644 index 000000000..4471672f0 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/interpolate.go @@ -0,0 +1,157 @@ +package dbr + +import ( + "database/sql/driver" + "reflect" + "strconv" + "strings" + "time" +) + +type interpolator struct { + Buffer + Dialect + IgnoreBinary bool + N int +} + +// InterpolateForDialect replaces placeholder in query with corresponding value in dialect +func InterpolateForDialect(query string, value []interface{}, d Dialect) (string, error) { + i := interpolator{ + Buffer: NewBuffer(), + Dialect: d, + } + err := i.interpolate(query, value) + if err != nil { + return "", err + } + return i.String(), nil +} + +func (i *interpolator) interpolate(query string, value []interface{}) error { + if strings.Count(query, placeholder) != len(value) { + return ErrPlaceholderCount + } + + valueIndex := 0 + + for { + index := strings.Index(query, placeholder) + if index == -1 { + break + } + + i.WriteString(query[:index]) + if _, ok := value[valueIndex].([]byte); ok && i.IgnoreBinary { + i.WriteString(i.Placeholder(i.N)) + i.N++ + i.WriteValue(value[valueIndex]) + } else { + err := i.encodePlaceholder(value[valueIndex]) + if err != nil { + return err + } + } + query = query[index+len(placeholder):] + valueIndex++ + } + + // placeholder not found; write remaining query + i.WriteString(query) + + return nil +} + +func (i *interpolator) encodePlaceholder(value interface{}) error { + if builder, ok := value.(Builder); ok { + pbuf := NewBuffer() + err := builder.Build(i.Dialect, pbuf) + if err != nil { + return err + } + paren := true + switch value.(type) { + case *SelectStmt: + case *union: + default: + paren = false + } + if paren { + i.WriteString("(") + } + err = i.interpolate(pbuf.String(), pbuf.Value()) + if err != nil { + return err + } + if paren { + i.WriteString(")") + } + return nil + } + + if valuer, ok := value.(driver.Valuer); ok { + // get driver.Valuer's data + var err error + value, err = valuer.Value() + if err != nil { + return err + } + } + + if value == nil { + i.WriteString("NULL") + return nil + } + v := reflect.ValueOf(value) + switch v.Kind() { + case reflect.String: + i.WriteString(i.EncodeString(v.String())) + return nil + case reflect.Bool: + i.WriteString(i.EncodeBool(v.Bool())) + return nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + i.WriteString(strconv.FormatInt(v.Int(), 10)) + return nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + i.WriteString(strconv.FormatUint(v.Uint(), 10)) + return nil + case reflect.Float32, reflect.Float64: + i.WriteString(strconv.FormatFloat(v.Float(), 'f', -1, 64)) + return nil + case reflect.Struct: + if v.Type() == reflect.TypeOf(time.Time{}) { + i.WriteString(i.EncodeTime(v.Interface().(time.Time))) + return nil + } + case reflect.Slice: + if v.Type().Elem().Kind() == reflect.Uint8 { + // []byte + i.WriteString(i.EncodeBytes(v.Bytes())) + return nil + } + if v.Len() == 0 { + // FIXME: support zero-length slice + return ErrInvalidSliceLength + } + i.WriteString("(") + for n := 0; n < v.Len(); n++ { + if n > 0 { + i.WriteString(",") + } + err := i.encodePlaceholder(v.Index(n).Interface()) + if err != nil { + return err + } + } + i.WriteString(")") + return nil + case reflect.Ptr: + if v.IsNil() { + i.WriteString("NULL") + return nil + } + return i.encodePlaceholder(v.Elem().Interface()) + } + return ErrNotSupported +} diff --git a/vendor/github.com/gocraft/dbr/join.go b/vendor/github.com/gocraft/dbr/join.go new file mode 100644 index 000000000..635c0af1f --- /dev/null +++ b/vendor/github.com/gocraft/dbr/join.go @@ -0,0 +1,41 @@ +package dbr + +type joinType uint8 + +const ( + inner joinType = iota + left + right + full +) + +func join(t joinType, table interface{}, on interface{}) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + buf.WriteString(" ") + switch t { + case left: + buf.WriteString("LEFT ") + case right: + buf.WriteString("RIGHT ") + case full: + buf.WriteString("FULL ") + } + buf.WriteString("JOIN ") + switch table := table.(type) { + case string: + buf.WriteString(d.QuoteIdent(table)) + default: + buf.WriteString(placeholder) + buf.WriteValue(table) + } + buf.WriteString(" ON ") + switch on := on.(type) { + case string: + buf.WriteString(on) + case Builder: + buf.WriteString(placeholder) + buf.WriteValue(on) + } + return nil + }) +} diff --git a/vendor/github.com/gocraft/dbr/load.go b/vendor/github.com/gocraft/dbr/load.go new file mode 100644 index 000000000..757d0e4a8 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/load.go @@ -0,0 +1,119 @@ +package dbr + +import ( + "database/sql" + "reflect" +) + +// Load loads any value from sql.Rows +func Load(rows *sql.Rows, value interface{}) (int, error) { + defer rows.Close() + + column, err := rows.Columns() + if err != nil { + return 0, err + } + + v := reflect.ValueOf(value) + if v.Kind() != reflect.Ptr || v.IsNil() { + return 0, ErrInvalidPointer + } + v = v.Elem() + isScanner := v.Addr().Type().Implements(typeScanner) + isSlice := v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 && !isScanner + isMap := v.Kind() == reflect.Map && !isScanner + isMapOfSlices := isMap && v.Type().Elem().Kind() == reflect.Slice && v.Type().Elem().Elem().Kind() != reflect.Uint8 + if isMap { + v.Set(reflect.MakeMap(v.Type())) + } + count := 0 + for rows.Next() { + var elem, keyElem reflect.Value + var ptr []interface{} + var err error + + if isMapOfSlices { + elem = reflect.New(v.Type().Elem().Elem()).Elem() + } else if isSlice || isMap { + elem = reflect.New(v.Type().Elem()).Elem() + } else { + elem = v + } + + if isMap { + ptr, err = findPtr(column[1:], elem) + if err != nil { + return 0, err + } + keyElem = reflect.New(v.Type().Key()).Elem() + keyPtr, err := findPtr(column[0:1], keyElem) + if err != nil { + return 0, err + } + ptr = append(keyPtr, ptr...) + } else { + ptr, err = findPtr(column, elem) + if err != nil { + return 0, err + } + } + + err = rows.Scan(ptr...) + if err != nil { + return 0, err + } + + count++ + + if isSlice { + v.Set(reflect.Append(v, elem)) + } else if isMapOfSlices { + s := v.MapIndex(keyElem) + if !s.IsValid() { + s = reflect.Zero(v.Type().Elem()) + } + v.SetMapIndex(keyElem, reflect.Append(s, elem)) + } else if isMap { + v.SetMapIndex(keyElem, elem) + } else { + break + } + } + return count, nil +} + +type dummyScanner struct{} + +func (dummyScanner) Scan(interface{}) error { + return nil +} + +var ( + dummyDest sql.Scanner = dummyScanner{} + typeScanner = reflect.TypeOf((*sql.Scanner)(nil)).Elem() +) + +func findPtr(column []string, value reflect.Value) ([]interface{}, error) { + if value.Addr().Type().Implements(typeScanner) { + return []interface{}{value.Addr().Interface()}, nil + } + switch value.Kind() { + case reflect.Struct: + var ptr []interface{} + m := structMap(value) + for _, key := range column { + if val, ok := m[key]; ok { + ptr = append(ptr, val.Addr().Interface()) + } else { + ptr = append(ptr, dummyDest) + } + } + return ptr, nil + case reflect.Ptr: + if value.IsNil() { + value.Set(reflect.New(value.Type().Elem())) + } + return findPtr(column, value.Elem()) + } + return []interface{}{value.Addr().Interface()}, nil +} diff --git a/vendor/github.com/gocraft/dbr/now.go b/vendor/github.com/gocraft/dbr/now.go new file mode 100644 index 000000000..c1b19a178 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/now.go @@ -0,0 +1,19 @@ +package dbr + +import ( + "database/sql/driver" + "time" +) + +// Now is a value that serializes to the current time +var Now = nowSentinel{} + +const timeFormat = "2006-01-02 15:04:05.000000" + +type nowSentinel struct{} + +// Value implements a valuer for compatibility +func (n nowSentinel) Value() (driver.Value, error) { + now := time.Now().UTC().Format(timeFormat) + return now, nil +} diff --git a/vendor/github.com/gocraft/dbr/order.go b/vendor/github.com/gocraft/dbr/order.go new file mode 100644 index 000000000..3d4bf8078 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/order.go @@ -0,0 +1,24 @@ +package dbr + +type direction bool + +// orderby directions +// most databases by default use asc +const ( + asc direction = false + desc = true +) + +func order(column string, dir direction) Builder { + return BuildFunc(func(d Dialect, buf Buffer) error { + // FIXME: no quote ident + buf.WriteString(column) + switch dir { + case asc: + buf.WriteString(" ASC") + case desc: + buf.WriteString(" DESC") + } + return nil + }) +} diff --git a/vendor/github.com/gocraft/dbr/select.go b/vendor/github.com/gocraft/dbr/select.go new file mode 100644 index 000000000..dfe7e22fc --- /dev/null +++ b/vendor/github.com/gocraft/dbr/select.go @@ -0,0 +1,238 @@ +package dbr + +import "fmt" + +// SelectStmt builds `SELECT ...` +type SelectStmt struct { + raw + + IsDistinct bool + + Column []interface{} + Table interface{} + JoinTable []Builder + + WhereCond []Builder + Group []Builder + HavingCond []Builder + Order []Builder + + LimitCount int64 + OffsetCount int64 +} + +// Build builds `SELECT ...` in dialect +func (b *SelectStmt) Build(d Dialect, buf Buffer) error { + if b.raw.Query != "" { + return b.raw.Build(d, buf) + } + + if len(b.Column) == 0 { + return ErrColumnNotSpecified + } + + buf.WriteString("SELECT ") + + if b.IsDistinct { + buf.WriteString("DISTINCT ") + } + + for i, col := range b.Column { + if i > 0 { + buf.WriteString(", ") + } + switch col := col.(type) { + case string: + // FIXME: no quote ident + buf.WriteString(col) + default: + buf.WriteString(placeholder) + buf.WriteValue(col) + } + } + + if b.Table != nil { + buf.WriteString(" FROM ") + switch table := b.Table.(type) { + case string: + // FIXME: no quote ident + buf.WriteString(table) + default: + buf.WriteString(placeholder) + buf.WriteValue(table) + } + if len(b.JoinTable) > 0 { + for _, join := range b.JoinTable { + err := join.Build(d, buf) + if err != nil { + return err + } + } + } + } + + if len(b.WhereCond) > 0 { + buf.WriteString(" WHERE ") + err := And(b.WhereCond...).Build(d, buf) + if err != nil { + return err + } + } + + if len(b.Group) > 0 { + buf.WriteString(" GROUP BY ") + for i, group := range b.Group { + if i > 0 { + buf.WriteString(", ") + } + err := group.Build(d, buf) + if err != nil { + return err + } + } + } + + if len(b.HavingCond) > 0 { + buf.WriteString(" HAVING ") + err := And(b.HavingCond...).Build(d, buf) + if err != nil { + return err + } + } + + if len(b.Order) > 0 { + buf.WriteString(" ORDER BY ") + for i, order := range b.Order { + if i > 0 { + buf.WriteString(", ") + } + err := order.Build(d, buf) + if err != nil { + return err + } + } + } + + if b.LimitCount >= 0 { + buf.WriteString(" LIMIT ") + buf.WriteString(fmt.Sprint(b.LimitCount)) + } + + if b.OffsetCount >= 0 { + buf.WriteString(" OFFSET ") + buf.WriteString(fmt.Sprint(b.OffsetCount)) + } + return nil +} + +// Select creates a SelectStmt +func Select(column ...interface{}) *SelectStmt { + return &SelectStmt{ + Column: column, + LimitCount: -1, + OffsetCount: -1, + } +} + +// From specifies table +func (b *SelectStmt) From(table interface{}) *SelectStmt { + b.Table = table + return b +} + +// SelectBySql creates a SelectStmt from raw query +func SelectBySql(query string, value ...interface{}) *SelectStmt { + return &SelectStmt{ + raw: raw{ + Query: query, + Value: value, + }, + LimitCount: -1, + OffsetCount: -1, + } +} + +// Distinct adds `DISTINCT` +func (b *SelectStmt) Distinct() *SelectStmt { + b.IsDistinct = true + return b +} + +// Where adds a where condition +func (b *SelectStmt) Where(query interface{}, value ...interface{}) *SelectStmt { + switch query := query.(type) { + case string: + b.WhereCond = append(b.WhereCond, Expr(query, value...)) + case Builder: + b.WhereCond = append(b.WhereCond, query) + } + return b +} + +// Having adds a having condition +func (b *SelectStmt) Having(query interface{}, value ...interface{}) *SelectStmt { + switch query := query.(type) { + case string: + b.HavingCond = append(b.HavingCond, Expr(query, value...)) + case Builder: + b.HavingCond = append(b.HavingCond, query) + } + return b +} + +// GroupBy specifies columns for grouping +func (b *SelectStmt) GroupBy(col ...string) *SelectStmt { + for _, group := range col { + b.Group = append(b.Group, Expr(group)) + } + return b +} + +// OrderBy specifies columns for ordering +func (b *SelectStmt) OrderAsc(col string) *SelectStmt { + b.Order = append(b.Order, order(col, asc)) + return b +} + +func (b *SelectStmt) OrderDesc(col string) *SelectStmt { + b.Order = append(b.Order, order(col, desc)) + return b +} + +// Limit adds limit +func (b *SelectStmt) Limit(n uint64) *SelectStmt { + b.LimitCount = int64(n) + return b +} + +// Offset adds offset +func (b *SelectStmt) Offset(n uint64) *SelectStmt { + b.OffsetCount = int64(n) + return b +} + +// Join joins table on condition +func (b *SelectStmt) Join(table, on interface{}) *SelectStmt { + b.JoinTable = append(b.JoinTable, join(inner, table, on)) + return b +} + +func (b *SelectStmt) LeftJoin(table, on interface{}) *SelectStmt { + b.JoinTable = append(b.JoinTable, join(left, table, on)) + return b +} + +func (b *SelectStmt) RightJoin(table, on interface{}) *SelectStmt { + b.JoinTable = append(b.JoinTable, join(right, table, on)) + return b +} + +func (b *SelectStmt) FullJoin(table, on interface{}) *SelectStmt { + b.JoinTable = append(b.JoinTable, join(full, table, on)) + return b +} + +// As creates alias for select statement +func (b *SelectStmt) As(alias string) Builder { + return as(b, alias) +} diff --git a/vendor/github.com/gocraft/dbr/select_builder.go b/vendor/github.com/gocraft/dbr/select_builder.go new file mode 100644 index 000000000..6226e6946 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/select_builder.go @@ -0,0 +1,173 @@ +package dbr + +import "context" + +type SelectBuilder struct { + runner + EventReceiver + Dialect Dialect + + *SelectStmt +} + +func prepareSelect(a []string) []interface{} { + b := make([]interface{}, len(a)) + for i := range a { + b[i] = a[i] + } + return b +} + +func (sess *Session) Select(column ...string) *SelectBuilder { + return &SelectBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + SelectStmt: Select(prepareSelect(column)...), + } +} + +func (tx *Tx) Select(column ...string) *SelectBuilder { + return &SelectBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + SelectStmt: Select(prepareSelect(column)...), + } +} + +func (sess *Session) SelectBySql(query string, value ...interface{}) *SelectBuilder { + return &SelectBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + SelectStmt: SelectBySql(query, value...), + } +} + +func (tx *Tx) SelectBySql(query string, value ...interface{}) *SelectBuilder { + return &SelectBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + SelectStmt: SelectBySql(query, value...), + } +} + +// DEPRECATED: use LoadOne instead +func (b *SelectBuilder) LoadStruct(value interface{}) error { + return b.LoadOne(value) +} + +// DEPRECATED: use Load instead +func (b *SelectBuilder) LoadStructs(value interface{}) (int, error) { + return b.Load(value) +} + +// DEPRECATED: use LoadOne instead +func (b *SelectBuilder) LoadValue(value interface{}) error { + return b.LoadOne(value) +} + +// DEPRECATED: use Load instead +func (b *SelectBuilder) LoadValues(value interface{}) (int, error) { + return b.Load(value) +} + +func (b *SelectBuilder) LoadOneContext(ctx context.Context, value interface{}) error { + count, err := query(ctx, b.runner, b.EventReceiver, b, b.Dialect, value) + if err != nil { + return err + } + if count == 0 { + return ErrNotFound + } + return nil +} + +func (b *SelectBuilder) LoadOne(value interface{}) error { + return b.LoadOneContext(context.Background(), value) +} + +func (b *SelectBuilder) LoadContext(ctx context.Context, value interface{}) (int, error) { + return query(ctx, b.runner, b.EventReceiver, b, b.Dialect, value) +} + +func (b *SelectBuilder) Load(value interface{}) (int, error) { + return b.LoadContext(context.Background(), value) +} + +func (b *SelectBuilder) Join(table, on interface{}) *SelectBuilder { + b.SelectStmt.Join(table, on) + return b +} + +func (b *SelectBuilder) LeftJoin(table, on interface{}) *SelectBuilder { + b.SelectStmt.LeftJoin(table, on) + return b +} + +func (b *SelectBuilder) RightJoin(table, on interface{}) *SelectBuilder { + b.SelectStmt.RightJoin(table, on) + return b +} + +func (b *SelectBuilder) FullJoin(table, on interface{}) *SelectBuilder { + b.SelectStmt.FullJoin(table, on) + return b +} + +func (b *SelectBuilder) Distinct() *SelectBuilder { + b.SelectStmt.Distinct() + return b +} + +func (b *SelectBuilder) From(table interface{}) *SelectBuilder { + b.SelectStmt.From(table) + return b +} + +func (b *SelectBuilder) GroupBy(col ...string) *SelectBuilder { + b.SelectStmt.GroupBy(col...) + return b +} + +func (b *SelectBuilder) Having(query interface{}, value ...interface{}) *SelectBuilder { + b.SelectStmt.Having(query, value...) + return b +} + +func (b *SelectBuilder) Limit(n uint64) *SelectBuilder { + b.SelectStmt.Limit(n) + return b +} + +func (b *SelectBuilder) Offset(n uint64) *SelectBuilder { + b.SelectStmt.Offset(n) + return b +} + +func (b *SelectBuilder) OrderDir(col string, isAsc bool) *SelectBuilder { + if isAsc { + b.SelectStmt.OrderAsc(col) + } else { + b.SelectStmt.OrderDesc(col) + } + return b +} + +func (b *SelectBuilder) Paginate(page, perPage uint64) *SelectBuilder { + b.Limit(perPage) + b.Offset((page - 1) * perPage) + return b +} + +func (b *SelectBuilder) OrderBy(col string) *SelectBuilder { + b.SelectStmt.Order = append(b.SelectStmt.Order, Expr(col)) + return b +} + +func (b *SelectBuilder) Where(query interface{}, value ...interface{}) *SelectBuilder { + b.SelectStmt.Where(query, value...) + return b +} diff --git a/vendor/github.com/gocraft/dbr/select_return.go b/vendor/github.com/gocraft/dbr/select_return.go new file mode 100644 index 000000000..d55c208df --- /dev/null +++ b/vendor/github.com/gocraft/dbr/select_return.go @@ -0,0 +1,66 @@ +package dbr + +// +// These are a set of helpers that just call LoadValue and return the value. +// They return (_, ErrNotFound) if nothing was found. +// + +// The inclusion of these helpers in the package is not an obvious choice: +// Benefits: +// - slight increase in code clarity/conciseness b/c you can use ":=" to define the variable +// +// count, err := d.Select("COUNT(*)").From("users").Where("x = ?", x).ReturnInt64() +// +// vs +// +// var count int64 +// err := d.Select("COUNT(*)").From("users").Where("x = ?", x).LoadValue(&count) +// +// Downsides: +// - very small increase in code cost, although it's not complex code +// - increase in conceptual model / API footprint when presenting the package to new users +// - no functionality that you can't achieve calling .LoadValue directly. +// - There's a lot of possible types. Do we want to include ALL of them? u?int{8,16,32,64}?, strings, null varieties, etc. +// - Let's just do the common, non-null varieties. + +// ReturnInt64 executes the SelectStmt and returns the value as an int64 +func (b *SelectBuilder) ReturnInt64() (int64, error) { + var v int64 + err := b.LoadValue(&v) + return v, err +} + +// ReturnInt64s executes the SelectStmt and returns the value as a slice of int64s +func (b *SelectBuilder) ReturnInt64s() ([]int64, error) { + var v []int64 + _, err := b.LoadValues(&v) + return v, err +} + +// ReturnUint64 executes the SelectStmt and returns the value as an uint64 +func (b *SelectBuilder) ReturnUint64() (uint64, error) { + var v uint64 + err := b.LoadValue(&v) + return v, err +} + +// ReturnUint64s executes the SelectStmt and returns the value as a slice of uint64s +func (b *SelectBuilder) ReturnUint64s() ([]uint64, error) { + var v []uint64 + _, err := b.LoadValues(&v) + return v, err +} + +// ReturnString executes the SelectStmt and returns the value as a string +func (b *SelectBuilder) ReturnString() (string, error) { + var v string + err := b.LoadValue(&v) + return v, err +} + +// ReturnStrings executes the SelectStmt and returns the value as a slice of strings +func (b *SelectBuilder) ReturnStrings() ([]string, error) { + var v []string + _, err := b.LoadValues(&v) + return v, err +} diff --git a/vendor/github.com/gocraft/dbr/transaction.go b/vendor/github.com/gocraft/dbr/transaction.go new file mode 100644 index 000000000..0861f06df --- /dev/null +++ b/vendor/github.com/gocraft/dbr/transaction.go @@ -0,0 +1,99 @@ +package dbr + +import ( + "context" + "database/sql" + "time" +) + +// Tx is a transaction for the given Session +type Tx struct { + EventReceiver + Dialect Dialect + *sql.Tx + Timeout time.Duration + + // normally we don't call the context cancelFunc. + // however, if we start a tx without explictly tx, + // we will need to call this after the transaction. + Cancel func() +} + +func (tx *Tx) GetTimeout() time.Duration { + return tx.Timeout +} + +func (sess *Session) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) { + tx, err := sess.Connection.BeginTx(ctx, opts) + if err != nil { + return nil, sess.EventErr("dbr.begin.error", err) + } + sess.Event("dbr.begin") + + stx := &Tx{ + EventReceiver: sess, + Dialect: sess.Dialect, + Tx: tx, + Cancel: func() {}, + } + deadline, ok := ctx.Deadline() + if ok { + stx.Timeout = deadline.Sub(time.Now()) + } + return stx, nil +} + +// Begin creates a transaction for the given session +func (sess *Session) Begin() (*Tx, error) { + ctx := context.Background() + var cancel func() + timeout := sess.GetTimeout() + if timeout > 0 { + ctx, cancel = context.WithTimeout(ctx, timeout) + } + stx, err := sess.BeginTx(ctx, nil) + if err != nil { + return nil, err + } + if cancel != nil { + stx.Cancel = cancel + } + return stx, nil +} + +// Commit finishes the transaction +func (tx *Tx) Commit() error { + defer tx.Cancel() + err := tx.Tx.Commit() + if err != nil { + return tx.EventErr("dbr.commit.error", err) + } + tx.Event("dbr.commit") + return nil +} + +// Rollback cancels the transaction +func (tx *Tx) Rollback() error { + defer tx.Cancel() + err := tx.Tx.Rollback() + if err != nil { + return tx.EventErr("dbr.rollback", err) + } + tx.Event("dbr.rollback") + return nil +} + +// RollbackUnlessCommitted rollsback the transaction unless it has already been committed or rolled back. +// Useful to defer tx.RollbackUnlessCommitted() -- so you don't have to handle N failure cases +// Keep in mind the only way to detect an error on the rollback is via the event log. +func (tx *Tx) RollbackUnlessCommitted() { + defer tx.Cancel() + err := tx.Tx.Rollback() + if err == sql.ErrTxDone { + // ok + } else if err != nil { + tx.EventErr("dbr.rollback_unless_committed", err) + } else { + tx.Event("dbr.rollback") + } +} diff --git a/vendor/github.com/gocraft/dbr/types.go b/vendor/github.com/gocraft/dbr/types.go new file mode 100644 index 000000000..852530963 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/types.go @@ -0,0 +1,242 @@ +package dbr + +import ( + "bytes" + "database/sql" + "database/sql/driver" + "encoding/json" + "time" +) + +// +// Your app can use these Null types instead of the defaults. The sole benefit you get is a MarshalJSON method that is not retarded. +// + +// NullString is a type that can be null or a string +type NullString struct { + sql.NullString +} + +// NullFloat64 is a type that can be null or a float64 +type NullFloat64 struct { + sql.NullFloat64 +} + +// NullInt64 is a type that can be null or an int +type NullInt64 struct { + sql.NullInt64 +} + +// NullTime is a type that can be null or a time +type NullTime struct { + Time time.Time + Valid bool // Valid is true if Time is not NULL +} + +// Value implements the driver Valuer interface. +func (n NullTime) Value() (driver.Value, error) { + if !n.Valid { + return nil, nil + } + return n.Time, nil +} + +// NullBool is a type that can be null or a bool +type NullBool struct { + sql.NullBool +} + +var nullString = []byte("null") + +// MarshalJSON correctly serializes a NullString to JSON +func (n NullString) MarshalJSON() ([]byte, error) { + if n.Valid { + return json.Marshal(n.String) + } + return nullString, nil +} + +// MarshalJSON correctly serializes a NullInt64 to JSON +func (n NullInt64) MarshalJSON() ([]byte, error) { + if n.Valid { + return json.Marshal(n.Int64) + } + return nullString, nil +} + +// MarshalJSON correctly serializes a NullFloat64 to JSON +func (n NullFloat64) MarshalJSON() ([]byte, error) { + if n.Valid { + return json.Marshal(n.Float64) + } + return nullString, nil +} + +// MarshalJSON correctly serializes a NullTime to JSON +func (n NullTime) MarshalJSON() ([]byte, error) { + if n.Valid { + return json.Marshal(n.Time) + } + return nullString, nil +} + +// MarshalJSON correctly serializes a NullBool to JSON +func (n NullBool) MarshalJSON() ([]byte, error) { + if n.Valid { + return json.Marshal(n.Bool) + } + return nullString, nil +} + +// UnmarshalJSON correctly deserializes a NullString from JSON +func (n *NullString) UnmarshalJSON(b []byte) error { + var s interface{} + if err := json.Unmarshal(b, &s); err != nil { + return err + } + return n.Scan(s) +} + +// UnmarshalJSON correctly deserializes a NullInt64 from JSON +func (n *NullInt64) UnmarshalJSON(b []byte) error { + var s json.Number + if err := json.Unmarshal(b, &s); err != nil { + return err + } + if s == "" { + return n.Scan(nil) + } + return n.Scan(s) +} + +// UnmarshalJSON correctly deserializes a NullFloat64 from JSON +func (n *NullFloat64) UnmarshalJSON(b []byte) error { + var s interface{} + if err := json.Unmarshal(b, &s); err != nil { + return err + } + return n.Scan(s) +} + +// UnmarshalJSON correctly deserializes a NullTime from JSON +func (n *NullTime) UnmarshalJSON(b []byte) error { + // scan for null + if bytes.Equal(b, nullString) { + return n.Scan(nil) + } + // scan for JSON timestamp + var t time.Time + if err := json.Unmarshal(b, &t); err != nil { + return err + } + return n.Scan(t) +} + +// UnmarshalJSON correctly deserializes a NullBool from JSON +func (n *NullBool) UnmarshalJSON(b []byte) error { + var s interface{} + if err := json.Unmarshal(b, &s); err != nil { + return err + } + return n.Scan(s) +} + +func NewNullInt64(v interface{}) (n NullInt64) { + n.Scan(v) + return +} + +func NewNullFloat64(v interface{}) (n NullFloat64) { + n.Scan(v) + return +} + +func NewNullString(v interface{}) (n NullString) { + n.Scan(v) + return +} + +func NewNullTime(v interface{}) (n NullTime) { + n.Scan(v) + return +} + +func NewNullBool(v interface{}) (n NullBool) { + n.Scan(v) + return +} + +// The `(*NullTime) Scan(interface{})` and `parseDateTime(string, *time.Location)` +// functions are slightly modified versions of code from the github.com/go-sql-driver/mysql +// package. They work with Postgres and MySQL databases. Potential future +// drivers should ensure these will work for them, or come up with an alternative. +// +// Conforming with its licensing terms the copyright notice and link to the licence +// are available below. +// +// Source: https://github.com/go-sql-driver/mysql/blob/527bcd55aab2e53314f1a150922560174b493034/utils.go#L452-L508 + +// Copyright notice from original developers: +// +// Go MySQL Driver - A MySQL-Driver for Go's database/sql package +// +// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved. +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/ + +// Scan implements the Scanner interface. +// The value type must be time.Time or string / []byte (formatted time-string), +// otherwise Scan fails. +func (n *NullTime) Scan(value interface{}) error { + var err error + + if value == nil { + n.Time, n.Valid = time.Time{}, false + return nil + } + + switch v := value.(type) { + case time.Time: + n.Time, n.Valid = v, true + return nil + case []byte: + n.Time, err = parseDateTime(string(v), time.UTC) + n.Valid = (err == nil) + return err + case string: + n.Time, err = parseDateTime(v, time.UTC) + n.Valid = (err == nil) + return err + } + + n.Valid = false + return nil +} + +func parseDateTime(str string, loc *time.Location) (time.Time, error) { + var t time.Time + var err error + + base := "0000-00-00 00:00:00.0000000" + switch len(str) { + case 10, 19, 21, 22, 23, 24, 25, 26: + if str == base[:len(str)] { + return t, err + } + t, err = time.Parse(timeFormat[:len(str)], str) + default: + err = ErrInvalidTimestring + return t, err + } + + // Adjust location + if err == nil && loc != time.UTC { + y, mo, d := t.Date() + h, mi, s := t.Clock() + t, err = time.Date(y, mo, d, h, mi, s, t.Nanosecond(), loc), nil + } + + return t, err +} diff --git a/vendor/github.com/gocraft/dbr/union.go b/vendor/github.com/gocraft/dbr/union.go new file mode 100644 index 000000000..73db6a811 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/union.go @@ -0,0 +1,43 @@ +package dbr + +type union struct { + builder []Builder + all bool +} + +func Union(builder ...Builder) interface { + Builder + As(string) Builder +} { + return &union{ + builder: builder, + } +} + +func UnionAll(builder ...Builder) interface { + Builder + As(string) Builder +} { + return &union{ + builder: builder, + all: true, + } +} + +func (u *union) Build(d Dialect, buf Buffer) error { + for i, b := range u.builder { + if i > 0 { + buf.WriteString(" UNION ") + if u.all { + buf.WriteString("ALL ") + } + } + buf.WriteString(placeholder) + buf.WriteValue(b) + } + return nil +} + +func (u *union) As(alias string) Builder { + return as(u, alias) +} diff --git a/vendor/github.com/gocraft/dbr/update.go b/vendor/github.com/gocraft/dbr/update.go new file mode 100644 index 000000000..523a11841 --- /dev/null +++ b/vendor/github.com/gocraft/dbr/update.go @@ -0,0 +1,96 @@ +package dbr + +// UpdateStmt builds `UPDATE ...` +type UpdateStmt struct { + raw + + Table string + Value map[string]interface{} + + WhereCond []Builder +} + +// Build builds `UPDATE ...` in dialect +func (b *UpdateStmt) Build(d Dialect, buf Buffer) error { + if b.raw.Query != "" { + return b.raw.Build(d, buf) + } + + if b.Table == "" { + return ErrTableNotSpecified + } + + if len(b.Value) == 0 { + return ErrColumnNotSpecified + } + + buf.WriteString("UPDATE ") + buf.WriteString(d.QuoteIdent(b.Table)) + buf.WriteString(" SET ") + + i := 0 + for col, v := range b.Value { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(d.QuoteIdent(col)) + buf.WriteString(" = ") + buf.WriteString(placeholder) + + buf.WriteValue(v) + i++ + } + + if len(b.WhereCond) > 0 { + buf.WriteString(" WHERE ") + err := And(b.WhereCond...).Build(d, buf) + if err != nil { + return err + } + } + return nil +} + +// Update creates an UpdateStmt +func Update(table string) *UpdateStmt { + return &UpdateStmt{ + Table: table, + Value: make(map[string]interface{}), + } +} + +// UpdateBySql creates an UpdateStmt with raw query +func UpdateBySql(query string, value ...interface{}) *UpdateStmt { + return &UpdateStmt{ + raw: raw{ + Query: query, + Value: value, + }, + Value: make(map[string]interface{}), + } +} + +// Where adds a where condition +func (b *UpdateStmt) Where(query interface{}, value ...interface{}) *UpdateStmt { + switch query := query.(type) { + case string: + b.WhereCond = append(b.WhereCond, Expr(query, value...)) + case Builder: + b.WhereCond = append(b.WhereCond, query) + } + return b +} + +// Set specifies a key-value pair +func (b *UpdateStmt) Set(column string, value interface{}) *UpdateStmt { + b.Value[column] = value + return b +} + +// SetMap specifies a list of key-value pair +func (b *UpdateStmt) SetMap(m map[string]interface{}) *UpdateStmt { + for col, val := range m { + b.Set(col, val) + } + return b +} diff --git a/vendor/github.com/gocraft/dbr/update_builder.go b/vendor/github.com/gocraft/dbr/update_builder.go new file mode 100644 index 000000000..abd90dddb --- /dev/null +++ b/vendor/github.com/gocraft/dbr/update_builder.go @@ -0,0 +1,97 @@ +package dbr + +import ( + "context" + "database/sql" + "fmt" +) + +type UpdateBuilder struct { + runner + EventReceiver + Dialect Dialect + + *UpdateStmt + + LimitCount int64 +} + +func (sess *Session) Update(table string) *UpdateBuilder { + return &UpdateBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + UpdateStmt: Update(table), + LimitCount: -1, + } +} + +func (tx *Tx) Update(table string) *UpdateBuilder { + return &UpdateBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + UpdateStmt: Update(table), + LimitCount: -1, + } +} + +func (sess *Session) UpdateBySql(query string, value ...interface{}) *UpdateBuilder { + return &UpdateBuilder{ + runner: sess, + EventReceiver: sess, + Dialect: sess.Dialect, + UpdateStmt: UpdateBySql(query, value...), + LimitCount: -1, + } +} + +func (tx *Tx) UpdateBySql(query string, value ...interface{}) *UpdateBuilder { + return &UpdateBuilder{ + runner: tx, + EventReceiver: tx, + Dialect: tx.Dialect, + UpdateStmt: UpdateBySql(query, value...), + LimitCount: -1, + } +} + +func (b *UpdateBuilder) Exec() (sql.Result, error) { + return b.ExecContext(context.Background()) +} + +func (b *UpdateBuilder) ExecContext(ctx context.Context) (sql.Result, error) { + return exec(ctx, b.runner, b.EventReceiver, b, b.Dialect) +} + +func (b *UpdateBuilder) Set(column string, value interface{}) *UpdateBuilder { + b.UpdateStmt.Set(column, value) + return b +} + +func (b *UpdateBuilder) SetMap(m map[string]interface{}) *UpdateBuilder { + b.UpdateStmt.SetMap(m) + return b +} + +func (b *UpdateBuilder) Where(query interface{}, value ...interface{}) *UpdateBuilder { + b.UpdateStmt.Where(query, value...) + return b +} + +func (b *UpdateBuilder) Limit(n uint64) *UpdateBuilder { + b.LimitCount = int64(n) + return b +} + +func (b *UpdateBuilder) Build(d Dialect, buf Buffer) error { + err := b.UpdateStmt.Build(b.Dialect, buf) + if err != nil { + return err + } + if b.LimitCount >= 0 { + buf.WriteString(" LIMIT ") + buf.WriteString(fmt.Sprint(b.LimitCount)) + } + return nil +} diff --git a/vendor/github.com/gocraft/dbr/util.go b/vendor/github.com/gocraft/dbr/util.go new file mode 100644 index 000000000..69f4b639e --- /dev/null +++ b/vendor/github.com/gocraft/dbr/util.go @@ -0,0 +1,71 @@ +package dbr + +import ( + "bytes" + "database/sql/driver" + "reflect" + "unicode" +) + +func camelCaseToSnakeCase(name string) string { + buf := new(bytes.Buffer) + + runes := []rune(name) + + for i := 0; i < len(runes); i++ { + buf.WriteRune(unicode.ToLower(runes[i])) + if i != len(runes)-1 && unicode.IsUpper(runes[i+1]) && + (unicode.IsLower(runes[i]) || unicode.IsDigit(runes[i]) || + (i != len(runes)-2 && unicode.IsLower(runes[i+2]))) { + buf.WriteRune('_') + } + } + + return buf.String() +} + +func structMap(value reflect.Value) map[string]reflect.Value { + m := make(map[string]reflect.Value) + structValue(m, value) + return m +} + +var ( + typeValuer = reflect.TypeOf((*driver.Valuer)(nil)).Elem() +) + +func structValue(m map[string]reflect.Value, value reflect.Value) { + if value.Type().Implements(typeValuer) { + return + } + switch value.Kind() { + case reflect.Ptr: + if value.IsNil() { + return + } + structValue(m, value.Elem()) + case reflect.Struct: + t := value.Type() + for i := 0; i < t.NumField(); i++ { + field := t.Field(i) + if field.PkgPath != "" && !field.Anonymous { + // unexported + continue + } + tag := field.Tag.Get("db") + if tag == "-" { + // ignore + continue + } + if tag == "" { + // no tag, but we can record the field name + tag = camelCaseToSnakeCase(field.Name) + } + fieldValue := value.Field(i) + if _, ok := m[tag]; !ok { + m[tag] = fieldValue + } + structValue(m, fieldValue) + } + } +} diff --git a/vendor/github.com/golang/example/LICENSE b/vendor/github.com/golang/example/LICENSE new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/vendor/github.com/golang/example/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/vendor/github.com/golang/example/stringutil/reverse.go b/vendor/github.com/golang/example/stringutil/reverse.go new file mode 100644 index 000000000..d0bfd6143 --- /dev/null +++ b/vendor/github.com/golang/example/stringutil/reverse.go @@ -0,0 +1,27 @@ +/* +Copyright 2014 Google Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package stringutil contains utility functions for working with strings. +package stringutil + +// Reverse returns its argument string reversed rune-wise left to right. +func Reverse(s string) string { + r := []rune(s) + for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { + r[i], r[j] = r[j], r[i] + } + return string(r) +} diff --git a/vendor/github.com/google/go-querystring/LICENSE b/vendor/github.com/google/go-querystring/LICENSE new file mode 100644 index 000000000..ae121a1e4 --- /dev/null +++ b/vendor/github.com/google/go-querystring/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2013 Google. 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. diff --git a/vendor/github.com/google/go-querystring/query/encode.go b/vendor/github.com/google/go-querystring/query/encode.go new file mode 100644 index 000000000..37080b19b --- /dev/null +++ b/vendor/github.com/google/go-querystring/query/encode.go @@ -0,0 +1,320 @@ +// Copyright 2013 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 query implements encoding of structs into URL query parameters. +// +// As a simple example: +// +// type Options struct { +// Query string `url:"q"` +// ShowAll bool `url:"all"` +// Page int `url:"page"` +// } +// +// opt := Options{ "foo", true, 2 } +// v, _ := query.Values(opt) +// fmt.Print(v.Encode()) // will output: "q=foo&all=true&page=2" +// +// The exact mapping between Go values and url.Values is described in the +// documentation for the Values() function. +package query + +import ( + "bytes" + "fmt" + "net/url" + "reflect" + "strconv" + "strings" + "time" +) + +var timeType = reflect.TypeOf(time.Time{}) + +var encoderType = reflect.TypeOf(new(Encoder)).Elem() + +// Encoder is an interface implemented by any type that wishes to encode +// itself into URL values in a non-standard way. +type Encoder interface { + EncodeValues(key string, v *url.Values) error +} + +// Values returns the url.Values encoding of v. +// +// Values expects to be passed a struct, and traverses it recursively using the +// following encoding rules. +// +// Each exported struct field is encoded as a URL parameter unless +// +// - the field's tag is "-", or +// - the field is empty and its tag specifies the "omitempty" option +// +// The empty values are false, 0, any nil pointer or interface value, any array +// slice, map, or string of length zero, and any time.Time that returns true +// for IsZero(). +// +// The URL parameter name defaults to the struct field name but can be +// specified in the struct field's tag value. The "url" key in the struct +// field's tag value is the key name, followed by an optional comma and +// options. For example: +// +// // Field is ignored by this package. +// Field int `url:"-"` +// +// // Field appears as URL parameter "myName". +// Field int `url:"myName"` +// +// // Field appears as URL parameter "myName" and the field is omitted if +// // its value is empty +// Field int `url:"myName,omitempty"` +// +// // Field appears as URL parameter "Field" (the default), but the field +// // is skipped if empty. Note the leading comma. +// Field int `url:",omitempty"` +// +// For encoding individual field values, the following type-dependent rules +// apply: +// +// Boolean values default to encoding as the strings "true" or "false". +// Including the "int" option signals that the field should be encoded as the +// strings "1" or "0". +// +// time.Time values default to encoding as RFC3339 timestamps. Including the +// "unix" option signals that the field should be encoded as a Unix time (see +// time.Unix()) +// +// Slice and Array values default to encoding as multiple URL values of the +// same name. Including the "comma" option signals that the field should be +// encoded as a single comma-delimited value. Including the "space" option +// similarly encodes the value as a single space-delimited string. Including +// the "semicolon" option will encode the value as a semicolon-delimited string. +// Including the "brackets" option signals that the multiple URL values should +// have "[]" appended to the value name. "numbered" will append a number to +// the end of each incidence of the value name, example: +// name0=value0&name1=value1, etc. +// +// Anonymous struct fields are usually encoded as if their inner exported +// fields were fields in the outer struct, subject to the standard Go +// visibility rules. An anonymous struct field with a name given in its URL +// tag is treated as having that name, rather than being anonymous. +// +// Non-nil pointer values are encoded as the value pointed to. +// +// Nested structs are encoded including parent fields in value names for +// scoping. e.g: +// +// "user[name]=acme&user[addr][postcode]=1234&user[addr][city]=SFO" +// +// All other values are encoded using their default string representation. +// +// Multiple fields that encode to the same URL parameter name will be included +// as multiple URL values of the same name. +func Values(v interface{}) (url.Values, error) { + values := make(url.Values) + val := reflect.ValueOf(v) + for val.Kind() == reflect.Ptr { + if val.IsNil() { + return values, nil + } + val = val.Elem() + } + + if v == nil { + return values, nil + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("query: Values() expects struct input. Got %v", val.Kind()) + } + + err := reflectValue(values, val, "") + return values, err +} + +// reflectValue populates the values parameter from the struct fields in val. +// Embedded structs are followed recursively (using the rules defined in the +// Values function documentation) breadth-first. +func reflectValue(values url.Values, val reflect.Value, scope string) error { + var embedded []reflect.Value + + typ := val.Type() + for i := 0; i < typ.NumField(); i++ { + sf := typ.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + + sv := val.Field(i) + tag := sf.Tag.Get("url") + if tag == "-" { + continue + } + name, opts := parseTag(tag) + if name == "" { + if sf.Anonymous && sv.Kind() == reflect.Struct { + // save embedded struct for later processing + embedded = append(embedded, sv) + continue + } + + name = sf.Name + } + + if scope != "" { + name = scope + "[" + name + "]" + } + + if opts.Contains("omitempty") && isEmptyValue(sv) { + continue + } + + if sv.Type().Implements(encoderType) { + if !reflect.Indirect(sv).IsValid() { + sv = reflect.New(sv.Type().Elem()) + } + + m := sv.Interface().(Encoder) + if err := m.EncodeValues(name, &values); err != nil { + return err + } + continue + } + + if sv.Kind() == reflect.Slice || sv.Kind() == reflect.Array { + var del byte + if opts.Contains("comma") { + del = ',' + } else if opts.Contains("space") { + del = ' ' + } else if opts.Contains("semicolon") { + del = ';' + } else if opts.Contains("brackets") { + name = name + "[]" + } + + if del != 0 { + s := new(bytes.Buffer) + first := true + for i := 0; i < sv.Len(); i++ { + if first { + first = false + } else { + s.WriteByte(del) + } + s.WriteString(valueString(sv.Index(i), opts)) + } + values.Add(name, s.String()) + } else { + for i := 0; i < sv.Len(); i++ { + k := name + if opts.Contains("numbered") { + k = fmt.Sprintf("%s%d", name, i) + } + values.Add(k, valueString(sv.Index(i), opts)) + } + } + continue + } + + for sv.Kind() == reflect.Ptr { + if sv.IsNil() { + break + } + sv = sv.Elem() + } + + if sv.Type() == timeType { + values.Add(name, valueString(sv, opts)) + continue + } + + if sv.Kind() == reflect.Struct { + reflectValue(values, sv, name) + continue + } + + values.Add(name, valueString(sv, opts)) + } + + for _, f := range embedded { + if err := reflectValue(values, f, scope); err != nil { + return err + } + } + + return nil +} + +// valueString returns the string representation of a value. +func valueString(v reflect.Value, opts tagOptions) string { + for v.Kind() == reflect.Ptr { + if v.IsNil() { + return "" + } + v = v.Elem() + } + + if v.Kind() == reflect.Bool && opts.Contains("int") { + if v.Bool() { + return "1" + } + return "0" + } + + if v.Type() == timeType { + t := v.Interface().(time.Time) + if opts.Contains("unix") { + return strconv.FormatInt(t.Unix(), 10) + } + return t.Format(time.RFC3339) + } + + return fmt.Sprint(v.Interface()) +} + +// isEmptyValue checks if a value should be considered empty for the purposes +// of omitting fields with the "omitempty" option. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + + if v.Type() == timeType { + return v.Interface().(time.Time).IsZero() + } + + return false +} + +// tagOptions is the string following a comma in a struct field's "url" tag, or +// the empty string. It does not include the leading comma. +type tagOptions []string + +// parseTag splits a struct field's url tag into its name and comma-separated +// options. +func parseTag(tag string) (string, tagOptions) { + s := strings.Split(tag, ",") + return s[0], s[1:] +} + +// Contains checks whether the tagOptions contains the specified option. +func (o tagOptions) Contains(option string) bool { + for _, s := range o { + if s == option { + return true + } + } + return false +} diff --git a/vendor/github.com/kubesphere/sonargo/LICENSE b/vendor/github.com/kubesphere/sonargo/LICENSE new file mode 100644 index 000000000..d9a10c0d8 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/kubesphere/sonargo/sonar/ce_service.go b/vendor/github.com/kubesphere/sonargo/sonar/ce_service.go new file mode 100644 index 000000000..553e563e9 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/ce_service.go @@ -0,0 +1,124 @@ +// Get information on Compute Engine tasks. +package sonargo + +import "net/http" + +type CeService struct { + client *Client +} + +type CeActivityObject struct { + Tasks []*Task `json:"tasks,omitempty"` +} + +type CeComponentObject struct { + Current *Task `json:"current,omitempty"` + Queue []*Task `json:"queue,omitempty"` +} + +type CeTaskObject struct { + Task *Task `json:"task,omitempty"` +} + +type Task struct { + AnalysisID string `json:"analysisId,omitempty"` + ComponentID string `json:"componentId,omitempty"` + ComponentKey string `json:"componentKey,omitempty"` + ComponentName string `json:"componentName,omitempty"` + ComponentQualifier string `json:"componentQualifier,omitempty"` + ErrorMessage string `json:"errorMessage,omitempty"` + ErrorStacktrace string `json:"errorStacktrace,omitempty"` + ErrorType string `json:"errorType,omitempty"` + ExecutedAt string `json:"executedAt,omitempty"` + ExecutionTimeMs int64 `json:"executionTimeMs,omitempty"` + FinishedAt string `json:"finishedAt,omitempty"` + HasErrorStacktrace bool `json:"hasErrorStacktrace,omitempty"` + HasScannerContext bool `json:"hasScannerContext,omitempty"` + ID string `json:"id,omitempty"` + Logs bool `json:"logs,omitempty"` + Organization string `json:"organization,omitempty"` + ScannerContext string `json:"scannerContext,omitempty"` + StartedAt string `json:"startedAt,omitempty"` + Status string `json:"status,omitempty"` + SubmittedAt string `json:"submittedAt,omitempty"` + SubmitterLogin string `json:"submitterLogin,omitempty"` + TaskType string `json:"taskType,omitempty"` + Type string `json:"type,omitempty"` + WarningCount int `json:"warningCount,omitempty"` + Warnings []string `json:"warnings,omitempty"` +} + +type CeActivityOption struct { + ComponentId string `url:"componentId,omitempty"` // Description:"Id of the component (project) to filter on",ExampleValue:"AU-TpxcA-iU5OvuD2FL0" + MaxExecutedAt string `url:"maxExecutedAt,omitempty"` // Description:"Maximum date of end of task processing (inclusive)",ExampleValue:"2017-10-19T13:00:00+0200" + MinSubmittedAt string `url:"minSubmittedAt,omitempty"` // Description:"Minimum date of task submission (inclusive)",ExampleValue:"2017-10-19T13:00:00+0200" + OnlyCurrents string `url:"onlyCurrents,omitempty"` // Description:"Filter on the last tasks (only the most recent finished task by project)",ExampleValue:"" + Ps string `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 1000",ExampleValue:"20" + Q string `url:"q,omitempty"` // Description:"Limit search to: Must not be set together with componentId",ExampleValue:"Apache" + Status string `url:"status,omitempty"` // Description:"Comma separated list of task statuses",ExampleValue:"IN_PROGRESS,SUCCESS" + Type string `url:"type,omitempty"` // Description:"Task type",ExampleValue:"REPORT" +} + +// Activity Search for tasks.
Requires the system administration permission, or project administration permission if componentId is set. +func (s *CeService) Activity(opt *CeActivityOption) (v *CeActivityObject, resp *http.Response, err error) { + err = s.ValidateActivityOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "ce/activity", opt) + if err != nil { + return + } + v = new(CeActivityObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type CeComponentOption struct { + Component string `url:"component,omitempty"` // Description:"",ExampleValue:"my_project" + ComponentId string `url:"componentId,omitempty"` // Description:"",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" +} + +// Component Get the pending tasks, in-progress tasks and the last executed task of a given component (usually a project).
Requires the following permission: 'Browse' on the specified component.
Either 'componentId' or 'component' must be provided. +func (s *CeService) Component(opt *CeComponentOption) (v *CeComponentObject, resp *http.Response, err error) { + err = s.ValidateComponentOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "ce/component", opt) + if err != nil { + return + } + v = new(CeComponentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type CeTaskOption struct { + AdditionalFields string `url:"additionalFields,omitempty"` // Description:"Comma-separated list of the optional fields to be returned in response.",ExampleValue:"" + Id string `url:"id,omitempty"` // Description:"Id of task",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" +} + +// Task Give Compute Engine task details such as type, status, duration and associated component.
Requires 'Administer System' or 'Execute Analysis' permission.
Since 6.1, field "logs" is deprecated and its value is always false. +func (s *CeService) Task(opt *CeTaskOption) (v *CeTaskObject, resp *http.Response, err error) { + err = s.ValidateTaskOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "ce/task", opt) + if err != nil { + return + } + v = new(CeTaskObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/client.go b/vendor/github.com/kubesphere/sonargo/sonar/client.go new file mode 100644 index 000000000..04e8a4bf8 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/client.go @@ -0,0 +1,123 @@ +package sonargo + +import ( + "net/http" + "net/url" +) + +type Client struct { + baseURL *url.URL + username, password, token string + httpClient *http.Client + Ce *CeService + Components *ComponentsService + Duplications *DuplicationsService + Favorites *FavoritesService + Issues *IssuesService + Languages *LanguagesService + Measures *MeasuresService + Metrics *MetricsService + Notifications *NotificationsService + Permissions *PermissionsService + Plugins *PluginsService + ProjectAnalyses *ProjectAnalysesService + ProjectBadges *ProjectBadgesService + ProjectBranches *ProjectBranchesService + ProjectLinks *ProjectLinksService + ProjectPullRequests *ProjectPullRequestsService + ProjectTags *ProjectTagsService + Projects *ProjectsService + Qualitygates *QualitygatesService + QualityProfiles *QualityProfilesService + Rules *RulesService + Server *ServerService + Settings *SettingsService + Sources *SourcesService + System *SystemService + UserGroups *UserGroupsService + UserTokens *UserTokensService + Users *UsersService + Webhooks *WebhooksService +} + +func NewClient(endpoint, username, password string) (*Client, error) { + c := &Client{username: username, password: password, httpClient: http.DefaultClient} + if endpoint == "" { + c.SetBaseURL(defaultBaseURL) + } else { + if err := c.SetBaseURL(endpoint); err != nil { + return nil, err + } + } + c.Ce = &CeService{client: c} + c.Components = &ComponentsService{client: c} + c.Duplications = &DuplicationsService{client: c} + c.Favorites = &FavoritesService{client: c} + c.Issues = &IssuesService{client: c} + c.Languages = &LanguagesService{client: c} + c.Measures = &MeasuresService{client: c} + c.Metrics = &MetricsService{client: c} + c.Notifications = &NotificationsService{client: c} + c.Permissions = &PermissionsService{client: c} + c.Plugins = &PluginsService{client: c} + c.ProjectAnalyses = &ProjectAnalysesService{client: c} + c.ProjectBadges = &ProjectBadgesService{client: c} + c.ProjectBranches = &ProjectBranchesService{client: c} + c.ProjectLinks = &ProjectLinksService{client: c} + c.ProjectPullRequests = &ProjectPullRequestsService{client: c} + c.ProjectTags = &ProjectTagsService{client: c} + c.Projects = &ProjectsService{client: c} + c.Qualitygates = &QualitygatesService{client: c} + c.QualityProfiles = &QualityProfilesService{client: c} + c.Rules = &RulesService{client: c} + c.Server = &ServerService{client: c} + c.Settings = &SettingsService{client: c} + c.Sources = &SourcesService{client: c} + c.System = &SystemService{client: c} + c.UserGroups = &UserGroupsService{client: c} + c.UserTokens = &UserTokensService{client: c} + c.Users = &UsersService{client: c} + c.Webhooks = &WebhooksService{client: c} + return c, nil +} + +func NewClientWithToken(endpoint, token string) (*Client, error) { + c := &Client{username: token, password: "", httpClient: http.DefaultClient} + if endpoint == "" { + c.SetBaseURL(defaultBaseURL) + } else { + if err := c.SetBaseURL(endpoint); err != nil { + return nil, err + } + } + c.Ce = &CeService{client: c} + c.Components = &ComponentsService{client: c} + c.Duplications = &DuplicationsService{client: c} + c.Favorites = &FavoritesService{client: c} + c.Issues = &IssuesService{client: c} + c.Languages = &LanguagesService{client: c} + c.Measures = &MeasuresService{client: c} + c.Metrics = &MetricsService{client: c} + c.Notifications = &NotificationsService{client: c} + c.Permissions = &PermissionsService{client: c} + c.Plugins = &PluginsService{client: c} + c.ProjectAnalyses = &ProjectAnalysesService{client: c} + c.ProjectBadges = &ProjectBadgesService{client: c} + c.ProjectBranches = &ProjectBranchesService{client: c} + c.ProjectLinks = &ProjectLinksService{client: c} + c.ProjectPullRequests = &ProjectPullRequestsService{client: c} + c.ProjectTags = &ProjectTagsService{client: c} + c.Projects = &ProjectsService{client: c} + c.Qualitygates = &QualitygatesService{client: c} + c.QualityProfiles = &QualityProfilesService{client: c} + c.Rules = &RulesService{client: c} + c.Server = &ServerService{client: c} + c.Settings = &SettingsService{client: c} + c.Sources = &SourcesService{client: c} + c.System = &SystemService{client: c} + c.UserGroups = &UserGroupsService{client: c} + c.UserTokens = &UserTokensService{client: c} + c.Users = &UsersService{client: c} + c.Webhooks = &WebhooksService{client: c} + return c, nil +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/client_util.go b/vendor/github.com/kubesphere/sonargo/sonar/client_util.go new file mode 100644 index 000000000..68fb3b890 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/client_util.go @@ -0,0 +1,179 @@ +package sonargo + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "sort" + "strings" + + "github.com/golang/glog" + "github.com/google/go-querystring/query" +) + +// SetBaseURL sets the base URL for API requests to a custom endpoint. urlStr +// should always be specified with a trailing slash. +func SetBaseURLUtil(urlStr string) (*url.URL, error) { + // Make sure the given URL end with a slash + if !strings.HasSuffix(urlStr, "/") { + urlStr += "/" + } + + baseURL, err := url.Parse(urlStr) + if err != nil { + return nil, err + } + + // Update the base URL of the client. + return baseURL, nil +} + +// NewRequest creates an API request. A relative URL path can be provided in +// urlStr, in which case it is resolved relative to the base URL of the Client. +// Relative URL paths should always be specified without a preceding slash. If +// specified, the value pointed to by body is JSON encoded and included as the +// request body. +func NewRequest(method, path string, baseURL *url.URL, username, password string, opt interface{}) (*http.Request, error) { + // Set the encoded opaque data + u := *baseURL + unescaped, err := url.PathUnescape(path) + if err != nil { + return nil, err + } + u.RawPath = u.Path + path + u.Path = u.Path + unescaped + if opt != nil { + q, err := query.Values(opt) + if err != nil { + return nil, err + } + u.RawQuery = q.Encode() + } + + req := &http.Request{ + Method: method, + URL: &u, + Proto: "HTTP/1.1", + Header: make(http.Header), + Host: u.Host, + } + + if method == "POST" || method == "PUT" { + //SonarQube use RawQuery even method is POST + bodyBytes, err := json.Marshal(opt) + if err != nil { + return nil, err + } + bodyReader := bytes.NewReader(bodyBytes) + + u.RawQuery = "" + req.Body = ioutil.NopCloser(bodyReader) + req.ContentLength = int64(bodyReader.Len()) + req.Header.Set("Content-Type", "application/json") + } + + req.Header.Set("Accept", "application/json") + req.SetBasicAuth(username, password) + return req, nil +} + +// Do sends an API request and returns the API response. The API response is +// JSON decoded and stored in the value pointed to by v, or returned as an +// error if an API error has occurred. If v implements the io.Writer +// interface, the raw response body will be written to v, without attempting to +// first decode it. +func Do(c *http.Client, req *http.Request, v interface{}) (*http.Response, error) { + isText := false + if _, ok := v.(*string); ok { + req.Header.Set("Accept", "text/plain") + isText = true + } + glog.V(1).Infof("[%s] %s\n", req.Method, req.URL.String()) + resp, err := c.Do(req) + if err != nil { + return nil, err + } + err = CheckResponse(resp) + if err != nil { + return resp, err + } + if v != nil { + defer resp.Body.Close() + if w, ok := v.(io.Writer); ok { + _, err = io.Copy(w, resp.Body) + } else { + if isText { + byts, err := ioutil.ReadAll(resp.Body) + if err != nil { + return resp, err + } + w := v.(*string) + *w = string(byts) + } else { + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(v) + } + } + } + return resp, err +} + +type ErrorResponse struct { + Body []byte + Response *http.Response + Message string +} + +func (e *ErrorResponse) Error() string { + u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, e.Response.Request.URL.RequestURI()) + return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message) +} +func CheckResponse(r *http.Response) error { + switch r.StatusCode { + case 200, 201, 202, 204, 304: + return nil + } + + errorResponse := &ErrorResponse{Response: r} + data, err := ioutil.ReadAll(r.Body) + if err == nil && data != nil { + errorResponse.Body = data + + var raw interface{} + if err := json.Unmarshal(data, &raw); err != nil { + errorResponse.Message = string(data) + } else { + errorResponse.Message = parseError(raw) + } + } + + return errorResponse +} +func parseError(raw interface{}) string { + switch raw := raw.(type) { + case string: + return raw + + case []interface{}: + var errs []string + for _, v := range raw { + errs = append(errs, parseError(v)) + } + return fmt.Sprintf("[%s]", strings.Join(errs, ", ")) + + case map[string]interface{}: + var errs []string + for k, v := range raw { + errs = append(errs, fmt.Sprintf("{%s: %s}", k, parseError(v))) + } + sort.Strings(errs) + return strings.Join(errs, ", ") + + default: + return fmt.Sprintf("failed to parse unexpected error type: %T", raw) + } +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/components_service.go b/vendor/github.com/kubesphere/sonargo/sonar/components_service.go new file mode 100644 index 000000000..5de64f7a9 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/components_service.go @@ -0,0 +1,125 @@ +// Get information about a component (file, directory, project, ...) and its ancestors or descendants. Update a project or module key. +package sonargo + +import "net/http" + +type ComponentsService struct { + client *Client +} + +type ComponentsSearchObject struct { + Components []*Component `json:"components,omitempty"` + Paging *Paging `json:"paging,omitempty"` +} + +type ComponentsShowObject struct { + Ancestors []*Component `json:"ancestors,omitempty"` + Component *Component `json:"component,omitempty"` +} + +type Component struct { + AnalysisDate string `json:"analysisDate,omitempty"` + Description string `json:"description,omitempty"` + Enabled bool `json:"enabled,omitempty"` + ID string `json:"id,omitempty"` + Key string `json:"key,omitempty"` + Language string `json:"language,omitempty"` + LastAnalysisDate string `json:"lastAnalysisDate,omitempty"` + LeakPeriodDate string `json:"leakPeriodDate,omitempty"` + LongName string `json:"longName,omitempty"` + Measures []*SonarMeasure `json:"measures,omitempty"` + Name string `json:"name,omitempty"` + Organization string `json:"organization,omitempty"` + Path string `json:"path,omitempty"` + Project string `json:"project,omitempty"` + Qualifier string `json:"qualifier,omitempty"` + Tags []string `json:"tags,omitempty"` + UUID string `json:"uuid,omitempty"` + Version string `json:"version,omitempty"` + Visibility string `json:"visibility,omitempty"` +} + +type ComponentsTreeObject struct { + BaseComponent Component `json:"baseComponent,omitempty"` + Components []*Component `json:"components,omitempty"` + Paging Paging `json:"paging,omitempty"` +} + +type ComponentsSearchOption struct { + Language string `url:"language,omitempty"` // Description:"Language key. If provided, only components for the given language are returned.",ExampleValue:"py" + P string `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps string `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 500",ExampleValue:"20" + Q string `url:"q,omitempty"` // Description:"Limit search to: ",ExampleValue:"sonar" + Qualifiers string `url:"qualifiers,omitempty"` // Description:"Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. Possible values are:",ExampleValue:"" +} + +// Search Search for components +func (s *ComponentsService) Search(opt *ComponentsSearchOption) (v *ComponentsSearchObject, resp *http.Response, err error) { + err = s.ValidateSearchOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "components/search", opt) + if err != nil { + return + } + v = new(ComponentsSearchObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type ComponentsShowOption struct { + Component string `url:"component,omitempty"` // Description:"Component key",ExampleValue:"my_project" + ComponentId string `url:"componentId,omitempty"` // Description:"Component id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" +} + +// Show Returns a component (file, directory, project, view…) and its ancestors. The ancestors are ordered from the parent to the root project. The 'componentId' or 'component' parameter must be provided.
Requires the following permission: 'Browse' on the project of the specified component. +func (s *ComponentsService) Show(opt *ComponentsShowOption) (v *ComponentsShowObject, resp *http.Response, err error) { + err = s.ValidateShowOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "components/show", opt) + if err != nil { + return + } + v = new(ComponentsShowObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type ComponentsTreeOption struct { + Asc string `url:"asc,omitempty"` // Description:"Ascending sort",ExampleValue:"" + Component string `url:"component,omitempty"` // Description:"Base component key. The search is based on this component.",ExampleValue:"my_project" + ComponentId string `url:"componentId,omitempty"` // Description:"Base component id. The search is based on this component.",ExampleValue:"AU-TpxcA-iU5OvuD2FLz" + P string `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps string `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 500",ExampleValue:"20" + Q string `url:"q,omitempty"` // Description:"Limit search to: ",ExampleValue:"FILE_NAM" + Qualifiers string `url:"qualifiers,omitempty"` // Description:"Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. Possible values are:",ExampleValue:"" + S string `url:"s,omitempty"` // Description:"Comma-separated list of sort fields",ExampleValue:"name, path" + Strategy string `url:"strategy,omitempty"` // Description:"Strategy to search for base component descendants:",ExampleValue:"" +} + +// Tree Navigate through components based on the chosen strategy. The componentId or the component parameter must be provided.
Requires the following permission: 'Browse' on the specified project.
When limiting search with the q parameter, directories are not returned. +func (s *ComponentsService) Tree(opt *ComponentsTreeOption) (v *ComponentsTreeObject, resp *http.Response, err error) { + err = s.ValidateTreeOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "components/tree", opt) + if err != nil { + return + } + v = new(ComponentsTreeObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/duplications_service.go b/vendor/github.com/kubesphere/sonargo/sonar/duplications_service.go new file mode 100644 index 000000000..d4e9fe819 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/duplications_service.go @@ -0,0 +1,58 @@ +// Get duplication information for a project. +package sonargo + +import "net/http" + +type DuplicationsService struct { + client *Client +} + +type DuplicationsShowObject struct { + Duplications []*Duplication `json:"duplications,omitempty"` + Files *Files `json:"files,omitempty"` +} + +type Block struct { + Ref string `json:"_ref,omitempty"` + From int64 `json:"from,omitempty"` + Size int64 `json:"size,omitempty"` +} + +type Duplication struct { + Blocks []*Block `json:"blocks,omitempty"` +} + +type Files struct { + One *File `json:"1,omitempty"` + Two *File `json:"2,omitempty"` + Three *File `json:"3,omitempty"` +} + +type File struct { + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + ProjectName string `json:"projectName,omitempty"` +} + +type DuplicationsShowOption struct { + Key string `url:"key,omitempty"` // Description:"File key",ExampleValue:"my_project:/src/foo/Bar.php" + Uuid string `url:"uuid,omitempty"` // Description:"File ID. If provided, 'key' must not be provided.",ExampleValue:"584a89f2-8037-4f7b-b82c-8b45d2d63fb2" +} + +// Show Get duplications. Require Browse permission on file's project +func (s *DuplicationsService) Show(opt *DuplicationsShowOption) (v *DuplicationsShowObject, resp *http.Response, err error) { + err = s.ValidateShowOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "duplications/show", opt) + if err != nil { + return + } + v = new(DuplicationsShowObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/favorites_service.go b/vendor/github.com/kubesphere/sonargo/sonar/favorites_service.go new file mode 100644 index 000000000..80b6efdf8 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/favorites_service.go @@ -0,0 +1,85 @@ +// Manage user favorites +package sonargo + +import "net/http" + +type FavoritesService struct { + client *Client +} + +type FavoritesSearchObject struct { + Favorites []*Favorite `json:"favorites,omitempty"` + Paging Paging `json:"paging,omitempty"` +} + +type Favorite struct { + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + Organization string `json:"organization,omitempty"` + Qualifier string `json:"qualifier,omitempty"` +} + +type FavoritesAddOption struct { + Component string `url:"component,omitempty"` // Description:"Component key",ExampleValue:"my_project:/src/foo/Bar.php" +} + +// Add Add a component (project, directory, file etc.) as favorite for the authenticated user.
Requires authentication and the following permission: 'Browse' on the project of the specified component. +func (s *FavoritesService) Add(opt *FavoritesAddOption) (resp *http.Response, err error) { + err = s.ValidateAddOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "favorites/add", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type FavoritesRemoveOption struct { + Component string `url:"component,omitempty"` // Description:"Component key",ExampleValue:"my_project" +} + +// Remove Remove a component (project, directory, file etc.) as favorite for the authenticated user.
Requires authentication. +func (s *FavoritesService) Remove(opt *FavoritesRemoveOption) (resp *http.Response, err error) { + err = s.ValidateRemoveOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "favorites/remove", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type FavoritesSearchOption struct { + P int `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps int `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 500",ExampleValue:"20" +} + +// Search Search for the authenticated user favorites.
Requires authentication. +func (s *FavoritesService) Search(opt *FavoritesSearchOption) (v *FavoritesSearchObject, resp *http.Response, err error) { + err = s.ValidateSearchOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "favorites/search", opt) + if err != nil { + return + } + v = new(FavoritesSearchObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/issues_service.go b/vendor/github.com/kubesphere/sonargo/sonar/issues_service.go new file mode 100644 index 000000000..969eedc2c --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/issues_service.go @@ -0,0 +1,450 @@ +// Read and update issues. +package sonargo + +import "net/http" + +type IssuesService struct { + client *Client +} + +const ( + SeverityINFO = "INFO" + SeverityMINOR = "MINOR" + SeverityMAJOR = "MAJOR" + SeverityCRITICAL = "CRITICAL" + SeverityBLOCKER = "BLOCKER" + + IssueTypeBug = "BUG" + IssueTypeCodeSmell = "CODE_SMELL" + IssueTypeVulnerability = "VULNERABILITY" +) + +type IssuesAddCommentObject struct { + Components []*Component `json:"components,omitempty"` + Issue *Issue `json:"issue,omitempty"` + Rules []*Rule `json:"rules,omitempty"` + Users []*User `json:"users,omitempty"` +} + +type IssuesSearchObject struct { + Components []*Component `json:"components,omitempty"` + EffortTotal int `json:"effortTotal,omitempty"` + DebtTotal int `json:"debtTotal,omitempty"` + Issues []*Issue `json:"issues,omitempty"` + P int `json:"p,omitempty"` + Ps int `json:"ps,omitempty"` + Paging *Paging `json:"paging,omitempty"` + Rules []*Rule `json:"rules,omitempty"` + Total int `json:"total,omitempty"` + Users []*User `json:"users,omitempty"` + Facets []*Facet `json:"facets,omitempty"` +} + +type Comment struct { + CreatedAt string `json:"createdAt,omitempty"` + HTMLText string `json:"htmlText,omitempty"` + Key string `json:"key,omitempty"` + Login string `json:"login,omitempty"` + Markdown string `json:"markdown,omitempty"` + Updatable bool `json:"updatable,omitempty"` +} + +type Issue struct { + Actions []string `json:"actions,omitempty"` + Assignee string `json:"assignee,omitempty"` + Author string `json:"author,omitempty"` + Comments []*Comment `json:"comments,omitempty"` + Component string `json:"component,omitempty"` + CreationDate string `json:"creationDate,omitempty"` + Debt string `json:"debt,omitempty"` + Effort string `json:"effort,omitempty"` + Flows []interface{} `json:"flows,omitempty"` + Hash string `json:"hash,omitempty"` + Key string `json:"key,omitempty"` + Line int `json:"line,omitempty"` + Message string `json:"message,omitempty"` + Organization string `json:"organization,omitempty"` + Project string `json:"project,omitempty"` + Rule string `json:"rule,omitempty"` + Severity string `json:"severity,omitempty"` + Status string `json:"status,omitempty"` + Tags []string `json:"tags,omitempty"` + TextRange *TextRange `json:"textRange,omitempty"` + Transitions []string `json:"transitions,omitempty"` + Type string `json:"type,omitempty"` + UpdateDate string `json:"updateDate,omitempty"` + FromHotspot bool `json:"fromHotspot,omitempty"` + Resolution string `json:"resolution,omitempty"` + CloseDate string `json:"closeDate,omitempty"` +} + +type TextRange struct { + EndLine int `json:"endLine,omitempty"` + EndOffset int `json:"endOffset,omitempty"` + StartLine int `json:"startLine,omitempty"` + StartOffset int `json:"startOffset,omitempty"` +} + +type IssuesAuthorsObject struct { + Authors []string `json:"authors,omitempty"` +} + +type IssuesBulkChangeObject struct { + Failures int `json:"failures,omitempty"` + Ignored int `json:"ignored,omitempty"` + Success int `json:"success,omitempty"` + Total int `json:"total,omitempty"` +} + +type IssuesChangelogObject struct { + Changelog []*Changelog `json:"changelog,omitempty"` +} + +type Changelog struct { + Avatar string `json:"avatar,omitempty"` + CreationDate string `json:"creationDate,omitempty"` + Diffs []*Diff `json:"diffs,omitempty"` + User string `json:"user,omitempty"` + UserName string `json:"userName,omitempty"` +} + +type Diff struct { + Key string `json:"key,omitempty"` + NewValue string `json:"newValue,omitempty"` + OldValue string `json:"oldValue,omitempty"` +} + +type IssuesTagsObject struct { + Tags []string `json:"tags,omitempty"` +} + +type IssuesAddCommentOption struct { + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + Text string `url:"text,omitempty"` // Description:"Comment text",ExampleValue:"Won't fix because it doesn't apply to the context" +} + +// AddComment Add a comment.
Requires authentication and the following permission: 'Browse' on the project of the specified issue. +func (s *IssuesService) AddComment(opt *IssuesAddCommentOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateAddCommentOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/add_comment", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesAssignOption struct { + Assignee string `url:"assignee,omitempty"` // Description:"Login of the assignee. When not set, it will unassign the issue. Use '_me' to assign to current user",ExampleValue:"admin" + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" +} + +// Assign Assign/Unassign an issue. Requires authentication and Browse permission on project +func (s *IssuesService) Assign(opt *IssuesAssignOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateAssignOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/assign", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesAuthorsOption struct { + Ps int `url:"ps,omitempty"` // Description:"The size of the list to return",ExampleValue:"25" + Q string `url:"q,omitempty"` // Description:"A pattern to match SCM accounts against",ExampleValue:"luke" +} + +// Authors Search SCM accounts which match a given query +func (s *IssuesService) Authors(opt *IssuesAuthorsOption) (v *IssuesAuthorsObject, resp *http.Response, err error) { + err = s.ValidateAuthorsOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "issues/authors", opt) + if err != nil { + return + } + v = new(IssuesAuthorsObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesBulkChangeOption struct { + AddTags string `url:"add_tags,omitempty"` // Description:"Add tags",ExampleValue:"security,java8" + Assign string `url:"assign,omitempty"` // Description:"To assign the list of issues to a specific user (login), or un-assign all the issues",ExampleValue:"john.smith" + Comment string `url:"comment,omitempty"` // Description:"To add a comment to a list of issues",ExampleValue:"Here is my comment" + DoTransition string `url:"do_transition,omitempty"` // Description:"Transition",ExampleValue:"reopen" + Issues string `url:"issues,omitempty"` // Description:"Comma-separated list of issue keys",ExampleValue:"AU-Tpxb--iU5OvuD2FLy,AU-TpxcA-iU5OvuD2FLz" + Plan string `url:"plan,omitempty"` // Description:"In 5.5, action plans are dropped. Has no effect. To plan the list of issues to a specific action plan (key), or unlink all the issues from an action plan",ExampleValue:"" + RemoveTags string `url:"remove_tags,omitempty"` // Description:"Remove tags",ExampleValue:"security,java8" + SendNotifications string `url:"sendNotifications,omitempty"` // Description:"",ExampleValue:"" + SetSeverity string `url:"set_severity,omitempty"` // Description:"To change the severity of the list of issues",ExampleValue:"BLOCKER" + SetType string `url:"set_type,omitempty"` // Description:"To change the type of the list of issues",ExampleValue:"BUG" +} + +// BulkChange Bulk change on issues.
Requires authentication. +func (s *IssuesService) BulkChange(opt *IssuesBulkChangeOption) (v *IssuesBulkChangeObject, resp *http.Response, err error) { + err = s.ValidateBulkChangeOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/bulk_change", opt) + if err != nil { + return + } + v = new(IssuesBulkChangeObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesChangelogOption struct { + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" +} + +// Changelog Display changelog of an issue.
Requires the 'Browse' permission on the project of the specified issue. +func (s *IssuesService) Changelog(opt *IssuesChangelogOption) (v *IssuesChangelogObject, resp *http.Response, err error) { + err = s.ValidateChangelogOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "issues/changelog", opt) + if err != nil { + return + } + v = new(IssuesChangelogObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesDeleteCommentOption struct { + Comment string `url:"comment,omitempty"` // Description:"Comment key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" +} + +// DeleteComment Delete a comment.
Requires authentication and the following permission: 'Browse' on the project of the specified issue. +func (s *IssuesService) DeleteComment(opt *IssuesDeleteCommentOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateDeleteCommentOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/delete_comment", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesDoTransitionOption struct { + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + Transition string `url:"transition,omitempty"` // Description:"Transition",ExampleValue:"" +} + +// DoTransition Do workflow transition on an issue. Requires authentication and Browse permission on project.
The transitions 'wontfix' and 'falsepositive' require the permission 'Administer Issues'. +func (s *IssuesService) DoTransition(opt *IssuesDoTransitionOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateDoTransitionOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/do_transition", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesEditCommentOption struct { + Comment string `url:"comment,omitempty"` // Description:"Comment key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + Text string `url:"text,omitempty"` // Description:"Comment text",ExampleValue:"Won't fix because it doesn't apply to the context" +} + +// EditComment Edit a comment.
Requires authentication and the following permission: 'Browse' on the project of the specified issue. +func (s *IssuesService) EditComment(opt *IssuesEditCommentOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateEditCommentOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/edit_comment", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesSearchOption struct { + AdditionalFields string `url:"additionalFields,omitempty"` // Description:"Comma-separated list of the optional fields to be returned in response. Action plans are dropped in 5.5, it is not returned in the response.",ExampleValue:"" + Asc string `url:"asc,omitempty"` // Description:"Ascending sort",ExampleValue:"" + Assigned string `url:"assigned,omitempty"` // Description:"To retrieve assigned or unassigned issues",ExampleValue:"" + Assignees string `url:"assignees,omitempty"` // Description:"Comma-separated list of assignee logins. The value '__me__' can be used as a placeholder for user who performs the request",ExampleValue:"admin,usera,__me__" + Authors string `url:"authors,omitempty"` // Description:"Comma-separated list of SCM accounts",ExampleValue:"torvalds@linux-foundation.org" + ComponentKeys string `url:"componentKeys,omitempty"` // Description:"Comma-separated list of component keys. Retrieve issues associated to a specific list of components (and all its descendants). A component can be a portfolio, project, module, directory or file.",ExampleValue:"my_project" + ComponentRootUuids string `url:"componentRootUuids,omitempty"` // Description:"If used, will have the same meaning as componentUuids AND onComponentOnly=false.",ExampleValue:"" + ComponentRoots string `url:"componentRoots,omitempty"` // Description:"If used, will have the same meaning as componentKeys AND onComponentOnly=false.",ExampleValue:"" + ComponentUuids string `url:"componentUuids,omitempty"` // Description:"To retrieve issues associated to a specific list of components their sub-components (comma-separated list of component IDs). This parameter is mostly used by the Issues page, please prefer usage of the componentKeys parameter. A component can be a project, module, directory or file.",ExampleValue:"584a89f2-8037-4f7b-b82c-8b45d2d63fb2" + Components string `url:"components,omitempty"` // Description:"If used, will have the same meaning as componentKeys AND onComponentOnly=true.",ExampleValue:"" + CreatedAfter string `url:"createdAfter,omitempty"` // Description:"To retrieve issues created after the given date (inclusive).
Either a date (server timezone) or datetime can be provided.
If this parameter is set, createdSince must not be set",ExampleValue:"2017-10-19 or 2017-10-19T13:00:00+0200" + CreatedAt string `url:"createdAt,omitempty"` // Description:"Datetime to retrieve issues created during a specific analysis",ExampleValue:"2017-10-19T13:00:00+0200" + CreatedBefore string `url:"createdBefore,omitempty"` // Description:"To retrieve issues created before the given date (inclusive).
Either a date (server timezone) or datetime can be provided.",ExampleValue:"2017-10-19 or 2017-10-19T13:00:00+0200" + CreatedInLast string `url:"createdInLast,omitempty"` // Description:"To retrieve issues created during a time span before the current time (exclusive). Accepted units are 'y' for year, 'm' for month, 'w' for week and 'd' for day. If this parameter is set, createdAfter must not be set",ExampleValue:"1m2w (1 month 2 weeks)" + Facets string `url:"facets,omitempty"` // Description:"Comma-separated list of the facets to be computed. No facet is computed by default." + Issues string `url:"issues,omitempty"` // Description:"Comma-separated list of issue keys",ExampleValue:"5bccd6e8-f525-43a2-8d76-fcb13dde79ef" + Languages string `url:"languages,omitempty"` // Description:"Comma-separated list of languages. Available since 4.4",ExampleValue:"java,js" + P string `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps string `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 500",ExampleValue:"20" + Resolutions string `url:"resolutions,omitempty"` // Description:"Comma-separated list of resolutions",ExampleValue:"FIXED,REMOVED" + Resolved string `url:"resolved,omitempty"` // Description:"To match resolved or unresolved issues",ExampleValue:"" + Rules string `url:"rules,omitempty"` // Description:"Comma-separated list of coding rule keys. Format is <repository>:<rule>",ExampleValue:"squid:AvoidCycles" + S string `url:"s,omitempty"` // Description:"Sort field",ExampleValue:"" + Severities string `url:"severities,omitempty"` // Description:"Comma-separated list of severities",ExampleValue:"BLOCKER,CRITICAL" + SinceLeakPeriod string `url:"sinceLeakPeriod,omitempty"` // Description:"To retrieve issues created since the leak period.
If this parameter is set to a truthy value, createdAfter must not be set and one component id or key must be provided.",ExampleValue:"" + Statuses string `url:"statuses,omitempty"` // Description:"Comma-separated list of statuses",ExampleValue:"OPEN,REOPENED" + Tags string `url:"tags,omitempty"` // Description:"Comma-separated list of tags.",ExampleValue:"security,convention" + Types string `url:"types,omitempty"` // Description:"Comma-separated list of types.",ExampleValue:"CODE_SMELL,BUG" +} + +// Search Search for issues.
At most one of the following parameters can be provided at the same time: componentKeys, componentUuids, components, componentRootUuids, componentRoots.
Requires the 'Browse' permission on the specified project(s). +func (s *IssuesService) Search(opt *IssuesSearchOption) (v *IssuesSearchObject, resp *http.Response, err error) { + err = s.ValidateSearchOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "issues/search", opt) + if err != nil { + return + } + v = new(IssuesSearchObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesSetSeverityOption struct { + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + Severity string `url:"severity,omitempty"` // Description:"New severity",ExampleValue:"" +} + +// SetSeverity Change severity.
Requires the following permissions: +func (s *IssuesService) SetSeverity(opt *IssuesSetSeverityOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateSetSeverityOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/set_severity", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesSetTagsOption struct { + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + Tags string `url:"tags,omitempty"` // Description:"Comma-separated list of tags. All tags are removed if parameter is empty or not set.",ExampleValue:"security,cwe,misra-c" +} + +// SetTags Set tags on an issue.
Requires authentication and Browse permission on project +func (s *IssuesService) SetTags(opt *IssuesSetTagsOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateSetTagsOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/set_tags", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesSetTypeOption struct { + Issue string `url:"issue,omitempty"` // Description:"Issue key",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + Type string `url:"type,omitempty"` // Description:"New type",ExampleValue:"" +} + +// SetType Change type of issue, for instance from 'code smell' to 'bug'.
Requires the following permissions: +func (s *IssuesService) SetType(opt *IssuesSetTypeOption) (v *IssuesAddCommentObject, resp *http.Response, err error) { + err = s.ValidateSetTypeOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "issues/set_type", opt) + if err != nil { + return + } + v = new(IssuesAddCommentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type IssuesTagsOption struct { + Ps int `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 100",ExampleValue:"20" + Q string `url:"q,omitempty"` // Description:"Limit search to tags that contain the supplied string.",ExampleValue:"misra" +} + +// Tags List tags matching a given query +func (s *IssuesService) Tags(opt *IssuesTagsOption) (v *IssuesTagsObject, resp *http.Response, err error) { + err = s.ValidateTagsOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "issues/tags", opt) + if err != nil { + return + } + v = new(IssuesTagsObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/languages_service.go b/vendor/github.com/kubesphere/sonargo/sonar/languages_service.go new file mode 100644 index 000000000..438a1a8ef --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/languages_service.go @@ -0,0 +1,40 @@ +// Get the list of programming languages supported in this instance. +package sonargo + +import "net/http" + +type LanguagesService struct { + client *Client +} + +type LanguagesListObject struct { + Languages []*Language `json:"languages,omitempty"` +} + +type Language struct { + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` +} + +type LanguagesListOption struct { + Ps int `url:"ps,omitempty"` // Description:"The size of the list to return, 0 for all languages",ExampleValue:"25" + Q string `url:"q,omitempty"` // Description:"A pattern to match language keys/names against",ExampleValue:"java" +} + +// List List supported programming languages +func (s *LanguagesService) List(opt *LanguagesListOption) (v *LanguagesListObject, resp *http.Response, err error) { + err = s.ValidateListOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "languages/list", opt) + if err != nil { + return + } + v = new(LanguagesListObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/measures_service.go b/vendor/github.com/kubesphere/sonargo/sonar/measures_service.go new file mode 100644 index 000000000..66d41dc5b --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/measures_service.go @@ -0,0 +1,138 @@ +// Get components or children with specified measures. +package sonargo + +import ( + "net/http" +) + +type MeasuresService struct { + client *Client +} + +type MeasuresComponentObject struct { + Component *Component `json:"component,omitempty"` + Metrics []*Metric `json:"metrics,omitempty"` + Periods []*Period `json:"periods,omitempty"` +} + +type SonarMeasure struct { + Metric string `json:"metric,omitempty"` + Periods []*Period `json:"periods,omitempty"` + Value string `json:"value,omitempty"` + Histories []*History `json:"history,omitempty"` + BestValue bool `json:"bestValue,omitempty"` +} + +type Period struct { + Date string `json:"date,omitempty"` + Index int64 `json:"index,omitempty"` + Mode string `json:"mode,omitempty"` + Parameter string `json:"parameter,omitempty"` + Value string `json:"value,omitempty"` + BestValue bool `json:"bestValue,omitempty"` +} + +type MeasuresComponentTreeObject struct { + BaseComponent Component `json:"baseComponent,omitempty"` + Components []*Component `json:"components,omitempty"` + Metrics []*Metric `json:"metrics,omitempty"` + Paging Paging `json:"paging,omitempty"` + Periods []*Period `json:"periods,omitempty"` +} + +type MeasuresSearchHistoryObject struct { + Measures []*SonarMeasure `json:"measures,omitempty"` + Paging Paging `json:"paging,omitempty"` +} + +type History struct { + Date string `json:"date,omitempty"` + Value string `json:"value,omitempty"` +} + +type MeasuresComponentOption struct { + AdditionalFields string `url:"additionalFields,omitempty"` // Description:"Comma-separated list of additional fields that can be returned in the response.",ExampleValue:"periods,metrics" + Component string `url:"component,omitempty"` // Description:"Component key",ExampleValue:"my_project" + ComponentId string `url:"componentId,omitempty"` // Description:"Component id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + MetricKeys string `url:"metricKeys,omitempty"` // Description:"Comma-separated list of metric keys",ExampleValue:"ncloc,complexity,violations" +} + +// Component Return component with specified measures. The componentId or the component parameter must be provided.
Requires the following permission: 'Browse' on the project of specified component. +func (s *MeasuresService) Component(opt *MeasuresComponentOption) (v *MeasuresComponentObject, resp *http.Response, err error) { + err = s.ValidateComponentOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "measures/component", opt) + if err != nil { + return + } + v = new(MeasuresComponentObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type MeasuresComponentTreeOption struct { + AdditionalFields string `url:"additionalFields,omitempty"` // Description:"Comma-separated list of additional fields that can be returned in the response.",ExampleValue:"periods,metrics" + Asc string `url:"asc,omitempty"` // Description:"Ascending sort",ExampleValue:"" + BaseComponentId string `url:"baseComponentId,omitempty"` // Description:"Base component id. The search is based on this component.",ExampleValue:"AU-TpxcA-iU5OvuD2FLz" + Component string `url:"component,omitempty"` // Description:"Component key. The search is based on this component.",ExampleValue:"my_project" + MetricKeys string `url:"metricKeys,omitempty"` // Description:"Comma-separated list of metric keys. Types DISTRIB, DATA are not allowed.",ExampleValue:"ncloc,complexity,violations" + MetricPeriodSort string `url:"metricPeriodSort,omitempty"` // Description:"Sort measures by leak period or not ?. The 's' parameter must contain the 'metricPeriod' value.",ExampleValue:"" + MetricSort string `url:"metricSort,omitempty"` // Description:"Metric key to sort by. The 's' parameter must contain the 'metric' or 'metricPeriod' value. It must be part of the 'metricKeys' parameter",ExampleValue:"ncloc" + MetricSortFilter string `url:"metricSortFilter,omitempty"` // Description:"Filter components. Sort must be on a metric. Possible values are: ",ExampleValue:"" + P string `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps string `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 500",ExampleValue:"20" + Q string `url:"q,omitempty"` // Description:"Limit search to: ",ExampleValue:"FILE_NAM" + Qualifiers string `url:"qualifiers,omitempty"` // Description:"Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. Possible values are:",ExampleValue:"" + S string `url:"s,omitempty"` // Description:"Comma-separated list of sort fields",ExampleValue:"name,path" + Strategy string `url:"strategy,omitempty"` // Description:"Strategy to search for base component descendants:",ExampleValue:"" +} + +// ComponentTree Navigate through components based on the chosen strategy with specified measures. The baseComponentId or the component parameter must be provided.
Requires the following permission: 'Browse' on the specified project.
When limiting search with the q parameter, directories are not returned. +func (s *MeasuresService) ComponentTree(opt *MeasuresComponentTreeOption) (v *MeasuresComponentTreeObject, resp *http.Response, err error) { + err = s.ValidateComponentTreeOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "measures/component_tree", opt) + if err != nil { + return + } + v = new(MeasuresComponentTreeObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type MeasuresSearchHistoryOption struct { + Component string `url:"component,omitempty"` // Description:"Component key",ExampleValue:"my_project" + From string `url:"from,omitempty"` // Description:"Filter measures created after the given date (inclusive).
Either a date (server timezone) or datetime can be provided",ExampleValue:"2017-10-19 or 2017-10-19T13:00:00+0200" + Metrics string `url:"metrics,omitempty"` // Description:"Comma-separated list of metric keys",ExampleValue:"ncloc,coverage,new_violations" + P string `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps string `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 1000",ExampleValue:"20" + To string `url:"to,omitempty"` // Description:"Filter measures created before the given date (inclusive).
Either a date (server timezone) or datetime can be provided",ExampleValue:"2017-10-19 or 2017-10-19T13:00:00+0200" +} + +// SearchHistory Search measures history of a component.
Measures are ordered chronologically.
Pagination applies to the number of measures for each metric.
Requires the following permission: 'Browse' on the specified component +func (s *MeasuresService) SearchHistory(opt *MeasuresSearchHistoryOption) (v *MeasuresSearchHistoryObject, resp *http.Response, err error) { + err = s.ValidateSearchHistoryOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "measures/search_history", opt) + if err != nil { + return + } + v = new(MeasuresSearchHistoryObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/metrics.go b/vendor/github.com/kubesphere/sonargo/sonar/metrics.go new file mode 100644 index 000000000..7e41d4596 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/metrics.go @@ -0,0 +1,219 @@ +package sonargo + +var metrics = []string{ + "complexity", + "cognitive_complexity", + "duplicated_blocks", + "duplicated_files", + "duplicated_lines", + "duplicated_lines_density", + "new_violations", + "new_xxx_violations", + "violations", + "xxx_issues", + "false_positive_issues", + "open_issues", + "confirmed_issues", + "reopened_issues", + "code_smells", + "new_code_smells", + "sqale_rating", + "sqale_index", + "new_technical_debt", + "sqale_debt_ratio", + "new_sqale_debt_ratio", + "alert_status", + "quality_gate_details", + "bugs", + "new_bugs", + "reliability_rating", + "reliability_remediation_effort", + "new_reliability_remediation_effort", + "vulnerabilities", + "new_vulnerabilities", + "security_rating", + "security_remediation_effort", + "new_security_remediation_effort", + "classes", + "comment_lines", + "comment_lines_density", + "directories", + "files", + "lines", + "ncloc", + "ncloc_language_distribution", + "functions", + "projects", + "statements", + "branch_coverage", + "new_branch_coverage", + "branch_coverage_hits_data", + "conditions_by_line", + "covered_conditions_by_line", + "coverage", + "new_coverage", + "new_line_coverage", + "coverage_line_hits_data", + "lines_to_cover", + "new_lines_to_cover", + "skipped_tests", + "uncovered_conditions", + "new_uncovered_conditions", + "uncovered_lines", + "new_uncovered_lines", + "tests", + "test_execution_time", + "test_errors", + "test_failures", + "test_success_density", +} + +var metricsDescription = []string{ + "It is the Cyclomatic Complexity calculated based on the number of paths through the code. Whenever the control flow of a function splits, the complexity counter gets incremented by one. Each function has a minimum complexity of 1. This calculation varies slightly by language because keywords and functionalities do.", + "How hard it is to understand the code's control flow. See the Cognitive Complexity White Paper for a complete description of the mathematical model applied to compute this measure.", + "Number of duplicated blocks of lines.", + "Number of files involved in duplications.", + "Number of lines involved in duplications.", + "= duplicated_lines / lines * 100", + "Number of issues raised for the first time in the New Code period.", + "Number of issues of the specified severity raised for the first time in the New Code period, where xxx is one of: blocker, critical, major, minor, info.", + "Total count of issues in all states.", + "Total count of issues of the specified severity, where xxx is one of: blocker, critical, major, minor, info.", + "Total count of issues marked False Positive", + "Total count of issues in the Open state.", + "Total count of issues in the Confirmed state.", + "Total count of issues in the Reopened state", + "Total count of Code Smell issues.", + "Total count of Code Smell issues raised for the first time in the New Code period.", + "(Formerly the SQALE rating.)\nRating given to your project related to the value of your Technical Debt Ratio. The default Maintainability Rating grid is:", + "Effort to fix all Code Smells. The measure is stored in minutes in the database. An 8-hour day is assumed when values are shown in days.", + "Effort to fix all Code Smells raised for the first time in the New Code period.", + "Ratio between the cost to develop the software and the cost to fix it. The Technical Debt Ratio formula is:\n\nRemediation cost / Development cost\n\nWhich can be restated as:\n\nRemediation cost / (Cost to develop 1 line of code * Number of lines of code)\n\nThe value of the cost to develop a line of code is 0.06 days.", + "Ratio between the cost to develop the code changed in the New Code period and the cost of the issues linked to it.", + "State of the Quality Gate associated to your Project. Possible values are : ERROR, WARN, OK", + "For all the conditions of your Quality Gate, you know which condition is failing and which is not.", + "Number of bug issues.", + "Number of new bug issues.", + "A = 0 Bugs\n\nB = at least 1 Minor Bug\n\nC = at least 1 Major Bug\n\nD = at least 1 Critical Bug\n\nE = at least 1 Blocker Bug ", + "Effort to fix all bug issues. The measure is stored in minutes in the DB. An 8-hour day is assumed when values are shown in days.", + "Same as Reliability remediation effort but on the code changed in the New Code period.", + "Number of vulnerability issues.", + "Number of new vulnerability issues.", + "A = 0 Vulnerabilities\n\nB = at least 1 Minor Vulnerability\n\nC = at least 1 Major Vulnerability\n\nD = at least 1 Critical Vulnerability\n\nE = at least 1 Blocker Vulnerability ", + "Effort to fix all vulnerability issues. The measure is stored in minutes in the DB. An 8-hour day is assumed when values are shown in days.", + "Same as Security remediation effort but on the code changed in the New Code period.", + "Number of classes (including nested classes, interfaces, enums and annotations).", + "Number of lines containing either comment or commented-out code.", + "Density of comment lines = Comment lines / (Lines of code + Comment lines) * 100", + "Number of directories.", + "Number of files.", + "Number of physical lines (number of carriage returns).", + "Number of physical lines that contain at least one character which is neither a whitespace nor a tabulation nor part of a comment.", + "Non Commenting Lines of Code Distributed By Language", + "Number of functions. Depending on the language, a function is either a function or a method or a paragraph.", + "Number of projects in a Portfolio.", + "Number of statements.", + "On each line of code containing some boolean expressions, the condition coverage simply answers the following question: 'Has each boolean expression been evaluated both to true and false?'. This is the density of possible conditions in flow control structures that have been followed during unit tests execution.", + "Identical to Condition coverage but restricted to new / updated source code.", + "List of covered conditions.", + "Number of conditions by line.", + "Number of covered conditions by line.", + "It is a mix of Line coverage and Condition coverage. Its goal is to provide an even more accurate answer to the following question: How much of the source code has been covered by the unit tests?", + "Identical to Coverage but restricted to new / updated source code.", + "Identical to Line coverage but restricted to new / updated source code.", + "List of covered lines.", + "Number of lines of code which could be covered by unit tests (for example, blank lines or full comments lines are not considered as lines to cover).", + "Identical to Lines to cover but restricted to new / updated source code.", + "Number of skipped unit tests.", + "Number of conditions which are not covered by unit tests.", + "Identical to Uncovered conditions but restricted to new / updated source code.", + "Number of lines of code which are not covered by unit tests.", + "Identical to Uncovered lines but restricted to new / updated source code.", + "Number of unit tests.", + "Time required to execute all the unit tests.", + "Number of unit tests that have failed.", + "Number of unit tests that have failed with an unexpected exception.", + "Test success density = (Unit tests - (Unit test errors + Unit test failures)) / Unit tests * 100", +} + +var metricNames = []string{ + "Complexity", + "Cognitive Complexity", + "Duplicated blocks", + "Duplicated files", + "Duplicated lines", + "Duplicated lines (%)", + "New issues", + "New xxx issues", + "Issues", + "xxx issues", + "False positive issues", + "Open issues", + "Confirmed issues", + "Reopened issues", + "Code Smells", + "New Code Smells", + "Maintainability Rating", + "Technical Debt", + "Technical Debt on New Code", + "Technical Debt Ratio", + "Technical Debt Ratio on New Code", + "Quality Gate Status", + "Quality Gate Details", + "Bugs", + "New Bugs", + "Reliability Rating", + "Reliability remediation effort", + "Reliability remediation effort on new code", + "Vulnerabilities", + "New Vulnerabilities", + "Security Rating", + "Security remediation effort", + "Security remedation effort on new code", + "Classes", + "Comment lines", + "Comments (%)", + "Directories", + "Files", + "Lines", + "Lines of code", + "Lines of code per language", + "Functions", + "Projects", + "Statements", + "Condition coverage", + "Condition coverage on new code", + "Condition coverage hits", + "Conditions by line", + "Covered conditions by line", + "Coverage", + "Coverage on new code", + "Line coverage on new code", + "Line coverage hits", + "Lines to cover", + "Lines to cover on new code", + "Skipped unit tests", + "Uncovered conditions", + "Uncovered conditions on new code", + "Uncovered lines", + "Uncovered lines on new code", + "Unit tests", + "Unit tests duration", + "Unit test errors", + "Unit test failures", + "Unit test success density (%)", +} + +const ( + MetricTypeINT = "INT" + MetricTypeFLOAT = "FLOAT" + MetricTypePERCENT = "PERCENT" + MetricTypeBOOL = "BOOL" + MetricTypeSTRING = "STRING" + MetricTypeMILLISEC = "MILLISEC" + MetricTypeDATA = "DATA" + MetricTypeLEVEL = "LEVEL" + MetricTypeDISTRIB = "DISTRIB" + MetricTypeRATING = "RATING" + MetricTypeWORK_DUR = "WORK_DUR" +) diff --git a/vendor/github.com/kubesphere/sonargo/sonar/metrics_service.go b/vendor/github.com/kubesphere/sonargo/sonar/metrics_service.go new file mode 100644 index 000000000..f59cdadd7 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/metrics_service.go @@ -0,0 +1,164 @@ +// Get information on automatic metrics, and manage custom metrics. See also api/custom_measures. +package sonargo + +import "net/http" + +type MetricsService struct { + client *Client +} + +type Metric struct { + ID string `url:"id,omitempty" json:"id,omitempty"` + Description string `url:"description,omitempty"` // Description:"Description",ExampleValue:"Size of the team" + Direction int `json:"direction"` + Domain string `url:"domain,omitempty"` // Description:"Domain",ExampleValue:"Tests" + Key string `url:"key,omitempty"` // Description:"Key",ExampleValue:"team_size" + Name string `url:"name,omitempty"` // Description:"Name",ExampleValue:"Team Size" + Type string `url:"type,omitempty"` // Description:"Metric type key",ExampleValue:"INT" + Qualitative bool `json:"qualitative,omitempty"` + Hidden bool `json:"hidden,omitempty"` + Custom bool `json:"custom,omitempty"` + DecimalScale int `json:"decimalScale,omitempty"` +} + +func (s *MetricsService) GetDefaultMetrics() []Metric { + result := []Metric{} + for index, key := range metrics { + result = append(result, Metric{ + Key: key, + Name: metricNames[index], + Description: metricsDescription[index], + }) + } + return result +} + +type MetricsDomainsObject struct { + Domains []string `json:"domains,omitempty"` +} + +type MetricsSearchObject struct { + Metrics []*Metric `json:"metrics,omitempty"` + P int64 `json:"p,omitempty"` + Ps int64 `json:"ps,omitempty"` + Total int64 `json:"total,omitempty"` +} + +type MetricsTypesObject struct { + Types []string `json:"types,omitempty"` +} + +type MetricsCreateOption Metric + +// Create Create custom metric.
Requires 'Administer System' permission. +func (s *MetricsService) Create(opt *MetricsCreateOption) (v *Metric, resp *http.Response, err error) { + err = s.ValidateCreateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "metrics/create", opt) + if err != nil { + return + } + v = new(Metric) + resp, err = s.client.Do(req, v) + if err != nil { + return + } + return +} + +type MetricsDeleteOption struct { + Ids string `url:"ids,omitempty"` // Description:"Metrics ids to delete.",ExampleValue:"5, 23, 42" + Keys string `url:"keys,omitempty"` // Description:"Metrics keys to delete",ExampleValue:"team_size, business_value" +} + +// Delete Delete metrics and associated measures. Delete only custom metrics.
Ids or keys must be provided.
Requires 'Administer System' permission. +func (s *MetricsService) Delete(opt *MetricsDeleteOption) (resp *http.Response, err error) { + err = s.ValidateDeleteOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "metrics/delete", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +// Domains List all custom metric domains. +func (s *MetricsService) Domains() (v *MetricsDomainsObject, resp *http.Response, err error) { + req, err := s.client.NewRequest("GET", "metrics/domains", nil) + if err != nil { + return + } + v = new(MetricsDomainsObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type MetricsSearchOption struct { + F string `url:"f,omitempty"` // Description:"Comma-separated list of the fields to be returned in response. All the fields are returned by default.",ExampleValue:"" + IsCustom string `url:"isCustom,omitempty"` // Description:"Choose custom metrics following 3 cases:",ExampleValue:"true" + P int `url:"p,omitempty"` // Description:"1-based page number",ExampleValue:"42" + Ps int `url:"ps,omitempty"` // Description:"Page size. Must be greater than 0 and less or equal than 500",ExampleValue:"20" +} + +// Search Search for metrics +func (s *MetricsService) Search(opt *MetricsSearchOption) (v *MetricsSearchObject, resp *http.Response, err error) { + err = s.ValidateSearchOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "metrics/search", opt) + if err != nil { + return + } + v = new(MetricsSearchObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +// Types List all available metric types. +func (s *MetricsService) Types() (v *MetricsTypesObject, resp *http.Response, err error) { + req, err := s.client.NewRequest("GET", "metrics/types", nil) + if err != nil { + return + } + v = new(MetricsTypesObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type MetricsUpdateOption Metric + +// Update Update a custom metric.
Requires 'Administer System' permission. +func (s *MetricsService) Update(opt *MetricsUpdateOption) (v *Metric, resp *http.Response, err error) { + err = s.ValidateUpdateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "metrics/update", opt) + if err != nil { + return + } + v = new(Metric) + resp, err = s.client.Do(req, v) + if err != nil { + return + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/notifications_service.go b/vendor/github.com/kubesphere/sonargo/sonar/notifications_service.go new file mode 100644 index 000000000..5db7b1042 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/notifications_service.go @@ -0,0 +1,93 @@ +// Manage notifications of the authenticated user +package sonargo + +import "net/http" + +type NotificationsService struct { + client *Client +} + +type NotificationsListObject struct { + Channels []string `json:"channels,omitempty"` + GlobalTypes []string `json:"globalTypes,omitempty"` + Notifications []*Notification `json:"notifications,omitempty"` + PerProjectTypes []string `json:"perProjectTypes,omitempty"` +} + +type Notification struct { + Channel string `json:"channel,omitempty"` + Organization string `json:"organization,omitempty"` + Project string `json:"project,omitempty"` + ProjectName string `json:"projectName,omitempty"` + Type string `json:"type,omitempty"` +} + +type NotificationsAddOption struct { + Channel string `url:"channel,omitempty"` // Description:"Channel through which the notification is sent. For example, notifications can be sent by email.",ExampleValue:"" + Login string `url:"login,omitempty"` // Description:"User login",ExampleValue:"" + Project string `url:"project,omitempty"` // Description:"Project key",ExampleValue:"my_project" + Type string `url:"type,omitempty"` // Description:"Notification type. Possible values are for:",ExampleValue:"SQ-MyNewIssues" +} + +// Add Add a notification for the authenticated user.
Requires one of the following permissions: +func (s *NotificationsService) Add(opt *NotificationsAddOption) (resp *http.Response, err error) { + err = s.ValidateAddOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "notifications/add", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type NotificationsListOption struct { + Login string `url:"login,omitempty"` // Description:"User login",ExampleValue:"" +} + +// List List notifications of the authenticated user.
Requires one of the following permissions: +func (s *NotificationsService) List(opt *NotificationsListOption) (v *NotificationsListObject, resp *http.Response, err error) { + err = s.ValidateListOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "notifications/list", opt) + if err != nil { + return + } + v = new(NotificationsListObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type NotificationsRemoveOption struct { + Channel string `url:"channel,omitempty"` // Description:"Channel through which the notification is sent. For example, notifications can be sent by email.",ExampleValue:"" + Login string `url:"login,omitempty"` // Description:"User login",ExampleValue:"" + Project string `url:"project,omitempty"` // Description:"Project key",ExampleValue:"my_project" + Type string `url:"type,omitempty"` // Description:"Notification type. Possible values are for:",ExampleValue:"SQ-MyNewIssues" +} + +// Remove Remove a notification for the authenticated user.
Requires one of the following permissions: +func (s *NotificationsService) Remove(opt *NotificationsRemoveOption) (resp *http.Response, err error) { + err = s.ValidateRemoveOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "notifications/remove", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/permissions_service.go b/vendor/github.com/kubesphere/sonargo/sonar/permissions_service.go new file mode 100644 index 000000000..37f514719 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/permissions_service.go @@ -0,0 +1,425 @@ +// Manage permission templates, and the granting and revoking of permissions at the global and project levels. +package sonargo + +import "net/http" + +type PermissionsService struct { + client *Client +} + +type PermissionsCreateTemplateObject struct { + PermissionTemplate *PermissionTemplate `json:"permissionTemplate,omitempty"` +} + +type PermissionTemplate struct { + ID string `json:"id,omitempty"` + CreatedAt string `json:"createdAt,omitempty"` + UpdatedAt string `json:"updatedAt,omitempty"` + Description string `json:"description,omitempty"` + Name string `json:"name,omitempty"` + ProjectKeyPattern string `json:"projectKeyPattern,omitempty"` + Permissions []*Permission `json:"permissions,omitempty"` +} + +type Permission struct { + Description string `json:"description,omitempty"` + GroupsCount int64 `json:"groupsCount,omitempty"` + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` + UsersCount int64 `json:"usersCount,omitempty"` + WithProjectCreator bool `json:"withProjectCreator,omitempty"` +} + +type PermissionsSearchTemplatesObject struct { + DefaultTemplates []*DefaultTemplate `json:"defaultTemplates,omitempty"` + PermissionTemplates []*PermissionTemplate `json:"permissionTemplates,omitempty"` + Permissions []*Permission `json:"permissions,omitempty"` +} + +type DefaultTemplate struct { + Qualifier string `json:"qualifier,omitempty"` + TemplateID string `json:"templateId,omitempty"` +} + +type PermissionsAddGroupOption struct { + GroupId int `url:"groupId,omitempty"` // Description:"Group id",ExampleValue:"42" + GroupName string `url:"groupName,omitempty"` // Description:"Group name or 'anyone' (case insensitive)",ExampleValue:"sonar-administrators" + Permission string `url:"permission,omitempty"` // Description:"Permission",ExampleValue:"" + ProjectId string `url:"projectId,omitempty"` // Description:"Project id",ExampleValue:"ce4c03d6-430f-40a9-b777-ad877c00aa4d" + ProjectKey string `url:"projectKey,omitempty"` // Description:"Project key",ExampleValue:"my_project" +} + +// AddGroup Add permission to a group.
This service defaults to global permissions, but can be limited to project permissions by providing project id or project key.
The group name or group id must be provided.
Requires one of the following permissions: +func (s *PermissionsService) AddGroup(opt *PermissionsAddGroupOption) (resp *http.Response, err error) { + err = s.ValidateAddGroupOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/add_group", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsAddGroupToTemplateOption struct { + GroupId int `url:"groupId,omitempty"` // Description:"Group id",ExampleValue:"42" + GroupName string `url:"groupName,omitempty"` // Description:"Group name or 'anyone' (case insensitive)",ExampleValue:"sonar-administrators" + Permission string `url:"permission,omitempty"` // Description:"Permission",ExampleValue:"" + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// AddGroupToTemplate Add a group to a permission template.
The group id or group name must be provided.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) AddGroupToTemplate(opt *PermissionsAddGroupToTemplateOption) (resp *http.Response, err error) { + err = s.ValidateAddGroupToTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/add_group_to_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsAddProjectCreatorToTemplateOption struct { + Permission string `url:"permission,omitempty"` // Description:"Permission",ExampleValue:"" + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// AddProjectCreatorToTemplate Add a project creator to a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) AddProjectCreatorToTemplate(opt *PermissionsAddProjectCreatorToTemplateOption) (resp *http.Response, err error) { + err = s.ValidateAddProjectCreatorToTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/add_project_creator_to_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsAddUserOption struct { + Login string `url:"login,omitempty"` // Description:"User login",ExampleValue:"g.hopper" + Permission string `url:"permission,omitempty"` // Description:"Permission",ExampleValue:"" + ProjectId string `url:"projectId,omitempty"` // Description:"Project id",ExampleValue:"ce4c03d6-430f-40a9-b777-ad877c00aa4d" + ProjectKey string `url:"projectKey,omitempty"` // Description:"Project key",ExampleValue:"my_project" +} + +// AddUser Add permission to a user.
This service defaults to global permissions, but can be limited to project permissions by providing project id or project key.
Requires one of the following permissions: +func (s *PermissionsService) AddUser(opt *PermissionsAddUserOption) (resp *http.Response, err error) { + err = s.ValidateAddUserOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/add_user", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsAddUserToTemplateOption struct { + Login string `url:"login,omitempty"` // Description:"User login",ExampleValue:"g.hopper" + Permission string `url:"permission,omitempty"` // Description:"Permission",ExampleValue:"" + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// AddUserToTemplate Add a user to a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) AddUserToTemplate(opt *PermissionsAddUserToTemplateOption) (resp *http.Response, err error) { + err = s.ValidateAddUserToTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/add_user_to_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsApplyTemplateOption struct { + ProjectId string `url:"projectId,omitempty"` // Description:"Project id",ExampleValue:"ce4c03d6-430f-40a9-b777-ad877c00aa4d" + ProjectKey string `url:"projectKey,omitempty"` // Description:"Project key",ExampleValue:"my_project" + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// ApplyTemplate Apply a permission template to one project.
The project id or project key must be provided.
The template id or name must be provided.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) ApplyTemplate(opt *PermissionsApplyTemplateOption) (resp *http.Response, err error) { + err = s.ValidateApplyTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/apply_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsBulkApplyTemplateOption struct { + AnalyzedBefore string `url:"analyzedBefore,omitempty"` // Description:"Filter the projects for which last analysis is older than the given date (exclusive).
Either a date (server timezone) or datetime can be provided.",ExampleValue:"2017-10-19 or 2017-10-19T13:00:00+0200" + OnProvisionedOnly string `url:"onProvisionedOnly,omitempty"` // Description:"Filter the projects that are provisioned",ExampleValue:"" + Projects string `url:"projects,omitempty"` // Description:"Comma-separated list of project keys",ExampleValue:"my_project,another_project" + Q string `url:"q,omitempty"` // Description:"Limit search to: ",ExampleValue:"apac" + Qualifiers string `url:"qualifiers,omitempty"` // Description:"Comma-separated list of component qualifiers. Filter the results with the specified qualifiers. Possible values are:",ExampleValue:"" + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// BulkApplyTemplate Apply a permission template to several projects.
The template id or name must be provided.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) BulkApplyTemplate(opt *PermissionsBulkApplyTemplateOption) (resp *http.Response, err error) { + err = s.ValidateBulkApplyTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/bulk_apply_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsCreateTemplateOption struct { + Description string `url:"description,omitempty"` // Description:"Description",ExampleValue:"Permissions for all projects related to the financial service" + Name string `url:"name,omitempty"` // Description:"Name",ExampleValue:"Financial Service Permissions" + ProjectKeyPattern string `url:"projectKeyPattern,omitempty"` // Description:"Project key pattern. Must be a valid Java regular expression",ExampleValue:".*\.finance\..*" +} + +// CreateTemplate Create a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) CreateTemplate(opt *PermissionsCreateTemplateOption) (v *PermissionsCreateTemplateObject, resp *http.Response, err error) { + err = s.ValidateCreateTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/create_template", opt) + if err != nil { + return + } + v = new(PermissionsCreateTemplateObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type PermissionsDeleteTemplateOption struct { + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// DeleteTemplate Delete a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) DeleteTemplate(opt *PermissionsDeleteTemplateOption) (resp *http.Response, err error) { + err = s.ValidateDeleteTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/delete_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsRemoveGroupOption PermissionsAddGroupOption + +// RemoveGroup Remove a permission from a group.
This service defaults to global permissions, but can be limited to project permissions by providing project id or project key.
The group id or group name must be provided, not both.
Requires one of the following permissions: +func (s *PermissionsService) RemoveGroup(opt *PermissionsRemoveGroupOption) (resp *http.Response, err error) { + err = s.ValidateRemoveGroupOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/remove_group", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsRemoveGroupFromTemplateOption PermissionsAddGroupToTemplateOption + +// RemoveGroupFromTemplate Remove a group from a permission template.
The group id or group name must be provided.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) RemoveGroupFromTemplate(opt *PermissionsRemoveGroupFromTemplateOption) (resp *http.Response, err error) { + err = s.ValidateRemoveGroupFromTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/remove_group_from_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsRemoveProjectCreatorFromTemplateOption PermissionsAddProjectCreatorToTemplateOption + +// RemoveProjectCreatorFromTemplate Remove a project creator from a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) RemoveProjectCreatorFromTemplate(opt *PermissionsRemoveProjectCreatorFromTemplateOption) (resp *http.Response, err error) { + err = s.ValidateRemoveProjectCreatorFromTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/remove_project_creator_from_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsRemoveUserOption PermissionsAddUserOption + +// RemoveUser Remove permission from a user.
This service defaults to global permissions, but can be limited to project permissions by providing project id or project key.
Requires one of the following permissions: +func (s *PermissionsService) RemoveUser(opt *PermissionsRemoveUserOption) (resp *http.Response, err error) { + err = s.ValidateRemoveUserOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/remove_user", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsRemoveUserFromTemplateOption PermissionsAddUserToTemplateOption + +// RemoveUserFromTemplate Remove a user from a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) RemoveUserFromTemplate(opt *PermissionsRemoveUserFromTemplateOption) (resp *http.Response, err error) { + err = s.ValidateRemoveUserFromTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/remove_user_from_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsSearchTemplatesOption struct { + Q string `url:"q,omitempty"` // Description:"Limit search to permission template names that contain the supplied string.",ExampleValue:"defau" +} + +// SearchTemplates List permission templates.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) SearchTemplates(opt *PermissionsSearchTemplatesOption) (v *PermissionsSearchTemplatesObject, resp *http.Response, err error) { + err = s.ValidateSearchTemplatesOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("GET", "permissions/search_templates", opt) + if err != nil { + return + } + v = new(PermissionsSearchTemplatesObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +type PermissionsSetDefaultTemplateOption struct { + Qualifier string `url:"qualifier,omitempty"` // Description:"Project qualifier. Filter the results with the specified qualifier. Possible values are:",ExampleValue:"" + TemplateId string `url:"templateId,omitempty"` // Description:"Template id",ExampleValue:"AU-Tpxb--iU5OvuD2FLy" + TemplateName string `url:"templateName,omitempty"` // Description:"Template name",ExampleValue:"Default Permission Template for Projects" +} + +// SetDefaultTemplate Set a permission template as default.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) SetDefaultTemplate(opt *PermissionsSetDefaultTemplateOption) (resp *http.Response, err error) { + err = s.ValidateSetDefaultTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/set_default_template", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PermissionsUpdateTemplateOption struct { + Description string `url:"description,omitempty"` // Description:"Description",ExampleValue:"Permissions for all projects related to the financial service" + Id string `url:"id,omitempty"` // Description:"Id",ExampleValue:"af8cb8cc-1e78-4c4e-8c00-ee8e814009a5" + Name string `url:"name,omitempty"` // Description:"Name",ExampleValue:"Financial Service Permissions" + ProjectKeyPattern string `url:"projectKeyPattern,omitempty"` // Description:"Project key pattern. Must be a valid Java regular expression",ExampleValue:".*\.finance\..*" +} + +// UpdateTemplate Update a permission template.
Requires the following permission: 'Administer System'. +func (s *PermissionsService) UpdateTemplate(opt *PermissionsUpdateTemplateOption) (v *PermissionsCreateTemplateObject, resp *http.Response, err error) { + err = s.ValidateUpdateTemplateOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "permissions/update_template", opt) + if err != nil { + return + } + v = new(PermissionsCreateTemplateObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} diff --git a/vendor/github.com/kubesphere/sonargo/sonar/plugins_service.go b/vendor/github.com/kubesphere/sonargo/sonar/plugins_service.go new file mode 100644 index 000000000..392d3a566 --- /dev/null +++ b/vendor/github.com/kubesphere/sonargo/sonar/plugins_service.go @@ -0,0 +1,207 @@ +// Manage the plugins on the server, including installing, uninstalling, and upgrading. +package sonargo + +import "net/http" + +type PluginsService struct { + client *Client +} + +type Plugin struct { + Category string `json:"category,omitempty"` + Description string `json:"description,omitempty"` + EditionBundled bool `json:"editionBundled,omitempty"` + Filename string `json:"filename,omitempty"` + Hash string `json:"hash,omitempty"` + HomepageURL string `json:"homepageUrl,omitempty"` + ImplementationBuild string `json:"implementationBuild,omitempty"` + IssueTrackerURL string `json:"issueTrackerUrl,omitempty"` + Key string `json:"key,omitempty"` + License string `json:"license,omitempty"` + Name string `json:"name,omitempty"` + OrganizationName string `json:"organizationName,omitempty"` + OrganizationURL string `json:"organizationUrl,omitempty"` + Release *Release `json:"release,omitempty"` + SonarLintSupported bool `json:"sonarLintSupported,omitempty"` + TermsAndConditionsURL string `json:"termsAndConditionsUrl,omitempty"` + Update *Update `json:"update,omitempty"` + Updates []*Update `json:"updates,omitempty"` + UpdatedAt int64 `json:"updatedAt,omitempty"` + Version string `json:"version,omitempty"` +} + +type PluginsAvailableObject struct { + Plugins []*Plugin `json:"plugins,omitempty"` + UpdateCenterRefresh string `json:"updateCenterRefresh,omitempty"` +} + +type Release struct { + Date string `json:"date,omitempty"` + Version string `json:"version,omitempty"` + Description string `json:"description,omitempty"` + ChangeLogURL string `json:"changeLogUrl,omitempty"` +} + +type Require struct { + Description string `json:"description,omitempty"` + Key string `json:"key,omitempty"` + Name string `json:"name,omitempty"` +} + +type Update struct { + Release *Release `json:"release,omitempty"` + Requires []*Require `json:"requires,omitempty"` + Status string `json:"status,omitempty"` +} + +type PluginsInstalledObject struct { + Plugins []*Plugin `json:"plugins,omitempty"` +} + +type PluginsPendingObject struct { + Installing []*Plugin `json:"installing,omitempty"` + Removing []*Plugin `json:"removing,omitempty"` + Updating []*Plugin `json:"updating,omitempty"` +} + +type PluginsUpdatesObject PluginsAvailableObject + +// Available Get the list of all the plugins available for installation on the SonarQube instance, sorted by plugin name.
Plugin information is retrieved from Update Center. Date and time at which Update Center was last refreshed is provided in the response.
Update status values are: Require 'Administer System' permission. +func (s *PluginsService) Available() (v *PluginsAvailableObject, resp *http.Response, err error) { + req, err := s.client.NewRequest("GET", "plugins/available", nil) + if err != nil { + return + } + v = new(PluginsAvailableObject) + resp, err = s.client.Do(req, v) + if err != nil { + return nil, resp, err + } + return +} + +// CancelAll Cancels any operation pending on any plugin (install, update or uninstall)
Requires user to be authenticated with Administer System permissions +func (s *PluginsService) CancelAll() (resp *http.Response, err error) { + req, err := s.client.NewRequest("POST", "plugins/cancel_all", nil) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PluginsInstallOption struct { + Key string `url:"key,omitempty"` // Description:"The key identifying the plugin to install",ExampleValue:"" +} + +// Install Installs the latest version of a plugin specified by its key.
Plugin information is retrieved from Update Center.
Requires user to be authenticated with Administer System permissions +func (s *PluginsService) Install(opt *PluginsInstallOption) (resp *http.Response, err error) { + err = s.ValidateInstallOpt(opt) + if err != nil { + return + } + req, err := s.client.NewRequest("POST", "plugins/install", opt) + if err != nil { + return + } + resp, err = s.client.Do(req, nil) + if err != nil { + return + } + return +} + +type PluginsInstalledOption struct { + F string `url:"f,omitempty"` // Description:"Comma-separated list of the additional fields to be returned in response. No additional field is returned by default. Possible values are: