├── .dockerignore
├── .github
└── workflows
│ └── ci.yaml
├── .gitignore
├── .goreleaser.dev.yml
├── .goreleaser.yml
├── Dockerfile
├── Iris.jpg
├── LICENSE
├── Makefile
├── README.md
├── VERSION
├── cmd
├── cmdutils.go
├── root.go
└── run.go
├── go.mod
├── go.sum
├── hack
├── .codefresh
│ └── push-helm.sh
├── build.sh
└── test.sh
├── iris
├── Chart.yaml
├── templates
│ ├── _helpers.tpl
│ ├── cm.yaml
│ ├── deploy.yaml
│ ├── namespace.yaml
│ └── role.yaml
└── values.yaml
├── main.go
└── pkg
├── app
└── app.go
├── dal
├── dal.go
└── integration.go
├── destination
├── codefresh_destination.go
├── default_destination.go
├── destination.go
└── service.go
├── filter
├── any_filter.go
├── factory.go
├── factory_internal_test.go
├── factory_test.go
├── filter.go
├── filter_apply_test.go
├── filter_test.go
├── helpers_test.go
├── jsonpath_filter.go
├── label_filter.go
├── mocks
│ ├── Factory.go
│ ├── Filter.go
│ └── Service.go
├── namespace_filter.go
├── reason_filter.go
├── service.go
└── service_test.go
├── kube
├── kube.go
└── mocks
│ └── Kube.go
├── logger
└── logger.go
├── reader
├── reader.go
└── reader_test.go
├── server
└── server.go
└── util
├── reader
└── file
│ ├── file.go
│ └── file_test.go
└── util.go
/.dockerignore:
--------------------------------------------------------------------------------
1 | example.*
2 | Dockerfile
3 | kube-resourecs
4 | .envrc
5 | .vscode
6 | .cover
7 | .examples
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on:
4 | push:
5 | branches: [ '*' ]
6 |
7 | jobs:
8 | ci:
9 | name: ci
10 | runs-on: "ubuntu-18.04"
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-go@v2
14 | with:
15 | go-version: '^1.18'
16 | - run: make test
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | example.*
3 | kube-resources
4 | .envrc
5 | .vscode
6 | .cover
7 | .examples
8 | vendor
--------------------------------------------------------------------------------
/.goreleaser.dev.yml:
--------------------------------------------------------------------------------
1 | builds:
2 | - env:
3 | - CGO_ENABLED=0
4 | goos:
5 | - darwin
6 | - linux
7 | ldflags:
8 | - -s -w
9 | - -X 'github.com/olegsu/iris/pkg/util.BuildVersion={{.Version}}'
10 | - -X 'github.com/olegsu/iris/pkg/util.BuildDate={{.Date}}'
11 | - -X 'github.com/olegsu/iris/pkg/util.BuildCommit={{.ShortCommit}}'
12 | - -X 'github.com/olegsu/iris/pkg/util.BuildBy=goreleaser'
13 | archive:
14 | replacements:
15 | darwin: Darwin
16 | linux: LinuxS
17 | checksum:
18 | name_template: 'checksums.txt'
19 | snapshot:
20 | name_template: "{{ .Tag }}-next"
21 | changelog:
22 | sort: asc
23 | filters:
24 | exclude:
25 | - '^docs:'
26 | - '^test:'
27 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | builds:
2 | - env:
3 | - CGO_ENABLED=0
4 | goos:
5 | - darwin
6 | - linux
7 | - windows
8 | ldflags:
9 | - -s -w
10 | - -X 'github.com/olegsu/iris/pkg/util.BuildVersion={{.Version}}'
11 | - -X 'github.com/olegsu/iris/pkg/util.BuildDate={{.Date}}'
12 | - -X 'github.com/olegsu/iris/pkg/util.BuildCommit={{.ShortCommit}}'
13 | - -X 'github.com/olegsu/iris/pkg/util.BuildBy=goreleaser'
14 |
15 | archive:
16 | replacements:
17 | darwin: Darwin
18 | linux: Linux
19 | windows: Windows
20 | 386: i386
21 | amd64: x86_64
22 | format_overrides:
23 | - goos: windows
24 | format: zip
25 | checksum:
26 | name_template: 'checksums.txt'
27 | snapshot:
28 | name_template: "{{ .Tag }}-next"
29 | changelog:
30 | sort: asc
31 | filters:
32 | exclude:
33 | - '^docs:'
34 | - '^test:'
35 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM alpine:3.8
2 |
3 | RUN apk --no-cache add --update ca-certificates
4 |
5 | COPY dist/iris_linux_386/iris /usr/local/bin/
6 |
7 | ENTRYPOINT ["/usr/local/bin/iris"]
8 | CMD [ "--help" ]
9 |
--------------------------------------------------------------------------------
/Iris.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olegsu/iris/2c8c0a1f81500e6454f8a7cbd1a46709801a52a8/Iris.jpg
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | ./hack/test.sh
3 |
4 | build-local:
5 | sh ./hack/build.sh
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # IRIS
2 |
3 |
4 |
5 | [](https://goreportcard.com/report/github.com/olegsu/iris)
6 | [](https://codecov.io/gh/olegsu/iris)
7 |
8 | **_In Greek mythology, Iris is the personification of the rainbow and messenger of the gods._**
9 |
10 | - [IRIS](#iris)
11 | - [Run in cluster](#run-in-cluster)
12 | - [Using Helm](#using-helm)
13 | - [Build](#build)
14 | - [Filters](#filters)
15 | - [Reason](#reason)
16 | - [Namespace](#namespace)
17 | - [JSONPath](#jsonpath)
18 | - [Labels](#labels)
19 | - [Any](#any)
20 | - [Destinations](#destinations)
21 | - [Default](#default)
22 | - [Codefresh](#codefresh)
23 | - [Integrations](#integrations)
24 |
25 | Easily configure webhooks on Kubernetes events using highly customizable filters
26 |
27 | * This project is not stable yet and may be changed anytime without any notice.
28 |
29 | ## Run in cluster
30 | ### Using Helm
31 | * clone or fork this repository
32 | * create your iris.yaml file
33 | * install chart from local directory `helm install ./iris --values ./iris.yaml`
34 | * by default the chart will be installed into namespace `iris`, see default values to overwrite it
35 |
36 | ## Build
37 | * clone or fork this repo
38 | * `make install`
39 | * `make build`
40 | * Limitations:
41 | * Execute out of cluster `iris run --help`
42 | * Execute on non GCP cluster
43 |
44 | Quick example:
45 |
46 | In this example we will configure to listen on any Kubernetes event that been reported by the pod controller and matched to the filter will be sent to the destination.
47 |
48 | ```yaml
49 | filters:
50 | - name: MatchDefaultNamespace
51 | type: namespace
52 | namespace: default
53 | - name: MatchPodKind
54 | type: jsonpath
55 | path: $.involvedObject.kind
56 | value: Pod
57 |
58 | destinations:
59 | - name: prod
60 | url: http://localhost
61 |
62 | integrations:
63 | - name: Report
64 | destinations:
65 | - prod
66 | filters:
67 | - MatchPodKind
68 | - MatchDefaultNamespace
69 | ```
70 |
71 | ## Filters
72 | Set of rules that will be applied on each [Kubernetes event](https://github.com/kubernetes/api/blob/master/core/v1/types.go#L4501).
73 | Kubernetes event that will pass all required filters will be passed to the destination to be reported
74 | Types of filters:
75 | ### Reason
76 | Reason filter is a syntactic sugar for [JSONPath](#jsonpath) filter with `path: $.reason` and `value: {{reason}}`
77 | ```yaml
78 | filters:
79 | - name: PodScheduled
80 | reason: "Scheduled"
81 | ```
82 |
83 | ### Namespace
84 | Namespace filter is a syntactic sugar for [JSONPath](#jsonpath) filter with `path: $.metadata.namespace` and `value: {{reason}}`
85 | ```yaml
86 | filters:
87 | - name: FromDefaultNamespace
88 | namespace: default
89 | ```
90 |
91 | ### JSONPath
92 | With JSONPath gives the ability to match any field from [Kubernetes event](https://github.com/kubernetes/api/blob/master/core/v1/types.go#L5478).
93 | The value from the fields can be matched to exec value using `value: {{value}}` or matched by regex using `regexp: {{regexp}}`
94 | ```yaml
95 | filters:
96 | # Match to Warning event type
97 | - name: WarningLevel
98 | type: jsonpath
99 | path: $.type
100 | value: Warning
101 | # Match to any event that the name matched to regexp /tiller/
102 | - name: MatchToRegexpTiller
103 | type: jsonpath
104 | path: $.metadata.name
105 | regexp: tiller
106 | ```
107 |
108 |
109 |
110 | ### Labels
111 | Labels filter will try to get the original resource from the event with the given filters.
112 | The filter considers as passed if any resource were found
113 | ```yaml
114 | filters:
115 | - name: MatchLabels
116 | type: labels
117 | labels:
118 | app: helm
119 | ```
120 |
121 | ### Any
122 | ```yaml
123 | filters:
124 | - name: WarningLevel
125 | type: any
126 | filters:
127 | - FromDefaultNamespace
128 | - WarningLevel
129 | ```
130 |
131 | ## Destinations
132 | Each destination is an api endpoint where the Kubernetes event will be sent
133 | Types of destinations:
134 | ### Default
135 | The default destinations will attempt to send POST request with the event json in the request body
136 | If `secret` is given, hash string will be calculated using the given key on the request body and the result will be set in the request header as `X-IRIS-HMAC: hash`
137 | ```yaml
138 | destinations:
139 | - name: Webhook
140 | url: https://webhook.site
141 | secret: SECRET
142 | ```
143 | ### Codefresh
144 | With Iris, you can execute Codefresh pipelines.
145 | Add destinations with Codefresh type:
146 | * name: pipeline full name can be found easily using [Codefresh CLI](https://codefresh-io.github.io/cli/) - `codefresh get pipelines`
147 | * branch: which branch of the repo should be cloned
148 | * cftoken: Token to Codefresh API can be generated in [Account settings/Tokens](https://g.codefresh.io/account-conf/tokens) view
149 | ```yaml
150 | - name: ExecuteCodefreshPipeline
151 | type: Codefresh
152 | pipeline: PIPELINE_NAME
153 | cftoken: API_TOKEN
154 | branch: master
155 | ```
156 |
157 | ## Integrations
158 | Connecting between filters and destinations
159 | ```yaml
160 | integrations:
161 | - name: Report
162 | destinations:
163 | - {{name of destination}}
164 | filters:
165 | - {{name of filters to apply}}
166 | ```
167 |
--------------------------------------------------------------------------------
/VERSION:
--------------------------------------------------------------------------------
1 | 0.7.0
2 |
--------------------------------------------------------------------------------
/cmd/cmdutils.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | // Copyright © 2019 oleg2807@gmail.com
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/olegsu/iris/pkg/logger"
22 | )
23 |
24 | func dieOnError(err error) {
25 | if err != nil {
26 | fmt.Errorf(err.Error())
27 | os.Exit(1)
28 | }
29 | }
30 |
31 | func buildLogger(cmd string) logger.Logger {
32 | return logger.New(&logger.Options{
33 | Command: cmd,
34 | Verbose: true,
35 | })
36 | }
37 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | // Copyright © 2019 oleg2807@gmail.com
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | import (
18 | "github.com/olegsu/iris/pkg/util"
19 |
20 | "github.com/spf13/cobra"
21 | )
22 |
23 | var rootCmdOptions struct {
24 | Verbose bool
25 | }
26 |
27 | var rootCmd = &cobra.Command{
28 | Use: "iris",
29 | Long: "Watch on Kubernetes events, filter and send them as standard wehbook to any system",
30 | Version: util.BuildVersion,
31 | }
32 |
33 | // Execute - execute the root command
34 | func Execute() {
35 | err := rootCmd.Execute()
36 | dieOnError(err)
37 | }
38 |
39 | func init() {
40 | rootCmd.PersistentFlags().BoolVar(&rootCmdOptions.Verbose, "verbose", false, "Set to get more detailed output")
41 | }
42 |
--------------------------------------------------------------------------------
/cmd/run.go:
--------------------------------------------------------------------------------
1 | package cmd
2 |
3 | // Copyright © 2019 oleg2807@gmail.com
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/spf13/cobra"
22 | "github.com/spf13/viper"
23 |
24 | "github.com/olegsu/iris/pkg/app"
25 | )
26 |
27 | var runCmdOptions struct {
28 | file string
29 | kube string
30 | inCluster bool
31 | }
32 |
33 | var runCmd = &cobra.Command{
34 | Use: "run",
35 | Long: "Start the server",
36 | Run: func(cmd *cobra.Command, args []string) {
37 | logger := buildLogger("run")
38 | if runCmdOptions.kube == "" {
39 | path := fmt.Sprintf("%s/.kube/config", os.Getenv("HOME"))
40 | logger.Debug("Path to kubeconfig not set, using default", "path", path)
41 | runCmdOptions.kube = path
42 | }
43 | config := app.NewApplicationOptions(runCmdOptions.file, runCmdOptions.kube, runCmdOptions.inCluster, logger)
44 | app.CreateApp(config)
45 | },
46 | }
47 |
48 | func init() {
49 | rootCmd.AddCommand(runCmd)
50 |
51 | viper.BindEnv("file", "IRIS_FILE")
52 |
53 | viper.BindEnv("kube", "KUBECONFIG")
54 |
55 | viper.BindEnv("inCluster", "IRIS_IN_CLUSTER")
56 |
57 | runCmd.Flags().StringVar(&runCmdOptions.file, "iris-file", viper.GetString("file"), "Iris yaml config file [$IRIS_FILE]")
58 | runCmd.Flags().StringVar(&runCmdOptions.kube, "kube-config", viper.GetString("kube"), "Path to kube-config file (default is ~/.kube/config) [$KUBECONFIG]")
59 | runCmd.Flags().BoolVar(&runCmdOptions.inCluster, "in-cluster", viper.GetBool("inCluster"), "Set when running inside a cluster. NOTE: This option will ignore --kube-config flag [$IRIS_IN_CLUSTER]")
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/olegsu/iris
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07
7 | github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec
8 | github.com/spf13/cobra v0.0.3
9 | github.com/spf13/viper v1.3.1
10 | github.com/stretchr/testify v1.3.0
11 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0
12 | gopkg.in/yaml.v2 v2.2.8
13 | k8s.io/api v0.0.0-20180628040859-072894a440bd
14 | k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d
15 | k8s.io/client-go v8.0.0+incompatible
16 | )
17 |
18 | require (
19 | github.com/BurntSushi/toml v0.3.1 // indirect
20 | github.com/davecgh/go-spew v1.1.1 // indirect
21 | github.com/fsnotify/fsnotify v1.4.7 // indirect
22 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 // indirect
23 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
24 | github.com/go-stack/stack v1.8.0 // indirect
25 | github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e // indirect
26 | github.com/golang/glog v0.0.0-20141105023935-44145f04b68c // indirect
27 | github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff // indirect
28 | github.com/golang/protobuf v1.1.0 // indirect
29 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e // indirect
30 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 // indirect
31 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d // indirect
32 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 // indirect
33 | github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 // indirect
34 | github.com/hashicorp/hcl v1.0.0 // indirect
35 | github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 // indirect
36 | github.com/inconshreveable/mousetrap v1.0.0 // indirect
37 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 // indirect
38 | github.com/magiconair/properties v1.8.0 // indirect
39 | github.com/mattn/go-colorable v0.1.1 // indirect
40 | github.com/mattn/go-isatty v0.0.5 // indirect
41 | github.com/mitchellh/mapstructure v1.1.2 // indirect
42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
43 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da // indirect
44 | github.com/pelletier/go-toml v1.2.0 // indirect
45 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
46 | github.com/pmezard/go-difflib v1.0.0 // indirect
47 | github.com/spf13/afero v1.1.2 // indirect
48 | github.com/spf13/cast v1.3.0 // indirect
49 | github.com/spf13/jwalterweatherman v1.0.0 // indirect
50 | github.com/spf13/pflag v1.0.3 // indirect
51 | github.com/stretchr/objx v0.1.0 // indirect
52 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 // indirect
53 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 // indirect
54 | golang.org/x/net v0.0.0-20170809000501-1c05540f6879 // indirect
55 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 // indirect
56 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 // indirect
57 | golang.org/x/text v0.3.0 // indirect
58 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d // indirect
59 | gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
60 | gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
61 | gopkg.in/inf.v0 v0.9.0 // indirect
62 | gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 // indirect
63 | )
64 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
4 | github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
5 | github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
6 | github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
11 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
12 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680 h1:ZktWZesgun21uEDrwW7iEV1zPCGQldM2atlJZ3TdvVM=
13 | github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
14 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
15 | github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
16 | github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07 h1:cZPJWzd2oNeoS0oJM2TlN9rl0OnCgUr10gC8Q4mH+6M=
17 | github.com/gin-gonic/gin v0.0.0-20170702092826-d459835d2b07/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
18 | github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
19 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
20 | github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e h1:ago6fNuQ6IhszPsXkeU7qRCyfsIX7L67WDybsAPkLl8=
21 | github.com/gogo/protobuf v0.0.0-20170330071051-c0656edd0d9e/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
22 | github.com/golang/glog v0.0.0-20141105023935-44145f04b68c h1:CbdkBQ1/PiAo0FYJhQGwASD8wrgNvTdf01g6+O9tNuA=
23 | github.com/golang/glog v0.0.0-20141105023935-44145f04b68c/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
24 | github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff h1:kOkM9whyQYodu09SJ6W3NCsHG7crFaJILQ22Gozp3lg=
25 | github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
26 | github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc=
27 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
28 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e h1:JHB7F/4TJCrYBW8+GZO8VkWDj1jxcWuCl6uxKODiyi4=
29 | github.com/google/btree v0.0.0-20160524151835-7d79101e329e/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
30 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367 h1:ScAXWS+TR6MZKex+7Z8rneuSJH+FSDqd6ocQyl+ZHo4=
31 | github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
32 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
33 | github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
34 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w=
35 | github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
36 | github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880 h1:OaRuzt9oCKNui8cCskZijoKUwe+aCuuCwvx1ox8FNyw=
37 | github.com/hashicorp/golang-lru v0.0.0-20160207214719-a0d98a5f2880/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
38 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
39 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
40 | github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1 h1:FeeCi0I2Fu8kA8IXrdVPtGzym+mW9bzfj9f26EaES9k=
41 | github.com/imdario/mergo v0.0.0-20141206190957-6633656539c1/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
42 | github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec h1:CGkYB1Q7DSsH/ku+to+foV4agt2F2miquaLUgF6L178=
43 | github.com/inconshreveable/log15 v0.0.0-20180818164646-67afb5ed74ec/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o=
44 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
45 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
46 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3 h1:/UewZcckqhvnnS0C6r3Sher2hSEbVmM6Ogpcjen08+Y=
47 | github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
48 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
49 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
50 | github.com/mattn/go-colorable v0.1.1 h1:G1f5SKeVxmagw/IyvzvtZE4Gybcc4Tr1tf7I8z0XgOg=
51 | github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
52 | github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw=
53 | github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
54 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
55 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
56 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
57 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
58 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da h1:ZQGIPjr1iTtUPXZFk8WShqb5G+Qg65VHFLtSvmHh+Mw=
59 | github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
60 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
61 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
62 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
63 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
64 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
65 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
66 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
67 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
68 | github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
69 | github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
70 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
71 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
72 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
73 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
74 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
75 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
76 | github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38=
77 | github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
78 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
79 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
80 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
81 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
82 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
83 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648=
84 | github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
85 | github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
86 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
87 | github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
88 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
89 | golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
90 | golang.org/x/net v0.0.0-20170809000501-1c05540f6879 h1:0rFa7EaCGdQPmZVbo9F7MNF65b8dyzS6EUnXjs9Cllk=
91 | golang.org/x/net v0.0.0-20170809000501-1c05540f6879/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
92 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
93 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
94 | golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
95 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223 h1:DH4skfRX4EBpamg7iV4ZlCpblAHI6s6TDM39bFZumv8=
96 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
97 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
98 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
99 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d h1:TnM+PKb3ylGmZvyPXmo9m/wktg7Jn/a/fNmr33HSj8g=
100 | golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
101 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
102 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
103 | gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
104 | gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
105 | gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
106 | gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
107 | gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
108 | gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
109 | gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0 h1:POO/ycCATvegFmVuPpQzZFJ+pGZeX22Ufu6fibxDVjU=
110 | gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg=
111 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
112 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
113 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
114 | k8s.io/api v0.0.0-20180628040859-072894a440bd h1:HzgYeLDS1jLxw8DGr68KJh9cdQ5iZJizG0HZWstIhfQ=
115 | k8s.io/api v0.0.0-20180628040859-072894a440bd/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA=
116 | k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d h1:MZjlsu9igBoVPZkXpIGoxI6EonqNsXXZU7hhvfQLkd4=
117 | k8s.io/apimachinery v0.0.0-20180621070125-103fd098999d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0=
118 | k8s.io/client-go v8.0.0+incompatible h1:tTI4hRmb1DRMl4fG6Vclfdi6nTM82oIrTT7HfitmxC4=
119 | k8s.io/client-go v8.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s=
120 |
--------------------------------------------------------------------------------
/hack/.codefresh/push-helm.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | defaultBranch="master"
4 |
5 | getChartVersion(){
6 | version="$(cat VERSION)"
7 | if [ "$CF_BRANCH_TAG_NORMALIZED" = "$defaultBranch" ]
8 | then
9 | version=$version
10 | else
11 | version=$version-$CF_BRANCH_TAG_NORMALIZED-$CF_SHORT_REVISION
12 | fi
13 | echo $version
14 | }
15 |
16 | updateChartVersion(){
17 | version=$1
18 | yq --arg version "$version" '.version = $version' $CF_VOLUME_PATH/iris/iris/Chart.yaml | yq -y '.sources[.sources | length] = env.CF_COMMIT_URL' --yaml-output > $CF_VOLUME_PATH/Chart.new.yaml
19 | mv $CF_VOLUME_PATH/Chart.new.yaml $CF_VOLUME_PATH/iris/iris/Chart.yaml
20 | }
21 |
22 | v=$(getChartVersion)
23 | echo "Setting version to be $v"
24 | updateChartVersion $v
25 | cf_export IRIS_VERSION=$v
--------------------------------------------------------------------------------
/hack/build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -e
3 |
4 | OUTFILE=${1:-/usr/local/bin/irisctl}
5 | BUILD_DATE=$(date)
6 | GIT_TAG=$(git describe --tags)
7 | GIT_COMMIT=$(git rev-parse --short HEAD)
8 |
9 | LDFLAGS="-s -w
10 | -X 'github.com/olegsu/iris/pkg/util.BuildVersion=${GIT_TAG}'
11 | -X 'github.com/olegsu/iris/pkg/util.BuildDate=${BUILD_DATE}'
12 | -X 'github.com/olegsu/iris/pkg/util.BuildCommit=${GIT_COMMIT}'
13 | -X 'github.com/olegsu/iris/pkg/util.BuildBy=Makefile'
14 | "
15 |
16 | go build -ldflags "$LDFLAGS" -o "$OUTFILE" main.go
17 |
--------------------------------------------------------------------------------
/hack/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -e
4 | set -o pipefail
5 |
6 | rm -rf .cover/ .test/
7 | mkdir .cover/ .test/
8 | trap "rm -rf .test/" EXIT
9 | for pkg in `go list ./... | grep -v /vendor/`; do
10 | go test -v -covermode=atomic \
11 | -coverprofile=".cover/$(echo $pkg | sed 's/\//_/g').cover.out" $pkg
12 | done
13 |
14 | echo "mode: set" > .cover/cover.out && cat .cover/*.cover.out | grep -v mode: | sort -r | \
15 | awk '{if($1 != last) {print $0;last=$1}}' >> .cover/cover.out
16 |
17 | go tool cover -html=.cover/cover.out -o=.cover/coverage.html
18 |
19 | CODECOV_BASH_URL=https://codecov.io/bash
20 | if [ "$CODECOV_TOKEN" != "" ]; then curl -s $CODECOV_BASH_URL | bash -s; fi
--------------------------------------------------------------------------------
/iris/Chart.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | version: 0.1.0
3 | description: Watch on Kubernetes event, filter and send them as standard wehbook you any system
4 | name: iris
5 | keywords:
6 | - kubernetes
7 | - webhook
8 | - codefresh
9 | - slack
10 | icon: https://github.com/olegsu/iris/raw/master/Iris.jpg
11 | sources:
12 | - https://github.com/olegsu/iris
13 | maintainers:
14 | - name: Oleg Sucharevich
15 | email: oleg2807@gmail.com
16 |
--------------------------------------------------------------------------------
/iris/templates/_helpers.tpl:
--------------------------------------------------------------------------------
1 | {{/* vim: set filetype=mustache: */}}
2 | {{/*
3 | Expand the name of the chart.
4 | */}}
5 | {{- define "name" -}}
6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
7 | {{- end -}}
8 |
9 | {{/*
10 | Create a default fully qualified app name.
11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
12 | */}}
13 | {{- define "fullname" -}}
14 | {{- $name := default .Chart.Name .Values.nameOverride -}}
15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}}
16 | {{- end -}}
--------------------------------------------------------------------------------
/iris/templates/cm.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | data:
3 | iris: |
4 |
5 | {{- if .Values.filters }}
6 | filters:
7 | {{ toYaml .Values.filters | indent 8 }}
8 | {{- end }}
9 |
10 | {{- if .Values.destinations }}
11 | destinations:
12 | {{ toYaml .Values.destinations | indent 8 }}
13 | {{- end }}
14 |
15 | {{- if .Values.integrations }}
16 | integrations:
17 | {{ toYaml .Values.integrations | indent 8 }}
18 | {{- end }}
19 |
20 | kind: ConfigMap
21 | metadata:
22 | name: {{ template "fullname" . }}
23 | namespace: {{ .Values.namespace }}
--------------------------------------------------------------------------------
/iris/templates/deploy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: extensions/v1beta1
2 | kind: Deployment
3 | metadata:
4 | name: {{ template "fullname" . }}
5 | namespace: {{ .Values.namespace }}
6 | labels:
7 | app: {{ template "fullname" . }}
8 | chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
9 | release: {{ .Release.Name | quote }}
10 | heritage: {{ .Release.Service | quote }}
11 | version: {{ .Values.image.tag | quote }}
12 | spec:
13 | replicas: 1
14 | template:
15 | metadata:
16 | labels:
17 | app: {{ template "fullname" . }}
18 | spec:
19 | containers:
20 | - env:
21 | - name: IRIS_FILE
22 | value: {{ .Values.configmap.mountPath }}
23 | name: {{ template "fullname" . }}
24 | command:
25 | - "iris"
26 | - "run"
27 | imagePullPolicy: {{ .Values.imagePullPolicy | quote }}
28 | image: {{ .Values.image.name }}:{{ .Values.image.tag }}
29 | volumes:
30 | - name: config
31 | configMap:
32 | name: {{ template "fullname" . }}
33 | mountPath: {{ .Values.configmap.mountPath }}
--------------------------------------------------------------------------------
/iris/templates/namespace.yaml:
--------------------------------------------------------------------------------
1 | {{- if ne .Values.namespace "default" }}
2 |
3 | apiVersion: v1
4 | kind: Namespace
5 | metadata:
6 | name: {{ .Values.namespace }}
7 |
8 | {{- end }}
--------------------------------------------------------------------------------
/iris/templates/role.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: rbac.authorization.k8s.io/v1
2 | kind: ClusterRole
3 | metadata:
4 | name: {{ template "fullname" . }}
5 | rules:
6 | - apiGroups: [""]
7 | verbs: ["get", "list", "watch"]
8 | resources: ["events", "pods"]
9 |
10 | ---
11 | apiVersion: rbac.authorization.k8s.io/v1
12 | kind: ClusterRoleBinding
13 | metadata:
14 | name: {{ template "fullname" . }}
15 | roleRef:
16 | apiGroup: rbac.authorization.k8s.io
17 | kind: ClusterRole
18 | name: {{ template "fullname" . }}
19 | subjects:
20 | - kind: ServiceAccount
21 | name: default
22 | namespace: {{ .Values.namespace }}
23 |
--------------------------------------------------------------------------------
/iris/values.yaml:
--------------------------------------------------------------------------------
1 | image:
2 | name: olsynt/iris
3 | tag: latest
4 |
5 | namespace: iris
6 |
7 | imagePullPolicy: IfNotPresent
8 |
9 | configmap:
10 | mountPath: /iris/iris.yaml
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | // Copyright © 2019 oleg2807@gmail.com
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 |
17 | import "github.com/olegsu/iris/cmd"
18 |
19 | func main() {
20 | cmd.Execute()
21 | }
22 |
--------------------------------------------------------------------------------
/pkg/app/app.go:
--------------------------------------------------------------------------------
1 | package app
2 |
3 | import (
4 | "github.com/olegsu/iris/pkg/dal"
5 | "github.com/olegsu/iris/pkg/kube"
6 | "github.com/olegsu/iris/pkg/logger"
7 | "github.com/olegsu/iris/pkg/reader"
8 | "github.com/olegsu/iris/pkg/server"
9 | )
10 |
11 | type ApplicationOptions struct {
12 | IrisPath string
13 | KubeconfigPath string
14 | InCluster bool
15 | Logger logger.Logger
16 | }
17 |
18 | func NewApplicationOptions(irisconfig string, kubeconfig string, incluster bool, logger logger.Logger) *ApplicationOptions {
19 | return &ApplicationOptions{
20 | IrisPath: irisconfig,
21 | KubeconfigPath: kubeconfig,
22 | InCluster: incluster,
23 | Logger: logger,
24 | }
25 | }
26 |
27 | func CreateApp(config *ApplicationOptions) {
28 | k := kube.NewKubeManager(config.KubeconfigPath, config.InCluster, config.Logger)
29 | var r reader.IRISProcessor
30 |
31 | r, _ = reader.NewProcessor([]string{
32 | config.IrisPath,
33 | }, k)
34 | bytes, _ := reader.Process(r)
35 | d := dal.CreateDalFromBytes(bytes, k, config.Logger)
36 | fn := func(obj interface{}) {
37 | onAdd(d.Integrations, obj)
38 | }
39 | go k.Watch(fn)
40 | server.StartServer(config.Logger)
41 | }
42 |
43 | func onAdd(integrations []*dal.Integration, obj interface{}) {
44 | for _, integration := range integrations {
45 | go integration.Exec(obj)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/pkg/dal/dal.go:
--------------------------------------------------------------------------------
1 | package dal
2 |
3 | import (
4 | "github.com/olegsu/iris/pkg/destination"
5 | "github.com/olegsu/iris/pkg/filter"
6 | "github.com/olegsu/iris/pkg/kube"
7 | "github.com/olegsu/iris/pkg/logger"
8 | "github.com/olegsu/iris/pkg/util"
9 | )
10 |
11 | var dal *Dal
12 |
13 | type Dal struct {
14 | DestinationService destination.Service
15 | Integrations []*Integration
16 | FilterService filter.Service
17 | Logger logger.Logger
18 | }
19 |
20 | func GetDal() *Dal {
21 | return dal
22 | }
23 |
24 | func CreateDalFromBytes(bytes []byte, k kube.Kube, logger logger.Logger) *Dal {
25 | d := &Dal{
26 | Logger: logger,
27 | }
28 | var data map[string][]map[string]interface{}
29 | util.UnmarshalOrDie(bytes, &data, logger)
30 |
31 | d.FilterService = filter.NewService(filter.NewFactory(logger), data["filters"], k, logger)
32 |
33 | d.DestinationService = destination.NewService(data["destinations"], k, logger)
34 |
35 | for _, integration := range data["integrations"] {
36 | i := &Integration{}
37 | util.MapToObjectOrDie(integration, i, logger)
38 | i.logger = logger.New("Integration-Name", i.Name)
39 | d.Integrations = append(d.Integrations, i)
40 | }
41 | dal = d
42 | return d
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/dal/integration.go:
--------------------------------------------------------------------------------
1 | package dal
2 |
3 | import (
4 | "encoding/json"
5 |
6 | "github.com/olegsu/iris/pkg/destination"
7 | "github.com/olegsu/iris/pkg/filter"
8 | "github.com/olegsu/iris/pkg/logger"
9 |
10 | v1 "k8s.io/api/core/v1"
11 | )
12 |
13 | type Integration struct {
14 | Name string `yaml:"name"`
15 | Filters []string `yaml:"filters"`
16 | Destinations []string `yaml:"destinations"`
17 | logger logger.Logger
18 | }
19 |
20 | func (i *Integration) Exec(obj interface{}) (bool, error) {
21 | ev := obj.(*v1.Event)
22 | var j interface{}
23 | bytes, err := json.Marshal(&ev)
24 | if err != nil {
25 | return false, nil
26 | }
27 | json.Unmarshal(bytes, &j)
28 | result := true
29 | result = filter.IsFiltersMatched(GetDal().FilterService, i.Filters, j, i.logger)
30 | if result == true {
31 | i.logger.Debug("All checks are passed, executing", "name", i.Name)
32 | destination.Exec(GetDal().DestinationService, i.Destinations, obj, i.logger)
33 | }
34 | return false, nil
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/destination/codefresh_destination.go:
--------------------------------------------------------------------------------
1 | package destination
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "io/ioutil"
8 | "net/http"
9 | "net/url"
10 |
11 | v1 "k8s.io/api/core/v1"
12 | )
13 |
14 | type codefreshDestination struct {
15 | baseDestination `yaml:",inline"`
16 | Branch string `yaml:"branch"`
17 | Pipeline string `yaml:"pipeline"`
18 | CFToken string `yaml:"cftoken"`
19 | }
20 |
21 | type codefreshPostRequestBody struct {
22 | Options map[string]string `json:"options"`
23 | Variables map[string]string `json:"variables"`
24 | Contexts []string `json:"contexts"`
25 | Branch string `json:"branch"`
26 | }
27 |
28 | func (d *codefreshDestination) Exec(payload interface{}) {
29 | postBody := &codefreshPostRequestBody{
30 | Variables: make(map[string]string),
31 | }
32 | if d.Branch != "" {
33 | postBody.Branch = d.Branch
34 | }
35 | var ev *v1.Event
36 | b, _ := json.Marshal(payload)
37 | json.Unmarshal(b, &ev)
38 |
39 | postBody.Variables["IRIS_RESOURCE_NAME"] = ev.InvolvedObject.Name
40 | postBody.Variables["IRIS_NAMESPACE"] = ev.InvolvedObject.Namespace
41 |
42 | mJSON, _ := json.Marshal(postBody)
43 | contentReader := bytes.NewReader(mJSON)
44 |
45 | url := fmt.Sprintf("https://g.codefresh.io/api/pipelines/run/%s", url.QueryEscape(d.Pipeline))
46 | d.logger.Debug("Executing Codefresh destination\n")
47 | req, _ := http.NewRequest("POST", url, contentReader)
48 | req.Header.Set("authorization", d.CFToken)
49 | req.Header.Set("User-Agent", UserAgent)
50 | req.Header.Set("Content-Type", "application/json")
51 | client := &http.Client{}
52 | resp, _ := client.Do(req)
53 | defer resp.Body.Close()
54 | body, _ := ioutil.ReadAll(resp.Body)
55 | if resp.StatusCode == 200 {
56 | d.logger.Debug("Build started", "ID", string(body))
57 | } else {
58 | d.logger.Debug("Error!", "Status_Code", resp.StatusCode, "message", string(body))
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/destination/default_destination.go:
--------------------------------------------------------------------------------
1 | package destination
2 |
3 | import (
4 | "bytes"
5 | "crypto/hmac"
6 | "crypto/sha256"
7 | "encoding/base64"
8 | "encoding/json"
9 | "net/http"
10 | )
11 |
12 | type defaultDestination struct {
13 | baseDestination `yaml:",inline"`
14 | URL string `yaml:"url"`
15 | Secret string `yaml:"secret"`
16 | Headers map[string]string `yaml:"headers"`
17 | }
18 |
19 | func getHmac(secret string, payload []byte) string {
20 | if secret != "" {
21 | key := []byte(secret)
22 | mac := hmac.New(sha256.New, key)
23 | mac.Write(payload)
24 | hmac := base64.URLEncoding.EncodeToString(mac.Sum(nil))
25 | return hmac
26 | }
27 | return ""
28 | }
29 |
30 | func (d *defaultDestination) Exec(payload interface{}) {
31 | d.logger.Debug("Executing default destination", "name", d.Name, "URL", d.URL)
32 | mJSON, _ := json.Marshal(payload)
33 | contentReader := bytes.NewReader(mJSON)
34 | req, _ := http.NewRequest("POST", d.URL, contentReader)
35 | req.Header.Set("X-IRIS-HMAC", getHmac(d.Secret, mJSON))
36 | req.Header.Set("Content-Type", "application/json")
37 | req.Header.Set("User-Agent", UserAgent)
38 | for k, v := range d.Headers {
39 | req.Header.Set(k, v)
40 | }
41 | client := &http.Client{}
42 | client.Do(req)
43 | }
44 |
--------------------------------------------------------------------------------
/pkg/destination/destination.go:
--------------------------------------------------------------------------------
1 | package destination
2 |
3 | import (
4 | "github.com/olegsu/iris/pkg/kube"
5 | "github.com/olegsu/iris/pkg/logger"
6 | "github.com/olegsu/iris/pkg/util"
7 | )
8 |
9 | const (
10 | TypeDefault = ""
11 | TypeCodefresh = "codefresh"
12 | )
13 |
14 | var (
15 | UserAgent = "iris/" + util.BuildVersion
16 | )
17 |
18 | type Destination interface {
19 | GetName() string
20 | Exec(payload interface{})
21 | GetType() string
22 | }
23 |
24 | func NewDestination(json map[string]interface{}, k kube.Kube, logger logger.Logger) Destination {
25 | var destination Destination
26 | if json["type"] != nil {
27 | destination = &codefreshDestination{
28 | baseDestination: baseDestination{
29 | logger: logger,
30 | },
31 | }
32 | } else {
33 | destination = &defaultDestination{
34 | baseDestination: baseDestination{
35 | logger: logger,
36 | },
37 | }
38 | }
39 | util.MapToObjectOrDie(json, destination, logger)
40 | return destination
41 | }
42 |
43 | type baseDestination struct {
44 | Name string `yaml:"name"`
45 | Type string `yaml:"type"`
46 | logger logger.Logger
47 | }
48 |
49 | func (d *baseDestination) GetName() string {
50 | return d.Name
51 | }
52 |
53 | func (d *baseDestination) GetType() string {
54 | return d.Type
55 | }
56 |
--------------------------------------------------------------------------------
/pkg/destination/service.go:
--------------------------------------------------------------------------------
1 | package destination
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/olegsu/iris/pkg/kube"
7 | "github.com/olegsu/iris/pkg/logger"
8 | )
9 |
10 | var d *dal
11 |
12 | type dal struct {
13 | destinations []Destination
14 | logger logger.Logger
15 | }
16 |
17 | // Service
18 | type Service interface {
19 | GetDestinationByName(string) (Destination, error)
20 | }
21 |
22 | // GetDestinationByName - finds a filters if exist
23 | func (d *dal) GetDestinationByName(name string) (Destination, error) {
24 | var destination Destination
25 | if d.destinations == nil {
26 | return nil, fmt.Errorf("No destination %s", name)
27 | }
28 | for index := 0; index < len(d.destinations); index++ {
29 | filterName := d.destinations[index].GetName()
30 | if filterName == name {
31 | destination = d.destinations[index]
32 | }
33 | }
34 | if destination == nil {
35 | return nil, fmt.Errorf("%s destination not found", name)
36 | }
37 | return destination, nil
38 | }
39 |
40 | // NewService - creates net Dal from json array of filters
41 | func NewService(destinationArray []map[string]interface{}, k kube.Kube, logger logger.Logger) Service {
42 | tempDal := &dal{
43 | logger: logger,
44 | }
45 | for _, json := range destinationArray {
46 | f := NewDestination(json, k, logger)
47 | tempDal.destinations = append(tempDal.destinations, f)
48 | }
49 | d = tempDal
50 | return tempDal
51 | }
52 |
53 | func Exec(serivce Service, names []string, payload interface{}, logger logger.Logger) {
54 | for _, name := range names {
55 | dest, err := serivce.GetDestinationByName(name)
56 | if err != nil {
57 | logger.Error("Error", "err", err.Error())
58 | } else {
59 | dest.Exec(payload)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/pkg/filter/any_filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | type anyFilter struct {
4 | baseFilter `yaml:",inline"`
5 | Filters []string `yaml:"filters"`
6 | Service Service
7 | }
8 |
9 | func (f *anyFilter) Apply(data interface{}) (bool, error) {
10 | result := false
11 | var err error
12 | for _, name := range f.Filters {
13 | filter, err := f.Service.GetFilterByName(name)
14 | if err != nil {
15 | return false, err
16 | }
17 | res, err := filter.Apply(data)
18 | if err != nil {
19 | return false, err
20 | }
21 | if res == true {
22 | result = true
23 | }
24 | }
25 | return result, err
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/filter/factory.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/olegsu/iris/pkg/kube"
8 | "github.com/olegsu/iris/pkg/logger"
9 | "github.com/olegsu/iris/pkg/util"
10 | )
11 |
12 | // Factory interface builds filters
13 | type Factory interface {
14 | Build(map[string]interface{}, Service, kube.Kube) (Filter, error)
15 | }
16 |
17 | type f struct {
18 | logger logger.Logger
19 | }
20 |
21 | // Build build actual filter and return Filter interface
22 | func (_f *f) Build(json map[string]interface{}, s Service, k kube.Kube) (Filter, error) {
23 | if json["type"] != nil {
24 | t := strings.ToLower(json["type"].(string))
25 | var f Filter
26 | switch t {
27 | case TypeReason:
28 | f = &reasonFilter{
29 | baseFilter: baseFilter{
30 | logger: _f.logger,
31 | },
32 | }
33 | break
34 | case TypeNamespace:
35 | f = &namespaceFilter{
36 | baseFilter: baseFilter{
37 | logger: _f.logger,
38 | },
39 | }
40 | break
41 | case TypeJSONPath:
42 | f = &jsonPathFilter{
43 | baseFilter: baseFilter{
44 | logger: _f.logger,
45 | },
46 | }
47 | break
48 | case TypeLabel:
49 | f = &labelFilter{
50 | kube: k,
51 | baseFilter: baseFilter{
52 | logger: _f.logger,
53 | },
54 | }
55 | break
56 | case TypeAny:
57 | f = &anyFilter{
58 | Service: s,
59 | baseFilter: baseFilter{
60 | logger: _f.logger,
61 | },
62 | }
63 | break
64 | }
65 | if f == nil {
66 | return nil, fmt.Errorf("Type %s is not supported", json["type"])
67 | }
68 | util.MapToObjectOrDie(json, f, _f.logger)
69 | return f, nil
70 | } else {
71 | return nil, fmt.Errorf("Type passed to filter %v", json)
72 | }
73 | }
74 |
75 | // NewFactory create new factory
76 | func NewFactory(logger logger.Logger) Factory {
77 | return &f{
78 | logger: logger,
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/pkg/filter/factory_internal_test.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/olegsu/iris/pkg/logger"
8 | "github.com/stretchr/testify/assert"
9 | )
10 |
11 | func TestNewFactory(t *testing.T) {
12 | tests := []struct {
13 | name string
14 | }{
15 | {
16 | name: "Get filter factory",
17 | },
18 | }
19 | for _, tt := range tests {
20 | t.Run(tt.name, func(t *testing.T) {
21 | message := fmt.Sprintf("Failed to get factory")
22 | factory := NewFactory(logger.New(nil))
23 | assert.IsType(t, &f{}, factory, message)
24 | })
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/pkg/filter/factory_test.go:
--------------------------------------------------------------------------------
1 | package filter_test
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/olegsu/iris/pkg/filter"
8 | "github.com/olegsu/iris/pkg/filter/mocks"
9 | kube "github.com/olegsu/iris/pkg/kube/mocks"
10 | "github.com/olegsu/iris/pkg/logger"
11 | "github.com/stretchr/testify/assert"
12 | )
13 |
14 | func Test_f_Build(t *testing.T) {
15 | tests := []struct {
16 | name string
17 | buildJSONArg func() map[string]interface{}
18 | want filter.Filter
19 | wantErr bool
20 | }{
21 | {
22 | name: "Build filte with reason type",
23 | wantErr: false,
24 | buildJSONArg: func() map[string]interface{} {
25 | return map[string]interface{}{
26 | "type": "reason",
27 | "name": "filter-name",
28 | "reason": "reason",
29 | }
30 | },
31 | },
32 | {
33 | name: "Build filte with namespace type",
34 | wantErr: false,
35 | buildJSONArg: func() map[string]interface{} {
36 | return map[string]interface{}{
37 | "type": "namespace",
38 | "name": "filter-name",
39 | "namespace": "default",
40 | }
41 | },
42 | },
43 | {
44 | name: "Build filte with jsonpath type with value match",
45 | wantErr: false,
46 | buildJSONArg: func() map[string]interface{} {
47 | return map[string]interface{}{
48 | "type": "jsonpath",
49 | "name": "filter-name",
50 | "path": "$.path",
51 | "value": "value",
52 | }
53 | },
54 | },
55 | {
56 | name: "Build filte with jsonpath type with regexp match",
57 | wantErr: false,
58 | buildJSONArg: func() map[string]interface{} {
59 | return map[string]interface{}{
60 | "type": "jsonpath",
61 | "name": "filter-name",
62 | "path": "$.path",
63 | "regexp": ".*",
64 | }
65 | },
66 | },
67 | {
68 | name: "Build filte with label type",
69 | wantErr: false,
70 | buildJSONArg: func() map[string]interface{} {
71 | return map[string]interface{}{
72 | "type": "labels",
73 | "name": "filter-name",
74 | "labels": map[string]string{
75 | "version": "v1",
76 | },
77 | }
78 | },
79 | },
80 | {
81 | name: "Build filte with any type",
82 | wantErr: false,
83 | buildJSONArg: func() map[string]interface{} {
84 | return map[string]interface{}{
85 | "type": "any",
86 | "name": "filter-name",
87 | "filters": []string{
88 | "filter-1",
89 | "filter-2",
90 | },
91 | }
92 | },
93 | },
94 | {
95 | name: "Return error when type not exit",
96 | wantErr: true,
97 | buildJSONArg: func() map[string]interface{} {
98 | return map[string]interface{}{}
99 | },
100 | want: nil,
101 | },
102 | {
103 | name: "Return error when type not supported",
104 | wantErr: true,
105 | buildJSONArg: func() map[string]interface{} {
106 | return map[string]interface{}{
107 | "type": "not-supported",
108 | }
109 | },
110 | },
111 | }
112 | for _, tt := range tests {
113 | t.Run(tt.name, func(t *testing.T) {
114 | l := logger.New(nil)
115 | factory := filter.NewFactory(l)
116 | got, err := factory.Build(tt.buildJSONArg(), &mocks.Service{}, &kube.Kube{})
117 | assert.Equalf(t, tt.wantErr, err != nil, "f.Build() error = %v, wantErr %v", err, tt.wantErr)
118 |
119 | if tt.wantErr == false {
120 | message := fmt.Sprintf("Returned object does not implement Filter interface")
121 | assert.Implements(t, (*filter.Filter)(nil), got, message)
122 | }
123 | })
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/pkg/filter/filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "github.com/olegsu/iris/pkg/logger"
5 | )
6 |
7 | const (
8 | TypeReason = "reason"
9 | TypeNamespace = "namespace"
10 | TypeJSONPath = "jsonpath"
11 | TypeLabel = "labels"
12 | TypeAny = "any"
13 | )
14 |
15 | type Filter interface {
16 | GetName() string
17 | Apply(interface{}) (bool, error)
18 | GetType() string
19 | }
20 |
21 | func ApplyFilter(f Filter, obj interface{}) (bool, error) {
22 | return f.Apply(obj)
23 | }
24 |
25 | type baseFilter struct {
26 | Name string `yaml:"name"`
27 | Type string `yaml:"type"`
28 | logger logger.Logger
29 | }
30 |
31 | func (f *baseFilter) GetName() string {
32 | return f.Name
33 | }
34 |
35 | func (f *baseFilter) GetType() string {
36 | return f.Type
37 | }
38 |
--------------------------------------------------------------------------------
/pkg/filter/filter_apply_test.go:
--------------------------------------------------------------------------------
1 | package filter_test
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/mock"
9 |
10 | "github.com/olegsu/iris/pkg/filter"
11 | "github.com/olegsu/iris/pkg/logger"
12 | "github.com/stretchr/testify/assert"
13 |
14 | mocks "github.com/olegsu/iris/pkg/filter/mocks"
15 |
16 | kubeMock "github.com/olegsu/iris/pkg/kube/mocks"
17 | )
18 |
19 | func Test_Filter_Apply(t *testing.T) {
20 | type args struct {
21 | data interface{}
22 | }
23 | tests := []struct {
24 | name string
25 | getFilter func(string) filter.Filter
26 | args args
27 | expectedResult bool
28 | expectedErr bool
29 | filterType string
30 | }{
31 | {
32 | filterType: filter.TypeAny,
33 | name: "Should success when at least one inner filter is success",
34 | getFilter: func(t string) filter.Filter {
35 | // build the actual tested filter
36 | factory := filter.NewFactory(logger.New(nil))
37 |
38 | mockedFactory := &mocks.Factory{}
39 | service := &mocks.Service{}
40 | mockedFilter1 := &mocks.Filter{}
41 | mockedFilter2 := &mocks.Filter{}
42 |
43 | anyFilter, _ := factory.Build(generateFilterJSON(t, "TEST", []string{"JSON-1", "JSON-2"}), service, nil)
44 | mockedFilter1.On("Apply", mock.Anything).Return(true, nil)
45 | mockedFilter2.On("Apply", mock.Anything).Return(false, nil)
46 |
47 | service.On("GetFilterByName", "JSON-1").Return(mockedFilter1, nil)
48 | service.On("GetFilterByName", "JSON-2").Return(mockedFilter2, nil)
49 |
50 | mockedFactory.On("Build", generateFilterJSON("jsonpath", "JSON-1", []string{"JSON-1"}), service, nil).Return(mockedFilter1, nil)
51 | mockedFactory.On("Build", generateFilterJSON("jsonpath", "JSON-2", []string{"JSON-2"}), service, nil).Return(mockedFilter2, nil)
52 |
53 | return anyFilter
54 | },
55 | args: args{},
56 | expectedResult: true,
57 | expectedErr: false,
58 | },
59 | {
60 | filterType: filter.TypeAny,
61 | name: "Should fail when GetFilterByName returns an error",
62 | getFilter: func(t string) filter.Filter {
63 | // build the actual tested filter
64 | factory := filter.NewFactory(logger.New(nil))
65 |
66 | service := &mocks.Service{}
67 |
68 | anyFilter, _ := factory.Build(generateFilterJSON(t, "TEST", []string{"JSON-1", "JSON-2"}), service, nil)
69 |
70 | service.On("GetFilterByName", "JSON-1").Return(nil, errors.New("Error!"))
71 |
72 | return anyFilter
73 | },
74 | args: args{},
75 | expectedResult: false,
76 | expectedErr: true,
77 | },
78 | {
79 | filterType: filter.TypeAny,
80 | name: "Should fail when filter.Apply returns an error",
81 | getFilter: func(t string) filter.Filter {
82 | // build the actual tested filter
83 | factory := filter.NewFactory(logger.New(nil))
84 |
85 | mockedFactory := &mocks.Factory{}
86 | service := &mocks.Service{}
87 | mockedFilter := &mocks.Filter{}
88 |
89 | anyFilter, _ := factory.Build(generateFilterJSON(t, "TEST", []string{"JSON-1", "JSON-2"}), service, nil)
90 | mockedFilter.On("Apply", mock.Anything).Return(false, errors.New("Error!"))
91 |
92 | service.On("GetFilterByName", "JSON-1").Return(mockedFilter, nil)
93 |
94 | mockedFactory.On("Build", generateFilterJSON("jsonpath", "JSON-1", []string{"JSON-1"}), service, nil).Return(mockedFilter, nil)
95 |
96 | return anyFilter
97 | },
98 | args: args{},
99 | expectedResult: false,
100 | expectedErr: true,
101 | },
102 | {
103 | name: "Should success mathcing json by exact value",
104 | filterType: filter.TypeJSONPath,
105 | getFilter: func(t string) filter.Filter {
106 | // build the actual tested filter
107 | factory := filter.NewFactory(logger.New(nil))
108 |
109 | service := &mocks.Service{}
110 |
111 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
112 | "value": "Value",
113 | }), service, nil)
114 |
115 | return jsonpathFilter
116 | },
117 | args: args{
118 | data: map[string]interface{}{
119 | "root": "Value",
120 | },
121 | },
122 | expectedResult: true,
123 | expectedErr: false,
124 | },
125 | {
126 | name: "Should failed mathcing json by exact value not match",
127 | filterType: filter.TypeJSONPath,
128 | getFilter: func(t string) filter.Filter {
129 | // build the actual tested filter
130 | factory := filter.NewFactory(logger.New(nil))
131 |
132 | service := &mocks.Service{}
133 |
134 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
135 | "value": "value",
136 | }), service, nil)
137 |
138 | return jsonpathFilter
139 | },
140 | args: args{
141 | data: map[string]interface{}{
142 | "root": "Value",
143 | },
144 | },
145 | expectedResult: false,
146 | expectedErr: false,
147 | },
148 | {
149 | name: "Should fail mathcing json by exact value",
150 | filterType: filter.TypeJSONPath,
151 | getFilter: func(t string) filter.Filter {
152 | // build the actual tested filter
153 | factory := filter.NewFactory(logger.New(nil))
154 |
155 | service := &mocks.Service{}
156 |
157 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{}), service, nil)
158 |
159 | return jsonpathFilter
160 | },
161 | args: args{
162 | data: map[string]interface{}{
163 | "root": "not-mached",
164 | },
165 | },
166 | expectedResult: false,
167 | expectedErr: false,
168 | },
169 | {
170 | name: "Should success mathcing any value using regexp",
171 | filterType: filter.TypeJSONPath,
172 | getFilter: func(t string) filter.Filter {
173 | // build the actual tested filter
174 | factory := filter.NewFactory(logger.New(nil))
175 |
176 | service := &mocks.Service{}
177 |
178 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
179 | "regexp": ".", // match anything
180 | }), service, nil)
181 |
182 | return jsonpathFilter
183 | },
184 | args: args{
185 | data: map[string]interface{}{
186 | "root": "Value",
187 | },
188 | },
189 | expectedResult: true,
190 | expectedErr: false,
191 | },
192 | {
193 | name: "Should fail matching value using regexp",
194 | filterType: filter.TypeJSONPath,
195 | getFilter: func(t string) filter.Filter {
196 | // build the actual tested filter
197 | factory := filter.NewFactory(logger.New(nil))
198 |
199 | service := &mocks.Service{}
200 |
201 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
202 | "regexp": "VALUE", // match anything
203 | }), service, nil)
204 |
205 | return jsonpathFilter
206 | },
207 | args: args{
208 | data: map[string]interface{}{
209 | "root": "Value",
210 | },
211 | },
212 | expectedResult: false,
213 | expectedErr: false,
214 | },
215 | {
216 | name: "Should fail when reading from json failed",
217 | filterType: filter.TypeJSONPath,
218 | getFilter: func(t string) filter.Filter {
219 | // build the actual tested filter
220 | factory := filter.NewFactory(logger.New(nil))
221 |
222 | service := &mocks.Service{}
223 |
224 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
225 | "regexp": "VALUE", // match VALUE
226 | }), service, nil)
227 |
228 | return jsonpathFilter
229 | },
230 | args: args{},
231 | expectedResult: false,
232 | expectedErr: true,
233 | },
234 | {
235 | name: "Should fail when regexp is not valid",
236 | filterType: filter.TypeJSONPath,
237 | getFilter: func(t string) filter.Filter {
238 | // build the actual tested filter
239 | factory := filter.NewFactory(logger.New(nil))
240 |
241 | service := &mocks.Service{}
242 |
243 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
244 | "regexp": "(?:", // regex not valid
245 | }), service, nil)
246 |
247 | return jsonpathFilter
248 | },
249 | args: args{
250 | data: map[string]interface{}{
251 | "root": "Value",
252 | },
253 | },
254 | expectedResult: false,
255 | expectedErr: true,
256 | },
257 | {
258 | name: "Should fail when regexp or value not exist",
259 | filterType: filter.TypeJSONPath,
260 | getFilter: func(t string) filter.Filter {
261 | // build the actual tested filter
262 | factory := filter.NewFactory(logger.New(nil))
263 |
264 | service := &mocks.Service{}
265 |
266 | jsonpathFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{}), service, nil)
267 |
268 | return jsonpathFilter
269 | },
270 | args: args{
271 | data: map[string]interface{}{
272 | "root": "Value",
273 | },
274 | },
275 | expectedResult: false,
276 | expectedErr: false,
277 | },
278 | {
279 | name: "Should success when namespace matched",
280 | filterType: filter.TypeNamespace,
281 | getFilter: func(t string) filter.Filter {
282 | // build the actual tested filter
283 | factory := filter.NewFactory(logger.New(nil))
284 |
285 | service := &mocks.Service{}
286 |
287 | namespaceFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
288 | "namespace": "default",
289 | }), service, nil)
290 |
291 | return namespaceFilter
292 | },
293 | args: args{
294 | data: map[string]interface{}{
295 | "metadata": map[string]interface{}{
296 | "namespace": "default",
297 | },
298 | },
299 | },
300 | expectedResult: true,
301 | expectedErr: false,
302 | },
303 | {
304 | name: "Should fail when namespace not matched",
305 | filterType: filter.TypeNamespace,
306 | getFilter: func(t string) filter.Filter {
307 | // build the actual tested filter
308 | factory := filter.NewFactory(logger.New(nil))
309 |
310 | service := &mocks.Service{}
311 |
312 | namespaceFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
313 | "namespace": "default",
314 | }), service, nil)
315 |
316 | return namespaceFilter
317 | },
318 | args: args{
319 | data: map[string]interface{}{
320 | "metadata": map[string]interface{}{
321 | "namespace": "iris",
322 | },
323 | },
324 | },
325 | expectedResult: false,
326 | expectedErr: false,
327 | },
328 | {
329 | name: "Should success when reason matched",
330 | filterType: filter.TypeReason,
331 | getFilter: func(t string) filter.Filter {
332 | // build the actual tested filter
333 | factory := filter.NewFactory(logger.New(nil))
334 |
335 | service := &mocks.Service{}
336 |
337 | reasonFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
338 | "reason": "Scheduled",
339 | }), service, nil)
340 |
341 | return reasonFilter
342 | },
343 | args: args{
344 | data: map[string]interface{}{
345 | "reason": "Scheduled",
346 | },
347 | },
348 | expectedResult: true,
349 | expectedErr: false,
350 | },
351 | {
352 | name: "Should fail when reason not matched",
353 | filterType: filter.TypeReason,
354 | getFilter: func(t string) filter.Filter {
355 | // build the actual tested filter
356 | factory := filter.NewFactory(logger.New(nil))
357 |
358 | service := &mocks.Service{}
359 |
360 | reasonFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
361 | "reason": "Scheduled",
362 | }), service, nil)
363 |
364 | return reasonFilter
365 | },
366 | args: args{
367 | data: map[string]interface{}{
368 | "reason": "Died",
369 | },
370 | },
371 | expectedResult: false,
372 | expectedErr: false,
373 | },
374 | {
375 | name: "Should success when reason matched",
376 | filterType: filter.TypeLabel,
377 | getFilter: func(t string) filter.Filter {
378 | // build the actual tested filter
379 | factory := filter.NewFactory(logger.New(nil))
380 |
381 | service := &mocks.Service{}
382 | kube := &kubeMock.Kube{}
383 | kube.On("ResourceByLabelsExist", mock.Anything, mock.Anything).Return(true, nil)
384 | reasonFilter, _ := factory.Build(generateFilterJSON(t, "TEST", map[string]interface{}{
385 | "app-version": "v1",
386 | }), service, kube)
387 |
388 | return reasonFilter
389 | },
390 | args: args{
391 | data: map[string]interface{}{
392 | "app-version": "v1",
393 | },
394 | },
395 | expectedResult: true,
396 | expectedErr: false,
397 | },
398 | }
399 | for _, tt := range tests {
400 | name := fmt.Sprintf("Filter type: %s .%s", tt.filterType, tt.name)
401 | t.Run(name, func(t *testing.T) {
402 | fil := tt.getFilter(tt.filterType)
403 | got, err := fil.Apply(tt.args.data)
404 | assert.Equalf(t, tt.expectedErr, err != nil, "filter.Apply() error = %v, expectedErr %v", err, tt.expectedErr)
405 |
406 | if tt.expectedErr == false {
407 | assert.Equalf(t, tt.expectedResult, got, "filter.Apply() = %v, want %v", got, tt.expectedResult)
408 | }
409 | })
410 | }
411 | }
412 |
--------------------------------------------------------------------------------
/pkg/filter/filter_test.go:
--------------------------------------------------------------------------------
1 | package filter_test
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/olegsu/iris/pkg/filter"
7 | "github.com/olegsu/iris/pkg/filter/mocks"
8 | "github.com/stretchr/testify/mock"
9 | )
10 |
11 | func TestApplyFilter(t *testing.T) {
12 | type args struct {
13 | f func() filter.Filter
14 | obj interface{}
15 | }
16 | tests := []struct {
17 | name string
18 | args args
19 | want bool
20 | wantErr bool
21 | }{
22 | {
23 | name: "Should call apply on filter",
24 | args: args{
25 | f: func() filter.Filter {
26 | m := &mocks.Filter{}
27 | m.On("Apply", mock.Anything).Return(true, nil)
28 | return m
29 | },
30 | },
31 | want: true,
32 | wantErr: false,
33 | },
34 | }
35 | for _, tt := range tests {
36 | t.Run(tt.name, func(t *testing.T) {
37 | got, err := filter.ApplyFilter(tt.args.f(), tt.args.obj)
38 | if (err != nil) != tt.wantErr {
39 | t.Errorf("ApplyFilter() error = %v, wantErr %v", err, tt.wantErr)
40 | return
41 | }
42 | if got != tt.want {
43 | t.Errorf("ApplyFilter() = %v, want %v", got, tt.want)
44 | }
45 | })
46 | }
47 | }
48 |
49 | func GenerateJSON() map[string]interface{} {
50 | return map[string]interface{}{
51 | "name": "name",
52 | "type": "jsonpath",
53 | }
54 | }
55 |
56 | // func Test_baseFilter(t *testing.T) {
57 |
58 | // tests := []struct {
59 | // name string
60 | // getFilter func() filter.Filter
61 | // wantName string
62 | // wantType string
63 | // }{
64 | // {
65 | // name: "Get filter name",
66 | // getFilter: func() filter.Filter {
67 | // factory := filter.NewFactory()
68 | // f, _ := factory.Build(GenerateJSON(), &kube.Kube{})
69 | // return f
70 | // },
71 | // wantName: "name",
72 | // wantType: "jsonpath",
73 | // },
74 | // }
75 | // for _, tt := range tests {
76 | // t.Run(tt.name, func(t *testing.T) {
77 | // f := tt.getFilter()
78 | // if got := f.GetName(); got != tt.wantName {
79 | // t.Errorf("GetName() = %v, want %v", got, tt.wantName)
80 | // }
81 |
82 | // if got := f.GetType(); got != tt.wantType {
83 | // t.Errorf("GetName() = %v, want %v", got, tt.wantType)
84 | // }
85 | // })
86 | // }
87 | // }
88 |
89 | func Test_baseFilter_GetType(t *testing.T) {
90 |
91 | tests := []struct {
92 | name string
93 | getFilter func() filter.Filter
94 | want string
95 | }{
96 | {
97 | name: "Get filter name",
98 | getFilter: func() filter.Filter {
99 | f := &mocks.Filter{}
100 | f.On("GetType", mock.Anything).Return("filter-type")
101 | return f
102 | },
103 | want: "filter-type",
104 | },
105 | }
106 | for _, tt := range tests {
107 | t.Run(tt.name, func(t *testing.T) {
108 | f := tt.getFilter()
109 | if got := f.GetType(); got != tt.want {
110 | t.Errorf("GetType() = %v, want %v", got, tt.want)
111 | }
112 | })
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/pkg/filter/helpers_test.go:
--------------------------------------------------------------------------------
1 | package filter_test
2 |
3 | import (
4 | "github.com/olegsu/iris/pkg/filter"
5 | )
6 |
7 | func generateFilterJSON(t string, name string, data interface{}) map[string]interface{} {
8 | var r map[string]interface{}
9 | switch t {
10 | case filter.TypeAny:
11 | r = generateAnyFilter(name, data)
12 | break
13 | case filter.TypeJSONPath:
14 | r = generateJSONPathFilter(name, data)
15 | break
16 | case filter.TypeNamespace:
17 | r = generateNamespaceFilter(name, data)
18 | break
19 | case filter.TypeReason:
20 | r = generateReasonFilter(name, data)
21 | break
22 | case filter.TypeLabel:
23 | r = generateLabelFilter(name, data)
24 | break
25 | }
26 | return r
27 | }
28 |
29 | func generateAnyFilter(name string, data interface{}) map[string]interface{} {
30 | if name == "" {
31 | name = "generated"
32 | }
33 | return map[string]interface{}{
34 | "name": name,
35 | "type": filter.TypeAny,
36 | "filters": data,
37 | }
38 | }
39 |
40 | func generateJSONPathFilter(name string, data interface{}) map[string]interface{} {
41 | var res map[string]interface{}
42 | var path string
43 | if name == "" {
44 | name = "generated"
45 | }
46 |
47 | res = map[string]interface{}{
48 | "name": name,
49 | "type": filter.TypeJSONPath,
50 | }
51 |
52 | path = "$.root"
53 |
54 | casted, ok := data.(map[string]interface{})
55 |
56 | if ok {
57 | if casted["regexp"] != nil {
58 | res["regexp"] = casted["regexp"]
59 | } else if casted["value"] != nil {
60 | res["value"] = casted["value"]
61 | }
62 | }
63 |
64 | res["path"] = path
65 | return res
66 | }
67 |
68 | func generateNamespaceFilter(name string, data interface{}) map[string]interface{} {
69 | var res map[string]interface{}
70 |
71 | res = map[string]interface{}{
72 | "name": name,
73 | "type": filter.TypeNamespace,
74 | }
75 |
76 | casted, ok := data.(map[string]interface{})
77 | if ok {
78 | res["namespace"] = casted["namespace"]
79 | }
80 | return res
81 | }
82 |
83 | func generateReasonFilter(name string, data interface{}) map[string]interface{} {
84 | var res map[string]interface{}
85 |
86 | res = map[string]interface{}{
87 | "name": name,
88 | "type": filter.TypeReason,
89 | }
90 |
91 | casted, ok := data.(map[string]interface{})
92 | if ok {
93 | res["reason"] = casted["reason"]
94 | }
95 | return res
96 | }
97 |
98 | func generateLabelFilter(name string, data interface{}) map[string]interface{} {
99 | var res map[string]interface{}
100 |
101 | res = map[string]interface{}{
102 | "name": name,
103 | "type": filter.TypeLabel,
104 | "lables": data,
105 | }
106 | return res
107 | }
108 |
--------------------------------------------------------------------------------
/pkg/filter/jsonpath_filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "regexp"
5 |
6 | "github.com/olegsu/iris/pkg/logger"
7 | "github.com/yalp/jsonpath"
8 | )
9 |
10 | type jsonPathFilter struct {
11 | baseFilter `yaml:",inline"`
12 | Path string `yaml:"path"`
13 | Value string `yaml:"value"`
14 | Regexp string `yaml:"regexp"`
15 | }
16 |
17 | func (f *jsonPathFilter) Apply(data interface{}) (bool, error) {
18 | path := f.Path
19 | actualValue, err := jsonpath.Read(data, path)
20 | if err != nil {
21 | return false, err
22 | }
23 | if f.Value != "" {
24 | res := applyMatchValueFilter(f.Value, actualValue.(string), f.logger)
25 | return res, nil
26 | } else if f.Regexp != "" {
27 | res, err := applyRegexpFilter(f.Regexp, actualValue.(string), f.logger)
28 | if err != nil {
29 | return false, err
30 | }
31 | return res, nil
32 | } else {
33 | return false, nil
34 | }
35 | }
36 |
37 | func applyRegexpFilter(pattern string, value string, logger logger.Logger) (bool, error) {
38 | match, err := regexp.MatchString(pattern, value)
39 | if err != nil {
40 | return false, err
41 | }
42 | if match == false {
43 | logger.Debug("JSON path does not match to regex", "pattern", pattern, "value", value)
44 | return false, nil
45 | }
46 | logger.Debug("JSON path match to regex", "pattern", pattern, "value", value)
47 | return true, nil
48 | }
49 |
50 | func applyMatchValueFilter(requiredValue string, actualValue string, logger logger.Logger) bool {
51 | if actualValue != requiredValue {
52 | logger.Debug("JSON path does not match", "requiredValue", requiredValue, "actualValue", actualValue)
53 | return false
54 | }
55 | logger.Debug("JSON path match", "requiredValue", requiredValue, "actualValue", actualValue)
56 | return true
57 | }
58 |
--------------------------------------------------------------------------------
/pkg/filter/label_filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import "github.com/olegsu/iris/pkg/kube"
4 |
5 | type labelFilter struct {
6 | baseFilter `yaml:",inline"`
7 | Labels map[string]string `yaml:"labels"`
8 | kube kube.Kube
9 | }
10 |
11 | func (f *labelFilter) Apply(data interface{}) (bool, error) {
12 | return f.kube.ResourceByLabelsExist(data, f.Labels)
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/filter/mocks/Factory.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package mocks
4 |
5 | import filter "github.com/olegsu/iris/pkg/filter"
6 | import kube "github.com/olegsu/iris/pkg/kube"
7 | import mock "github.com/stretchr/testify/mock"
8 |
9 | // Factory is an autogenerated mock type for the Factory type
10 | type Factory struct {
11 | mock.Mock
12 | }
13 |
14 | // Build provides a mock function with given fields: _a0, _a1, _a2
15 | func (_m *Factory) Build(_a0 map[string]interface{}, _a1 filter.Service, _a2 kube.Kube) (filter.Filter, error) {
16 | ret := _m.Called(_a0, _a1, _a2)
17 |
18 | var r0 filter.Filter
19 | if rf, ok := ret.Get(0).(func(map[string]interface{}, filter.Service, kube.Kube) filter.Filter); ok {
20 | r0 = rf(_a0, _a1, _a2)
21 | } else {
22 | if ret.Get(0) != nil {
23 | r0 = ret.Get(0).(filter.Filter)
24 | }
25 | }
26 |
27 | var r1 error
28 | if rf, ok := ret.Get(1).(func(map[string]interface{}, filter.Service, kube.Kube) error); ok {
29 | r1 = rf(_a0, _a1, _a2)
30 | } else {
31 | r1 = ret.Error(1)
32 | }
33 |
34 | return r0, r1
35 | }
36 |
--------------------------------------------------------------------------------
/pkg/filter/mocks/Filter.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package mocks
4 |
5 | import mock "github.com/stretchr/testify/mock"
6 |
7 | // Filter is an autogenerated mock type for the Filter type
8 | type Filter struct {
9 | mock.Mock
10 | }
11 |
12 | // Apply provides a mock function with given fields: _a0
13 | func (_m *Filter) Apply(_a0 interface{}) (bool, error) {
14 | ret := _m.Called(_a0)
15 |
16 | var r0 bool
17 | if rf, ok := ret.Get(0).(func(interface{}) bool); ok {
18 | r0 = rf(_a0)
19 | } else {
20 | r0 = ret.Get(0).(bool)
21 | }
22 |
23 | var r1 error
24 | if rf, ok := ret.Get(1).(func(interface{}) error); ok {
25 | r1 = rf(_a0)
26 | } else {
27 | r1 = ret.Error(1)
28 | }
29 |
30 | return r0, r1
31 | }
32 |
33 | // GetName provides a mock function with given fields:
34 | func (_m *Filter) GetName() string {
35 | ret := _m.Called()
36 |
37 | var r0 string
38 | if rf, ok := ret.Get(0).(func() string); ok {
39 | r0 = rf()
40 | } else {
41 | r0 = ret.Get(0).(string)
42 | }
43 |
44 | return r0
45 | }
46 |
47 | // GetType provides a mock function with given fields:
48 | func (_m *Filter) GetType() string {
49 | ret := _m.Called()
50 |
51 | var r0 string
52 | if rf, ok := ret.Get(0).(func() string); ok {
53 | r0 = rf()
54 | } else {
55 | r0 = ret.Get(0).(string)
56 | }
57 |
58 | return r0
59 | }
60 |
--------------------------------------------------------------------------------
/pkg/filter/mocks/Service.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package mocks
4 |
5 | import filter "github.com/olegsu/iris/pkg/filter"
6 | import mock "github.com/stretchr/testify/mock"
7 |
8 | // Service is an autogenerated mock type for the Service type
9 | type Service struct {
10 | mock.Mock
11 | }
12 |
13 | // GetFilterByName provides a mock function with given fields: _a0
14 | func (_m *Service) GetFilterByName(_a0 string) (filter.Filter, error) {
15 | ret := _m.Called(_a0)
16 |
17 | var r0 filter.Filter
18 | if rf, ok := ret.Get(0).(func(string) filter.Filter); ok {
19 | r0 = rf(_a0)
20 | } else {
21 | if ret.Get(0) != nil {
22 | r0 = ret.Get(0).(filter.Filter)
23 | }
24 | }
25 |
26 | var r1 error
27 | if rf, ok := ret.Get(1).(func(string) error); ok {
28 | r1 = rf(_a0)
29 | } else {
30 | r1 = ret.Error(1)
31 | }
32 |
33 | return r0, r1
34 | }
35 |
--------------------------------------------------------------------------------
/pkg/filter/namespace_filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | type namespaceFilter struct {
4 | baseFilter `yaml:",inline"`
5 | Namespace string
6 | }
7 |
8 | func (f *namespaceFilter) Apply(data interface{}) (bool, error) {
9 | jsonFilter := &jsonPathFilter{
10 | baseFilter: baseFilter{
11 | Name: f.GetName(),
12 | Type: f.GetType(),
13 | logger: f.logger,
14 | },
15 | Path: "$.metadata.namespace",
16 | Value: f.Namespace,
17 | }
18 | return jsonFilter.Apply(data)
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/filter/reason_filter.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | type reasonFilter struct {
4 | baseFilter `yaml:",inline"`
5 | Reason string `yaml:"reason"`
6 | }
7 |
8 | func (f *reasonFilter) Apply(data interface{}) (bool, error) {
9 | jsonFilter := &jsonPathFilter{
10 | baseFilter: baseFilter{
11 | Name: f.GetName(),
12 | Type: f.GetType(),
13 | logger: f.logger,
14 | },
15 | Path: "$.reason",
16 | Value: f.Reason,
17 | }
18 | return jsonFilter.Apply(data)
19 | }
20 |
--------------------------------------------------------------------------------
/pkg/filter/service.go:
--------------------------------------------------------------------------------
1 | package filter
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/olegsu/iris/pkg/kube"
7 | "github.com/olegsu/iris/pkg/logger"
8 | )
9 |
10 | type dal struct {
11 | filters []Filter
12 | logger logger.Logger
13 | }
14 |
15 | // Service is the service of the filter package
16 | type Service interface {
17 | GetFilterByName(string) (Filter, error)
18 | }
19 |
20 | // GetFilterByName - finds a filters if exist
21 | func (d *dal) GetFilterByName(name string) (Filter, error) {
22 | var f Filter
23 | for index := 0; index < len(d.filters); index++ {
24 | filterName := d.filters[index].GetName()
25 | if filterName == name {
26 | f = d.filters[index]
27 | }
28 | }
29 | if f == nil {
30 | return nil, fmt.Errorf("%s filter not found", name)
31 | }
32 | return f, nil
33 | }
34 |
35 | // NewService - creates net Dal from json array of filters
36 | func NewService(factory Factory, filterArray []map[string]interface{}, k kube.Kube, logger logger.Logger) Service {
37 | tempDal := &dal{
38 | filters: []Filter{},
39 | logger: logger,
40 | }
41 | for _, json := range filterArray {
42 | f, _ := factory.Build(json, tempDal, k)
43 | tempDal.filters = append(tempDal.filters, f)
44 | }
45 | return tempDal
46 | }
47 |
48 | // IsFiltersMatched Go over all filters and apply each one on on data
49 | // Return true is the data matched to all the filters
50 | func IsFiltersMatched(service Service, requiredFilters []string, data interface{}, logger logger.Logger) bool {
51 | matched := true
52 | for _, f := range requiredFilters {
53 | var res bool
54 | filter, err := service.GetFilterByName(f)
55 | if err != nil {
56 | logger.Error("Error", "err", err.Error())
57 | matched = false
58 | } else {
59 | res, err = filter.Apply(data)
60 | if err != nil {
61 | logger.Error("Error", "err", err.Error())
62 | matched = false
63 | }
64 | if res == false {
65 | matched = false
66 | }
67 | }
68 | }
69 |
70 | return matched
71 | }
72 |
--------------------------------------------------------------------------------
/pkg/filter/service_test.go:
--------------------------------------------------------------------------------
1 | package filter_test
2 |
3 | import (
4 | "errors"
5 | "reflect"
6 | "testing"
7 |
8 | "github.com/olegsu/iris/pkg/filter"
9 | filterMock "github.com/olegsu/iris/pkg/filter/mocks"
10 | "github.com/olegsu/iris/pkg/kube"
11 | kubeMock "github.com/olegsu/iris/pkg/kube/mocks"
12 | "github.com/olegsu/iris/pkg/logger"
13 | "github.com/stretchr/testify/mock"
14 | )
15 |
16 | func generateFilterAsJSONArray(len int) []map[string]interface{} {
17 | res := []map[string]interface{}{}
18 | for i := 0; i < len; i++ {
19 | toAdd := map[string]interface{}{
20 | "type": "jsonpath",
21 | }
22 | res = append(res, toAdd)
23 | }
24 | return res
25 | }
26 |
27 | func TestNewService(t *testing.T) {
28 | type args struct {
29 | factory *filterMock.Factory
30 | filterArray []map[string]interface{}
31 | kube kube.Kube
32 | service filter.Service
33 | }
34 |
35 | tests := []struct {
36 | name string
37 | args args
38 | callCount int
39 | }{
40 | {
41 | name: "Create service with no filters",
42 | args: args{
43 | factory: &filterMock.Factory{},
44 | kube: &kubeMock.Kube{},
45 | filterArray: generateFilterAsJSONArray(0),
46 | },
47 | callCount: 0,
48 | },
49 | {
50 | name: "Create service one filter",
51 | args: args{
52 | factory: &filterMock.Factory{},
53 | kube: &kubeMock.Kube{},
54 | filterArray: generateFilterAsJSONArray(1),
55 | },
56 | callCount: 1,
57 | },
58 | }
59 | for _, tt := range tests {
60 | t.Run(tt.name, func(t *testing.T) {
61 | l := logger.New(nil)
62 | if len(tt.args.filterArray) > 0 {
63 | tt.args.factory.On("Build", tt.args.filterArray[0], mock.Anything, tt.args.kube).Return(nil, nil)
64 | }
65 | filter.NewService(tt.args.factory, tt.args.filterArray, tt.args.kube, l)
66 | tt.args.factory.AssertNumberOfCalls(t, "Build", tt.callCount)
67 | })
68 | }
69 | }
70 |
71 | func TestIsFiltersMatched(t *testing.T) {
72 | type args struct {
73 | getServiceFn func() filter.Service
74 | requiredFilters []string
75 | data interface{}
76 | }
77 | tests := []struct {
78 | name string
79 | args args
80 | want bool
81 | }{
82 | {
83 | name: "Return true when no filters passed",
84 | args: args{
85 | getServiceFn: func() filter.Service {
86 | return nil
87 | },
88 | requiredFilters: []string{},
89 | data: nil,
90 | },
91 | want: true,
92 | },
93 | {
94 | name: "Return true when all filters return truly value",
95 | args: args{
96 | getServiceFn: func() filter.Service {
97 | s := &filterMock.Service{}
98 | f := &filterMock.Filter{}
99 | f.
100 | On("Apply", mock.Anything).
101 | Return(true, nil)
102 | s.
103 | On("GetFilterByName", mock.Anything).
104 | Return(f, nil)
105 | return s
106 | },
107 | requiredFilters: []string{"filter-name"},
108 | data: nil,
109 | },
110 | want: true,
111 | },
112 | {
113 | name: "Return false when at least one filter return falsy value",
114 | args: args{
115 | getServiceFn: func() filter.Service {
116 | s := &filterMock.Service{}
117 | f := &filterMock.Filter{}
118 | f.
119 | On("Apply", mock.Anything).
120 | Return(false, nil)
121 |
122 | s.
123 | On("GetFilterByName", mock.Anything).
124 | Return(f, nil)
125 | return s
126 | },
127 | requiredFilters: []string{
128 | "filter-success",
129 | "filter-failed",
130 | },
131 | data: nil,
132 | },
133 | want: false,
134 | },
135 | {
136 | name: "Return false when filter returns an error",
137 | args: args{
138 | getServiceFn: func() filter.Service {
139 | s := &filterMock.Service{}
140 | f := &filterMock.Filter{}
141 | f.
142 | On("Apply", mock.Anything).
143 | Return(false, errors.New("Test!"))
144 |
145 | s.
146 | On("GetFilterByName", mock.Anything).
147 | Return(f, nil)
148 | return s
149 | },
150 | requiredFilters: []string{
151 | "filter-success",
152 | "filter-failed",
153 | },
154 | data: nil,
155 | },
156 | want: false,
157 | },
158 | {
159 | name: "Return false getFilterByName returns an error",
160 | args: args{
161 | getServiceFn: func() filter.Service {
162 | s := &filterMock.Service{}
163 |
164 | s.
165 | On("GetFilterByName", mock.Anything).
166 | Return(nil, errors.New("Test!"))
167 | return s
168 | },
169 | requiredFilters: []string{
170 | "filter-failed",
171 | },
172 | data: nil,
173 | },
174 | want: false,
175 | },
176 | }
177 | for _, tt := range tests {
178 | t.Run(tt.name, func(t *testing.T) {
179 | l := logger.New(nil)
180 | got := filter.IsFiltersMatched(tt.args.getServiceFn(), tt.args.requiredFilters, tt.args.data, l)
181 | if got != tt.want {
182 | t.Errorf("IsFiltersMatched() = %v, want %v", got, tt.want)
183 | }
184 | })
185 | }
186 | }
187 |
188 | func Test_dal_GetFilterByName(t *testing.T) {
189 | type fields struct {
190 | filters []filter.Filter
191 | }
192 | type args struct {
193 | name string
194 | }
195 | tests := []struct {
196 | name string
197 | fields fields
198 | args args
199 | want func(string) filter.Filter
200 | wantErr bool
201 | getFilterJSONArray func() []map[string]interface{}
202 | getFactory func(filter.Filter) filter.Factory
203 | }{
204 | {
205 | name: "Should find filter when exist",
206 | args: args{
207 | name: "filter-found",
208 | },
209 | want: func(name string) filter.Filter {
210 | f := &filterMock.Filter{}
211 | f.On("GetName", mock.Anything).Return(name)
212 | return f
213 | },
214 | getFilterJSONArray: func() []map[string]interface{} {
215 | return generateFilterAsJSONArray(1)
216 | },
217 | getFactory: func(ret filter.Filter) filter.Factory {
218 | factoryMock := &filterMock.Factory{}
219 | factoryMock.On("Build", mock.Anything, mock.Anything, mock.Anything).Return(ret, nil)
220 | return factoryMock
221 | },
222 | },
223 | {
224 | name: "Should return error when filters are nil",
225 | want: func(name string) filter.Filter {
226 | return nil
227 | },
228 | getFilterJSONArray: func() []map[string]interface{} {
229 | return nil
230 | },
231 | getFactory: func(ret filter.Filter) filter.Factory {
232 | factoryMock := &filterMock.Factory{}
233 | factoryMock.On("Build", mock.Anything, mock.Anything, mock.Anything).Return(nil, errors.New("Error"))
234 | return factoryMock
235 | },
236 | wantErr: true,
237 | },
238 | }
239 | for _, tt := range tests {
240 | t.Run(tt.name, func(t *testing.T) {
241 | l := logger.New(nil)
242 | want := tt.want(tt.args.name)
243 | factoryMock := tt.getFactory(want)
244 | filterArray := tt.getFilterJSONArray()
245 | d := filter.NewService(factoryMock, filterArray, &kubeMock.Kube{}, l)
246 | got, err := d.GetFilterByName(tt.args.name)
247 | if (err != nil) != tt.wantErr {
248 | t.Errorf("dal.GetFilterByName() error = %v, wantErr %v", err, tt.wantErr)
249 | return
250 | }
251 |
252 | if !reflect.DeepEqual(got, want) {
253 | t.Errorf("dal.GetFilterByName() = %v, want %v", got, want)
254 | }
255 | })
256 | }
257 | }
258 |
--------------------------------------------------------------------------------
/pkg/kube/kube.go:
--------------------------------------------------------------------------------
1 | package kube
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/olegsu/iris/pkg/logger"
9 | v1 "k8s.io/api/core/v1"
10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11 | "k8s.io/apimachinery/pkg/fields"
12 | "k8s.io/client-go/kubernetes"
13 | "k8s.io/client-go/rest"
14 | "k8s.io/client-go/tools/cache"
15 | "k8s.io/client-go/tools/clientcmd"
16 | )
17 |
18 | type WatchFn func(obj interface{})
19 |
20 | type Kube interface {
21 | Watch(WatchFn)
22 | ResourceByLabelsExist(interface{}, map[string]string) (bool, error)
23 | }
24 |
25 | type kube struct {
26 | Clientset *kubernetes.Clientset
27 | logger logger.Logger
28 | }
29 |
30 | func (k *kube) Watch(watchFn WatchFn) {
31 | k.logger.Debug("Starting kuberntes watcher")
32 | watchlist := cache.NewListWatchFromClient(k.Clientset.Core().RESTClient(), "events", metav1.NamespaceAll, fields.Everything())
33 | _, controller := cache.NewInformer(
34 | watchlist,
35 | &v1.Event{},
36 | time.Second*0,
37 | cache.ResourceEventHandlerFuncs{
38 | AddFunc: func(obj interface{}) {
39 | watchFn(obj)
40 | },
41 | },
42 | )
43 | stop := make(chan struct{})
44 | go controller.Run(stop)
45 | for {
46 | time.Sleep(time.Second)
47 | }
48 | }
49 |
50 | func (k *kube) ResourceByLabelsExist(obj interface{}, labels map[string]string) (bool, error) {
51 | selector := ""
52 | for k, v := range labels {
53 | if selector == "" {
54 | selector = fmt.Sprintf("%s=%s", k, v)
55 | } else {
56 | selector = fmt.Sprintf("%s,%s=%s", selector, k, v)
57 | }
58 | }
59 | var ev *v1.Event
60 | bytes, err := json.Marshal(obj)
61 | json.Unmarshal(bytes, &ev)
62 | opt := metav1.ListOptions{
63 | LabelSelector: selector,
64 | }
65 | pods, err := k.Clientset.CoreV1().Pods(ev.InvolvedObject.Namespace).List(opt)
66 | if err != nil {
67 | return false, err
68 | }
69 | return len(pods.Items) > 0, nil
70 | }
71 |
72 | func NewKubeManager(kubeconfig string, incluster bool, logger logger.Logger) Kube {
73 | k := &kube{
74 | logger: logger,
75 | }
76 | var config *rest.Config
77 | var err error
78 | if incluster == true {
79 | logger.Debug("Running from in cluster")
80 | config, err = rest.InClusterConfig()
81 | } else {
82 | logger.Debug("Connecting to cluster from kubeconfig", "path", kubeconfig)
83 | config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
84 | }
85 | if err != nil {
86 | panic(err.Error())
87 | }
88 |
89 | cs, err := kubernetes.NewForConfig(config)
90 | if err != nil {
91 | panic(err.Error())
92 | }
93 | k.Clientset = cs
94 | return k
95 | }
96 |
--------------------------------------------------------------------------------
/pkg/kube/mocks/Kube.go:
--------------------------------------------------------------------------------
1 | // Code generated by mockery v1.0.0. DO NOT EDIT.
2 |
3 | package mocks
4 |
5 | import kube "github.com/olegsu/iris/pkg/kube"
6 | import mock "github.com/stretchr/testify/mock"
7 |
8 | // Kube is an autogenerated mock type for the Kube type
9 | type Kube struct {
10 | mock.Mock
11 | }
12 |
13 | // ResourceByLabelsExist provides a mock function with given fields: _a0, _a1
14 | func (_m *Kube) ResourceByLabelsExist(_a0 interface{}, _a1 map[string]string) (bool, error) {
15 | ret := _m.Called(_a0, _a1)
16 |
17 | var r0 bool
18 | if rf, ok := ret.Get(0).(func(interface{}, map[string]string) bool); ok {
19 | r0 = rf(_a0, _a1)
20 | } else {
21 | r0 = ret.Get(0).(bool)
22 | }
23 |
24 | var r1 error
25 | if rf, ok := ret.Get(1).(func(interface{}, map[string]string) error); ok {
26 | r1 = rf(_a0, _a1)
27 | } else {
28 | r1 = ret.Error(1)
29 | }
30 |
31 | return r0, r1
32 | }
33 |
34 | // GetIRISConfigmap provides a mock function with given fields: _a0, _a1
35 | func (_m *Kube) GetIRISConfigmap(_a0 string, _a1 string) ([]byte, error) {
36 | ret := _m.Called(_a0, _a1)
37 |
38 | var r0 []byte
39 | if rf, ok := ret.Get(0).(func(string, string) []byte); ok {
40 | r0 = rf(_a0, _a1)
41 | } else {
42 | if ret.Get(0) != nil {
43 | r0 = ret.Get(0).([]byte)
44 | }
45 | }
46 |
47 | var r1 error
48 | if rf, ok := ret.Get(1).(func(string, string) error); ok {
49 | r1 = rf(_a0, _a1)
50 | } else {
51 | r1 = ret.Error(1)
52 | }
53 |
54 | return r0, r1
55 | }
56 |
57 | // Watch provides a mock function with given fields: _a0
58 | func (_m *Kube) Watch(_a0 kube.WatchFn) {
59 | _m.Called(_a0)
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/logger/logger.go:
--------------------------------------------------------------------------------
1 | package logger
2 |
3 | import (
4 | log "github.com/inconshreveable/log15"
5 | )
6 |
7 | type (
8 | Logger interface {
9 | log.Logger
10 | }
11 |
12 | Options struct {
13 | Command string
14 | Verbose bool
15 | }
16 | )
17 |
18 | func New(opt *Options) Logger {
19 | var l Logger
20 | lvl := log.LvlError
21 | if opt != nil {
22 | l = log.New(log.Ctx{
23 | "Command": opt.Command,
24 | })
25 | if opt.Verbose {
26 | lvl = log.LvlDebug
27 | }
28 | } else {
29 | l = log.New()
30 | }
31 | handlers := []log.Handler{}
32 | verboseHandler := log.LvlFilterHandler(lvl, log.StdoutHandler)
33 | handlers = append(handlers, verboseHandler)
34 | l.SetHandler(log.MultiHandler(handlers...))
35 | return l
36 | }
37 |
--------------------------------------------------------------------------------
/pkg/reader/reader.go:
--------------------------------------------------------------------------------
1 | package reader
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/olegsu/iris/pkg/kube"
7 | "github.com/olegsu/iris/pkg/util/reader/file"
8 | )
9 |
10 | type (
11 | // IRISProcessor knows how to process iris configs
12 | IRISProcessor interface {
13 | Process() ([]byte, error)
14 | }
15 |
16 | processor struct {
17 | fileReader file.FileReader
18 | args []string
19 | }
20 | )
21 |
22 | // Process - start processing the iris confi
23 | func (i *processor) Process() ([]byte, error) {
24 | if i.fileReader != nil {
25 | return i.fileReader.Read(i.args[0])
26 | } else {
27 | return nil, fmt.Errorf("No reader found")
28 | }
29 | }
30 |
31 | // NewProcessor - crete new processor based on the len of the args
32 | func NewProcessor(args []string, k kube.Kube) (IRISProcessor, error) {
33 | if len(args) == 1 {
34 | return &processor{
35 | fileReader: file.NewFileReader(k),
36 | args: args,
37 | }, nil
38 | } else {
39 | return nil, fmt.Errorf("Could not create iris processor, arguments not match")
40 | }
41 | }
42 |
43 | // Process - execte processor
44 | func Process(processor IRISProcessor) ([]byte, error) {
45 | return processor.Process()
46 | }
47 |
--------------------------------------------------------------------------------
/pkg/reader/reader_test.go:
--------------------------------------------------------------------------------
1 | package reader
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/olegsu/iris/pkg/kube"
8 |
9 | "github.com/olegsu/iris/pkg/util/reader/file"
10 | )
11 |
12 | type mockFileReader struct{}
13 |
14 | func (m *mockFileReader) Read(path string) ([]byte, error) {
15 | return []byte{}, nil
16 | }
17 |
18 | type mockKube struct{}
19 |
20 | func (m *mockKube) Watch(fn kube.WatchFn) {
21 |
22 | }
23 |
24 | func (m *mockKube) ResourceByLabelsExist(obj interface{}, labels map[string]string) (bool, error) {
25 | return true, nil
26 | }
27 |
28 | func Test_processor_Process(t *testing.T) {
29 |
30 | type fields struct {
31 | fileReader file.FileReader
32 | args []string
33 | }
34 | tests := []struct {
35 | name string
36 | fields fields
37 | want []byte
38 | wantErr bool
39 | }{
40 | {
41 | name: "Should process from file",
42 | fields: fields{
43 | fileReader: &mockFileReader{},
44 | args: []string{
45 | "path/to/file",
46 | },
47 | },
48 | want: []byte{},
49 | wantErr: false,
50 | },
51 | {
52 | name: "Should return error when no readers found ",
53 | fields: fields{
54 | args: []string{},
55 | },
56 | want: nil,
57 | wantErr: true,
58 | },
59 | }
60 | for _, tt := range tests {
61 | t.Run(tt.name, func(t *testing.T) {
62 | i := &processor{
63 | fileReader: tt.fields.fileReader,
64 | args: tt.fields.args,
65 | }
66 | got, err := i.Process()
67 | if (err != nil) != tt.wantErr {
68 | t.Errorf("processor.Process() error = %v, wantErr %v", err, tt.wantErr)
69 | return
70 | }
71 | if !reflect.DeepEqual(got, tt.want) {
72 | t.Errorf("processor.Process() = %v, want %v", got, tt.want)
73 | }
74 | })
75 | }
76 | }
77 |
78 | func TestNewProcessor(t *testing.T) {
79 | type args struct {
80 | args []string
81 | obj kube.Kube
82 | }
83 | tests := []struct {
84 | name string
85 | args args
86 | want IRISProcessor
87 | wantErr bool
88 | }{
89 | {
90 | name: "Should create processor with file reader when given input len=1",
91 | args: args{
92 | args: []string{"path/to/file"},
93 | obj: &mockKube{},
94 | },
95 | want: &processor{
96 | args: []string{"path/to/file"},
97 | fileReader: file.NewFileReader(&mockKube{}),
98 | },
99 | wantErr: false,
100 | },
101 | {
102 | name: "Should return an error when args len is not 1 or 2",
103 | args: args{
104 | args: []string{},
105 | obj: &mockKube{},
106 | },
107 | want: nil,
108 | wantErr: true,
109 | },
110 | }
111 | for _, tt := range tests {
112 | t.Run(tt.name, func(t *testing.T) {
113 | got, err := NewProcessor(tt.args.args, tt.args.obj)
114 | if (err != nil) != tt.wantErr {
115 | t.Errorf("NewProcessor() error = %v, wantErr %v", err, tt.wantErr)
116 | return
117 | }
118 | if !reflect.DeepEqual(got, tt.want) {
119 | t.Errorf("NewProcessor() = %v, want %v", got, tt.want)
120 | }
121 | })
122 | }
123 | }
124 |
125 | type mockIRISProcessor struct{}
126 |
127 | func (m *mockIRISProcessor) Process() ([]byte, error) {
128 | return []byte{}, nil
129 | }
130 |
131 | func TestProcess(t *testing.T) {
132 | type args struct {
133 | processor IRISProcessor
134 | }
135 | tests := []struct {
136 | name string
137 | args args
138 | want []byte
139 | wantErr bool
140 | }{
141 | {
142 | name: "Should call process on given processor",
143 | args: args{
144 | processor: &mockIRISProcessor{},
145 | },
146 | want: []byte{},
147 | wantErr: false,
148 | },
149 | }
150 | for _, tt := range tests {
151 | t.Run(tt.name, func(t *testing.T) {
152 | got, err := Process(tt.args.processor)
153 | if (err != nil) != tt.wantErr {
154 | t.Errorf("Process() error = %v, wantErr %v", err, tt.wantErr)
155 | return
156 | }
157 | if !reflect.DeepEqual(got, tt.want) {
158 | t.Errorf("Process() = %v, want %v", got, tt.want)
159 | }
160 | })
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/pkg/server/server.go:
--------------------------------------------------------------------------------
1 | package server
2 |
3 | import (
4 | "github.com/gin-gonic/gin"
5 | "github.com/olegsu/iris/pkg/logger"
6 | )
7 |
8 | func StartServer(logger logger.Logger) {
9 | logger.Debug("Server started")
10 | r := gin.Default()
11 | r.Run()
12 | }
13 |
--------------------------------------------------------------------------------
/pkg/util/reader/file/file.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "io/ioutil"
5 |
6 | "github.com/olegsu/iris/pkg/kube"
7 | )
8 |
9 | // FileReader
10 | type FileReader interface {
11 | Read(string) ([]byte, error)
12 | }
13 |
14 | type reader struct {
15 | kube kube.Kube
16 | }
17 |
18 | // Read - reads the actual file from path
19 | func (r *reader) Read(path string) ([]byte, error) {
20 | return ioutil.ReadFile(path)
21 | }
22 |
23 | // NewFileReader - creates file reader
24 | func NewFileReader(k kube.Kube) FileReader {
25 | return &reader{
26 | kube: k,
27 | }
28 | }
29 |
30 | // ProcessFile - execute filereader with path as input
31 | func ProcessFile(r FileReader, path string) ([]byte, error) {
32 | return r.Read(path)
33 | }
34 |
--------------------------------------------------------------------------------
/pkg/util/reader/file/file_test.go:
--------------------------------------------------------------------------------
1 | package file
2 |
3 | import (
4 | "fmt"
5 | "reflect"
6 | "testing"
7 |
8 | kubeMock "github.com/olegsu/iris/pkg/kube/mocks"
9 | )
10 |
11 | type mockFileReaderSuccess struct {
12 | FileReader
13 | }
14 |
15 | func (m *mockFileReaderSuccess) Read(string) ([]byte, error) {
16 | return []byte{}, nil
17 | }
18 |
19 | type mockFileReaderNotFound struct {
20 | FileReader
21 | }
22 |
23 | func (m *mockFileReaderNotFound) Read(path string) ([]byte, error) {
24 | return nil, fmt.Errorf("%s not found", path)
25 | }
26 |
27 | func TestProcessFile(t *testing.T) {
28 | type args struct {
29 | r FileReader
30 | path string
31 | }
32 | tests := []struct {
33 | name string
34 | args args
35 | want []byte
36 | wantErr bool
37 | }{
38 | {
39 | name: "Should return file content",
40 | args: args{
41 | r: &mockFileReaderSuccess{},
42 | path: "fath/to/file",
43 | },
44 | want: []byte{},
45 | wantErr: false,
46 | },
47 | {
48 | name: "Should return error if file not found",
49 | args: args{
50 | r: &mockFileReaderNotFound{},
51 | path: "file/not/found",
52 | },
53 | want: nil,
54 | wantErr: true,
55 | },
56 | }
57 | for _, tt := range tests {
58 | t.Run(tt.name, func(t *testing.T) {
59 | got, err := ProcessFile(tt.args.r, tt.args.path)
60 | if (err != nil) != tt.wantErr {
61 | t.Errorf("ProcessFile() error = %v, wantErr %v", err, tt.wantErr)
62 | return
63 | }
64 | if !reflect.DeepEqual(got, tt.want) {
65 | t.Errorf("ProcessFile() = %v, want %v", got, tt.want)
66 | }
67 | })
68 | }
69 | }
70 |
71 | func TestNewFileReader(t *testing.T) {
72 | tests := []struct {
73 | name string
74 | want FileReader
75 | }{
76 | {
77 | name: "Should return Filereader interface",
78 | want: &reader{
79 | kube: &kubeMock.Kube{},
80 | },
81 | },
82 | }
83 |
84 | for _, tt := range tests {
85 | t.Run(tt.name, func(t *testing.T) {
86 | if got := NewFileReader(&kubeMock.Kube{}); !reflect.DeepEqual(got, tt.want) {
87 | t.Errorf("NewFileReader() = %v, want %v", got, tt.want)
88 | }
89 | })
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/pkg/util/util.go:
--------------------------------------------------------------------------------
1 | package util
2 |
3 | import (
4 | "fmt"
5 | "os"
6 |
7 | "github.com/olegsu/iris/pkg/logger"
8 |
9 | yaml "gopkg.in/yaml.v2"
10 | )
11 |
12 | var (
13 | BuildVersion, BuildDate, BuildCommit, BuildBy string
14 | )
15 |
16 | func UnmarshalOrDie(in []byte, out interface{}, logger logger.Logger) {
17 | err := yaml.Unmarshal(in, out)
18 | if err != nil {
19 | logger.Error("Failed to unmarshal", "err", err.Error())
20 | os.Exit(1)
21 | }
22 | }
23 |
24 | func MapToObjectOrDie(m map[string]interface{}, o interface{}, logger logger.Logger) {
25 | b, err := yaml.Marshal(m)
26 | if err != nil {
27 | logger.Error("Failed to marshal", "err", err.Error())
28 | os.Exit(1)
29 | }
30 | err = yaml.Unmarshal(b, o)
31 | if err != nil {
32 | logger.Error("Failed to unmarshal", "err", err.Error())
33 | os.Exit(1)
34 | }
35 | }
36 |
37 | func EchoError(err error) {
38 | fmt.Printf("Error: %s\n", err.Error())
39 | }
40 |
--------------------------------------------------------------------------------