├── .excludelint
├── .excludemetalint
├── .gitignore
├── .gitmodules
├── .metalinter.json
├── .travis.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── Makefile
├── README.md
├── auth
├── auth.go
├── noop.go
├── simple.go
└── simple_test.go
├── config
└── base.yaml
├── generated
├── mocks
│ └── generate.go
└── ui
│ └── statik
│ └── statik.go
├── glide.lock
├── glide.yaml
├── public
├── index.html
└── r2
│ └── v1
│ └── swagger
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── index.html
│ ├── oauth2-redirect.html
│ ├── swagger-ui-bundle.js
│ ├── swagger-ui-bundle.js.map
│ ├── swagger-ui-standalone-preset.js
│ ├── swagger-ui-standalone-preset.js.map
│ ├── swagger-ui.css
│ ├── swagger-ui.css.map
│ ├── swagger-ui.js
│ ├── swagger-ui.js.map
│ └── swagger.json
├── server
├── http
│ ├── options.go
│ └── server.go
└── server.go
├── service
├── health
│ ├── service.go
│ └── service_test.go
├── r2
│ ├── errors.go
│ ├── handler.go
│ ├── io.go
│ ├── routes.go
│ ├── routes_test.go
│ ├── service.go
│ ├── service_test.go
│ └── store
│ │ ├── kv
│ │ ├── options.go
│ │ ├── store.go
│ │ └── store_test.go
│ │ ├── store.go
│ │ ├── store_mock.go
│ │ ├── stub
│ │ └── store.go
│ │ └── update_options.go
└── service.go
├── services
└── r2ctl
│ ├── config
│ ├── r2ctl.go
│ └── server.go
│ └── main
│ └── main.go
├── tools.json
└── ui
├── .env
├── .eslintrc
├── .gitignore
├── .nvmrc
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── components
│ ├── HelpTooltip.js
│ ├── MappingRuleEditor.js
│ ├── MappingRulesTable.js
│ ├── PolicyEditor.js
│ ├── RollupRuleEditor.js
│ ├── RollupRulesTable.js
│ └── TableActions.js
├── hocs
│ ├── api.js
│ ├── filter.js
│ ├── index.js
│ └── promiseState.js
├── index.css
├── index.js
├── pages
│ ├── Namespace.js
│ └── Namespaces.js
└── utils
│ ├── helpText.js
│ ├── index.js
│ └── index.test.js
└── yarn.lock
/.excludelint:
--------------------------------------------------------------------------------
1 | (vendor/)
2 | (generated/)
3 | (_mock.go)
4 | (mock.go)
5 |
--------------------------------------------------------------------------------
/.excludemetalint:
--------------------------------------------------------------------------------
1 | vendor/
2 | generated/
3 | _mock.go
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.out
2 | *.test
3 | *.xml
4 | *.swp
5 | .idea/
6 | .vscode/
7 | *.iml
8 | *.ipr
9 | *.iws
10 | *.cov
11 | test.log
12 |
13 | # glide manages this
14 | vendor/
15 |
16 | # Build binaries
17 | bin/
18 |
19 | # Debug binaries
20 | debug
21 | debug.test
22 |
23 | # retool tools
24 | _tools/
25 |
26 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule ".ci/uber-licence"]
2 | path = .ci/uber-licence
3 | url = https://github.com/uber/uber-licence
4 | [submodule ".ci"]
5 | path = .ci
6 | url = https://github.com/m3db/ci-scripts.git
7 |
--------------------------------------------------------------------------------
/.metalinter.json:
--------------------------------------------------------------------------------
1 | {
2 | "Enable":
3 | [ "deadcode"
4 | , "varcheck"
5 | , "structcheck"
6 | , "goconst"
7 | , "ineffassign"
8 | , "unconvert"
9 | , "misspell"
10 | , "unparam"
11 | , "megacheck" ],
12 | "Deadline": "3m",
13 | "EnableGC": true
14 | }
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - "1.9.x"
4 | - "1.10.x"
5 | install: make install-ci
6 | env:
7 | # Set higher timeouts and package name for travis
8 | - TEST_TIMEOUT_SCALE=20 PACKAGE=github.com/m3db/m3ctl NPROC=2
9 | sudo: required
10 | dist: trusty
11 | script:
12 | - make all
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | Changelog
2 | =========
3 |
4 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing
2 | ============
3 |
4 | We'd love your help making m3ctl great!
5 |
6 | ## Getting Started
7 |
8 | m3ctl uses Go vendoring to manage dependencies.
9 | To get started:
10 |
11 | ```bash
12 | git submodule update --init --recursive
13 | make test
14 | ```
15 |
16 | ## Making A Change
17 |
18 | *Before making any significant changes, please [open an
19 | issue](https://github.com/m3db/m3ctl/issues).* Discussing your proposed
20 | changes ahead of time will make the contribution process smooth for everyone.
21 |
22 | Once we've discussed your changes and you've got your code ready, make sure
23 | that tests are passing (`make test` or `make cover`) and open your PR! Your
24 | pull request is most likely to be accepted if it:
25 |
26 | * Includes tests for new functionality.
27 | * Follows the guidelines in [Effective
28 | Go](https://golang.org/doc/effective_go.html) and the [Go team's common code
29 | review comments](https://github.com/golang/go/wiki/CodeReviewComments).
30 | * Has a [good commit
31 | message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
32 |
--------------------------------------------------------------------------------
/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 (c) 2018 Uber Technologies, Inc.
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 | SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
2 | include $(SELF_DIR)/.ci/common.mk
3 |
4 | SHELL=/bin/bash -o pipefail
5 |
6 | html_report := coverage.html
7 | test := .ci/test-cover.sh
8 | test_ci_integration := .ci/test-integration.sh
9 | convert-test-data := .ci/convert-test-data.sh
10 | coverfile := cover.out
11 | coverage_xml := coverage.xml
12 | junit_xml := junit.xml
13 | test_log := test.log
14 | lint_check := .ci/lint.sh
15 | metalint_check := .ci/metalint.sh
16 | metalint_config := .metalinter.json
17 | metalint_exclude := .excludemetalint
18 | m3ctl_package := github.com/m3db/m3ctl
19 | gopath_prefix := $(GOPATH)/src
20 | vendor_prefix := vendor
21 | mocks_output_dir := generated/mocks/mocks
22 | package_root := github.com/m3db/m3ctl
23 | mocks_rules_dir := generated/mocks
24 | ui_codegen_dir := generated/ui
25 | auto_gen := .ci/auto-gen.sh
26 | license_node_modules := $(license_dir)/node_modules
27 | license_dir := .ci/uber-licence
28 | retool_bin_path := _tools/bin
29 | retool_package := github.com/twitchtv/retool
30 | node_version := v6
31 |
32 | BUILD := $(abspath ./bin)
33 | LINUX_AMD64_ENV := GOOS=linux GOARCH=amd64 CGO_ENABLED=0
34 |
35 | SERVICES := \
36 | r2ctl
37 |
38 | .PHONY: setup
39 | setup:
40 | mkdir -p $(BUILD)
41 |
42 | define SERVICE_RULES
43 |
44 | $(SERVICE): setup
45 | ifeq ($(SERVICE),r2ctl)
46 | @echo "Building $(SERVICE) dependencies"
47 | make build-ui-statik-packages
48 | endif
49 | @echo Building $(SERVICE)
50 | $(VENDOR_ENV) go build -o $(BUILD)/$(SERVICE) ./services/$(SERVICE)/main/.
51 |
52 | $(SERVICE)-linux-amd64:
53 | $(LINUX_AMD64_ENV) make $(SERVICE)
54 |
55 | endef
56 |
57 | services: $(SERVICES)
58 | services-linux-amd64:
59 | $(LINUX_AMD64_ENV) make services
60 |
61 | cmd-using-node-version:
62 | ifneq ($(shell brew --prefix nvm 2>/dev/null),)
63 | @echo "Using nvm from brew to select node version $(node_version)"
64 | source $(shell brew --prefix nvm)/nvm.sh && nvm use $(node_version) && bash -c "$(node_cmd)"
65 | else ifneq ($(shell type nvm 2>/dev/null),)
66 | @echo "Using nvm to select node version $(node_version)"
67 | nvm use $(node_version) && bash -c "$(node_cmd)"
68 | else
69 | @echo "Not using nvm, using node version $(shell node --version)"
70 | bash -c "$(node_cmd)"
71 | endif
72 |
73 | build-ui:
74 | ifeq ($(shell ls ./ui/build 2>/dev/null),)
75 | # Need to use subshell output of set-node-version as cannot
76 | # set side-effects of nvm to later commands
77 | @echo "Building UI components, if npm install or build fails try: npm cache clean"
78 | make cmd-using-node-version node_cmd="cd ui && npm install && npm run build"
79 | else
80 | @echo "Skip building UI components, already built, to rebuild first make clean"
81 | endif
82 | # Move public assets into public subdirectory so that it can
83 | # be included in the single statik package built from ./ui/build
84 | rm -rf ./ui/build/public
85 | cp -r ./public ./ui/build/public
86 |
87 | build-ui-statik-packages: build-ui install-tools
88 | mkdir -p $(ui_codegen_dir)
89 | $(retool_bin_path)/statik -src ./ui/build -dest $(ui_codegen_dir) -p statik
90 |
91 | $(foreach SERVICE,$(SERVICES),$(eval $(SERVICE_RULES)))
92 |
93 | .PHONY: lint
94 | lint:
95 | @which golint > /dev/null || go get -u github.com/golang/lint/golint
96 | $(lint_check)
97 |
98 | .PHONY: metalint
99 | metalint: install-metalinter
100 | @($(metalint_check) $(metalint_config) $(metalint_exclude) && echo "metalinted successfully!") || (echo "metalinter failed" && exit 1)
101 |
102 | .PHONY: test-internal
103 | test-internal:
104 | @which go-junit-report > /dev/null || go get -u github.com/sectioneight/go-junit-report
105 | $(test) $(coverfile) | tee $(test_log)
106 |
107 | .PHONY: test-integration
108 | test-integration:
109 | go test -v -tags=integration ./integration
110 |
111 | .PHONY: test-xml
112 | test-xml: test-internal
113 | go-junit-report < $(test_log) > $(junit_xml)
114 | gocov convert $(coverfile) | gocov-xml > $(coverage_xml)
115 | @$(convert-test-data) $(coverage_xml)
116 | @rm $(coverfile) &> /dev/null
117 |
118 | .PHONY: test
119 | test: test-internal
120 | gocov convert $(coverfile) | gocov report
121 |
122 | .PHONY: testhtml
123 | testhtml: test-internal
124 | gocov convert $(coverfile) | gocov-html > $(html_report) && open $(html_report)
125 | @rm -f $(test_log) &> /dev/null
126 |
127 | .PHONY: test-ci-unit
128 | test-ci-unit: test-internal
129 | @which goveralls > /dev/null || go get -u -f github.com/mattn/goveralls
130 | goveralls -coverprofile=$(coverfile) -service=travis-ci || echo -e "\x1b[31mCoveralls failed\x1b[m"
131 |
132 | .PHONY: test-ci-integration
133 | test-ci-integration:
134 | $(test_ci_integration)
135 |
136 | .PHONY: clean
137 | clean:
138 | @rm -f *.html *.xml *.out *.test $(BUILD)/*
139 | @rm -rf ./ui/build
140 |
141 | .PHONY: all
142 | all: lint metalint test-ci-unit services
143 | @echo Made all successfully
144 |
145 | .PHONY: install-licence-bin
146 | install-license-bin: install-vendor
147 | @echo Installing node modules
148 | git submodule update --init --recursive
149 | [ -d $(license_node_modules) ] || (cd $(license_dir) && npm install)
150 |
151 | .PHONY: install-mockgen
152 | install-mockgen: install-vendor
153 | @echo Installing mockgen
154 | glide install
155 |
156 | .PHONY: install-retool
157 | install-retool:
158 | @which retool >/dev/null || go get $(retool_package)
159 |
160 | .PHONY: install-tools
161 | install-tools: install-retool
162 | @echo "Installing tools"
163 | @retool sync >/dev/null 2>/dev/null
164 | @retool build >/dev/null 2>/dev/null
165 |
166 | .PHONY: mock-gen
167 | mock-gen: install-mockgen install-license-bin install-util-mockclean
168 | @echo Generating mocks
169 | PACKAGE=$(package_root) $(auto_gen) $(mocks_output_dir) $(mocks_rules_dir)
170 |
171 | .PHONY: mock-gen-no-deps
172 | mock-gen-no-deps:
173 | @echo Generating mocks
174 | PACKAGE=$(package_root) $(auto_gen) $(mocks_output_dir) $(mocks_rules_dir)
175 |
176 | .DEFAULT_GOAL := all
177 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Migration Warning
2 | =================
3 | This repository has been migrated to github.com/m3db/m3. It's contents can be found at github.com/m3db/m3/src/ctl. Follow along there for updates. This repository is marked archived, and will no longer receive any updates.
4 |
5 | # m3ctl [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov]
6 |
7 | Configuration controller for the M3DB ecosystem. Provides an http API to perform CRUD operatations on
8 | the various configs for M3DB compontents.
9 |
10 | ### Run the R2 App
11 |
12 | ```bash
13 | git clone --recursive https://github.com/m3db/m3ctl.git
14 | cd m3ctl
15 | glide install -v
16 | make && ./bin/r2ctl -f config/base.yaml
17 | open http://localhost:9000
18 | ```
19 |
20 | **UI**
21 | `/`
22 |
23 | **API Server**
24 | `/r2/v1`
25 |
26 | **API Docs (via Swagger)**
27 | `public/r2/v1/swagger`
28 |
29 |
30 |
31 |
32 | This project is released under the [Apache License, Version 2.0](LICENSE).
33 |
34 | [doc-img]: https://godoc.org/github.com/m3db/m3ggregator?status.svg
35 | [doc]: https://godoc.org/github.com/m3db/m3ctl
36 | [ci-img]: https://travis-ci.org/m3db/m3ctl.svg?branch=master
37 | [ci]: https://travis-ci.org/m3db/m3ctl
38 | [cov-img]: https://coveralls.io/repos/m3db/m3ctl/badge.svg?branch=master&service=github
39 | [cov]: https://coveralls.io/github/m3db/m3ctl?branch=master
40 |
--------------------------------------------------------------------------------
/auth/auth.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package auth
22 |
23 | import (
24 | "context"
25 | "net/http"
26 | )
27 |
28 | type keyType int
29 |
30 | // AuthorizationType designates a type of authorization.
31 | type AuthorizationType int
32 |
33 | type errorResponseHandler func(w http.ResponseWriter, code int, msg string) error
34 |
35 | const (
36 | // UserIDField is a key
37 | UserIDField keyType = iota
38 | )
39 |
40 | const (
41 | // UnknownAuthorization is the unknown authorizationType case.
42 | UnknownAuthorization AuthorizationType = iota
43 | // NoAuthorization is the no authorizationType case.
44 | NoAuthorization
45 | // ReadOnlyAuthorization is the read only authorizationType case.
46 | ReadOnlyAuthorization
47 | // WriteOnlyAuthorization is the write only authorizationType case.
48 | WriteOnlyAuthorization
49 | // ReadWriteAuthorization is the read and write authorizationType case.
50 | ReadWriteAuthorization
51 | )
52 |
53 | // HTTPAuthService defines how to handle requests for various http authentication and authorization methods.
54 | type HTTPAuthService interface {
55 | // NewAuthHandler should return a handler that performs some check on the request coming into the given handler
56 | // and then runs the handler if it is. If the request passes authentication/authorization successfully, it should call SetUser
57 | // to make the callers id available to the service in a global context. errHandler should be passed in to properly format the
58 | // the error and respond the the request in the event of bad auth.
59 | NewAuthHandler(authType AuthorizationType, next http.Handler, errHandler errorResponseHandler) http.Handler
60 |
61 | // SetUser sets a userID that identifies the api caller in the global context.
62 | SetUser(parent context.Context, userID string) context.Context
63 |
64 | // GetUser fetches the ID of an api caller from the global context.
65 | GetUser(ctx context.Context) (string, error)
66 | }
67 |
--------------------------------------------------------------------------------
/auth/noop.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package auth
22 |
23 | import (
24 | "context"
25 | "net/http"
26 | )
27 |
28 | const (
29 | noopUser = "noopUser"
30 | )
31 |
32 | // noopAuth lets everything through
33 | type noopAuth struct{}
34 |
35 | // NewNoopAuth creates a new noop auth instance.
36 | func NewNoopAuth() HTTPAuthService {
37 | return noopAuth{}
38 | }
39 |
40 | func (a noopAuth) NewAuthHandler(_ AuthorizationType, next http.Handler, errHandler errorResponseHandler) http.Handler {
41 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
42 | next.ServeHTTP(w, r)
43 | })
44 | }
45 |
46 | // nolint:unparam
47 | func (a noopAuth) SetUser(parent context.Context, userID string) context.Context {
48 | return parent
49 | }
50 |
51 | func (a noopAuth) GetUser(ctx context.Context) (string, error) {
52 | return noopUser, nil
53 | }
54 |
--------------------------------------------------------------------------------
/auth/simple.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package auth
22 |
23 | import (
24 | "context"
25 | "fmt"
26 | "net/http"
27 | )
28 |
29 | // SimpleAuthConfig holds this configuration necessary for a simple auth implementation.
30 | type SimpleAuthConfig struct {
31 | Authentication authenticationConfig `yaml:"authentication"`
32 | Authorization authorizationConfig `yaml:"authorization"`
33 | }
34 |
35 | // authenticationConfig holds this configuration necessary for a simple authentication implementation.
36 | type authenticationConfig struct {
37 | // This is an HTTP header that identifies the user performing the operation.
38 | UserIDHeader string `yaml:"userIDHeader" validate:"nonzero"`
39 | // This is an HTTP header that identifies the user originating the operation.
40 | OriginatorIDHeader string `yaml:"originatorIDHeader"`
41 | }
42 |
43 | // authorizationConfig holds this configuration necessary for a simple authorization implementation.
44 | type authorizationConfig struct {
45 | // This indicates whether reads should use a read whitelist.
46 | ReadWhitelistEnabled bool `yaml:"readWhitelistEnabled,omitempty"`
47 | // This is a list of users that are allowed to perform read operations.
48 | ReadWhitelistedUserIDs []string `yaml:"readWhitelistedUserIDs,omitempty"`
49 | // This indicates whether writes should use a write whitelist.
50 | WriteWhitelistEnabled bool `yaml:"writeWhitelistEnabled,omitempty"`
51 | // This is a list of users that are allowed to perform write operations.
52 | WriteWhitelistedUserIDs []string `yaml:"writeWhitelistedUserIDs,omitempty"`
53 | }
54 |
55 | // NewSimpleAuth creates a new simple auth instance given using the provided config.
56 | func (ac SimpleAuthConfig) NewSimpleAuth() HTTPAuthService {
57 | return simpleAuth{
58 | authentication: simpleAuthentication{
59 | userIDHeader: ac.Authentication.UserIDHeader,
60 | originatorIDHeader: ac.Authentication.OriginatorIDHeader,
61 | },
62 | authorization: simpleAuthorization{
63 | readWhitelistEnabled: ac.Authorization.ReadWhitelistEnabled,
64 | readWhitelistedUserIDs: ac.Authorization.ReadWhitelistedUserIDs,
65 | writeWhitelistEnabled: ac.Authorization.WriteWhitelistEnabled,
66 | writeWhitelistedUserIDs: ac.Authorization.WriteWhitelistedUserIDs,
67 | },
68 | }
69 | }
70 |
71 | type simpleAuth struct {
72 | authentication simpleAuthentication
73 | authorization simpleAuthorization
74 | }
75 |
76 | type simpleAuthentication struct {
77 | userIDHeader string
78 | originatorIDHeader string
79 | }
80 |
81 | func (a simpleAuthentication) authenticate(userID string) error {
82 | if userID == "" {
83 | return fmt.Errorf("must provide header: [%s]", a.userIDHeader)
84 | }
85 | return nil
86 | }
87 |
88 | type simpleAuthorization struct {
89 | readWhitelistEnabled bool
90 | readWhitelistedUserIDs []string
91 | writeWhitelistEnabled bool
92 | writeWhitelistedUserIDs []string
93 | }
94 |
95 | func (a simpleAuthorization) authorize(authType AuthorizationType, userID string) error {
96 | switch authType {
97 | case NoAuthorization:
98 | return nil
99 | case ReadOnlyAuthorization:
100 | return a.authorizeUserForRead(userID)
101 | case WriteOnlyAuthorization:
102 | return a.authorizeUserForWrite(userID)
103 | case ReadWriteAuthorization:
104 | if err := a.authorizeUserForRead(userID); err != nil {
105 | return err
106 | }
107 | return a.authorizeUserForWrite(userID)
108 | default:
109 | return fmt.Errorf("unsupported authorization type %v passed to handler", authType)
110 | }
111 | }
112 |
113 | func authorizeUserForAccess(userID string, whitelistedUserIDs []string, enabled bool) error {
114 | if !enabled {
115 | return nil
116 | }
117 |
118 | for _, u := range whitelistedUserIDs {
119 | if u == userID {
120 | return nil
121 | }
122 | }
123 | return fmt.Errorf("supplied userID: [%s] is not authorized", userID)
124 | }
125 |
126 | func (a simpleAuthorization) authorizeUserForRead(userID string) error {
127 | return authorizeUserForAccess(userID, a.readWhitelistedUserIDs, a.readWhitelistEnabled)
128 | }
129 |
130 | func (a simpleAuthorization) authorizeUserForWrite(userID string) error {
131 | return authorizeUserForAccess(userID, a.writeWhitelistedUserIDs, a.writeWhitelistEnabled)
132 | }
133 |
134 | // Authenticate looks for a header defining a user name. If it finds it, runs the actual http handler passed as a parameter.
135 | // Otherwise, it returns an Unauthorized http response.
136 | func (a simpleAuth) NewAuthHandler(authType AuthorizationType, next http.Handler, errHandler errorResponseHandler) http.Handler {
137 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
138 | var (
139 | userID = r.Header.Get(a.authentication.userIDHeader)
140 | originatorID = r.Header.Get(a.authentication.originatorIDHeader)
141 | )
142 | if originatorID == "" {
143 | originatorID = userID
144 | }
145 | err := a.authentication.authenticate(originatorID)
146 | if err != nil {
147 | errHandler(w, http.StatusUnauthorized, err.Error())
148 | return
149 | }
150 |
151 | err = a.authorization.authorize(authType, userID)
152 | if err != nil {
153 | errHandler(w, http.StatusForbidden, err.Error())
154 | return
155 | }
156 |
157 | ctx := a.SetUser(r.Context(), originatorID)
158 | next.ServeHTTP(w, r.WithContext(ctx))
159 | })
160 | }
161 |
162 | // SetUser sets the user making the changes to the api.
163 | func (a simpleAuth) SetUser(parent context.Context, userID string) context.Context {
164 | return context.WithValue(parent, UserIDField, userID)
165 | }
166 |
167 | // GetUser fetches the ID of an api caller from the global context.
168 | func (a simpleAuth) GetUser(ctx context.Context) (string, error) {
169 | id := ctx.Value(UserIDField)
170 | if id == nil {
171 | return "", fmt.Errorf("couldn't identify user")
172 | }
173 | return id.(string), nil
174 | }
175 |
--------------------------------------------------------------------------------
/auth/simple_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package auth
22 |
23 | import (
24 | "bytes"
25 | "context"
26 | "fmt"
27 | "net/http"
28 | "net/http/httptest"
29 | "testing"
30 |
31 | "github.com/stretchr/testify/require"
32 | yaml "gopkg.in/yaml.v2"
33 | )
34 |
35 | var (
36 | testUser = "testUser"
37 | testOriginator = "testOriginator"
38 | testUserIDHeader = "testUserIDHeader"
39 | testOriginatorIDHeader = "testOriginatorIDHeader"
40 | testConfig = SimpleAuthConfig{
41 | Authentication: authenticationConfig{
42 | UserIDHeader: "testHeader",
43 | },
44 | Authorization: authorizationConfig{
45 | ReadWhitelistEnabled: true,
46 | WriteWhitelistEnabled: false,
47 | ReadWhitelistedUserIDs: []string{testUser},
48 | WriteWhitelistedUserIDs: []string{},
49 | },
50 | }
51 | testConfigWithOriginatorID = SimpleAuthConfig{
52 | Authentication: authenticationConfig{
53 | UserIDHeader: testUserIDHeader,
54 | OriginatorIDHeader: testOriginatorIDHeader,
55 | },
56 | Authorization: authorizationConfig{
57 | ReadWhitelistEnabled: true,
58 | WriteWhitelistEnabled: true,
59 | ReadWhitelistedUserIDs: []string{},
60 | WriteWhitelistedUserIDs: []string{testUser},
61 | },
62 | }
63 | )
64 |
65 | func TestSimpleAuthConfigUnmarshal(t *testing.T) {
66 | configStr := `
67 | authentication:
68 | userIDHeader: user-id
69 | authorization:
70 | readWhitelistEnabled: true
71 | readWhitelistedUserIDs:
72 | - foo
73 | - bar
74 | writeWhitelistEnabled: true
75 | writeWhitelistedUserIDs:
76 | - bar
77 | - baz
78 | `
79 | var cfg SimpleAuthConfig
80 | require.NoError(t, yaml.Unmarshal([]byte(configStr), &cfg))
81 | require.Equal(t, "user-id", cfg.Authentication.UserIDHeader)
82 | require.True(t, cfg.Authorization.ReadWhitelistEnabled)
83 | require.Equal(t, []string{"foo", "bar"}, cfg.Authorization.ReadWhitelistedUserIDs)
84 | require.True(t, cfg.Authorization.WriteWhitelistEnabled)
85 | require.Equal(t, []string{"bar", "baz"}, cfg.Authorization.WriteWhitelistedUserIDs)
86 | }
87 |
88 | func TestNewSimpleAuth(t *testing.T) {
89 | an := testConfig.NewSimpleAuth().(simpleAuth).authentication
90 | az := testConfig.NewSimpleAuth().(simpleAuth).authorization
91 | require.Equal(t, an.userIDHeader, "testHeader")
92 | require.Equal(t, az.readWhitelistEnabled, true)
93 | require.Equal(t, az.writeWhitelistEnabled, false)
94 | require.Equal(t, az.readWhitelistedUserIDs, []string{"testUser"})
95 | require.Equal(t, az.writeWhitelistedUserIDs, []string{})
96 | }
97 |
98 | func TestSetUser(t *testing.T) {
99 | a := testConfig.NewSimpleAuth()
100 | ctx := context.Background()
101 | require.Nil(t, ctx.Value(UserIDField))
102 | ctx = a.SetUser(ctx, "foo")
103 | require.Equal(t, "foo", ctx.Value(UserIDField).(string))
104 | }
105 |
106 | func TestGetUser(t *testing.T) {
107 | a := testConfig.NewSimpleAuth()
108 | ctx := context.Background()
109 |
110 | id, err := a.GetUser(ctx)
111 | require.Empty(t, id)
112 | require.Error(t, err)
113 |
114 | ctx = a.SetUser(ctx, "foo")
115 | id, err = a.GetUser(ctx)
116 | require.Equal(t, "foo", id)
117 | require.NoError(t, err)
118 | }
119 |
120 | func TestSimpleAuthenticationAuthenticate(t *testing.T) {
121 | authentication := simpleAuthentication{
122 | userIDHeader: "foo",
123 | }
124 |
125 | require.Nil(t, authentication.authenticate("bar"))
126 | require.EqualError(t, authentication.authenticate(""), "must provide header: [foo]")
127 | }
128 |
129 | func TestSimpleAuthorizationAuthorize(t *testing.T) {
130 | authorization := simpleAuthorization{
131 | readWhitelistEnabled: true,
132 | writeWhitelistEnabled: false,
133 | readWhitelistedUserIDs: []string{"foo", "bar"},
134 | writeWhitelistedUserIDs: []string{"foo", "bar", "baz"},
135 | }
136 |
137 | require.Nil(t, authorization.authorize(ReadOnlyAuthorization, "foo"))
138 | require.Nil(t, authorization.authorize(WriteOnlyAuthorization, "foo"))
139 | require.Nil(t, authorization.authorize(NoAuthorization, "foo"))
140 | require.Nil(t, authorization.authorize(WriteOnlyAuthorization, "baz"))
141 | require.EqualError(t, authorization.authorize(ReadOnlyAuthorization, "baz"), "supplied userID: [baz] is not authorized")
142 | require.EqualError(t, authorization.authorize(ReadWriteAuthorization, "baz"), "supplied userID: [baz] is not authorized")
143 | require.EqualError(t, authorization.authorize(AuthorizationType(100), "baz"), "unsupported authorization type 100 passed to handler")
144 | }
145 |
146 | func TestAuthorizeUserForAccess(t *testing.T) {
147 | userID := "user2"
148 | whitelistedUserIDs := []string{"user1", "user2", "user3"}
149 | require.NoError(t, authorizeUserForAccess(userID, whitelistedUserIDs, false))
150 | require.NoError(t, authorizeUserForAccess(userID, whitelistedUserIDs, true))
151 | }
152 |
153 | func TestAuthorizeUserForAccessUserNotWhitelisted(t *testing.T) {
154 | userID := "user4"
155 | whitelistedUserIDs := []string{"user1", "user2", "user3"}
156 | require.NoError(t, authorizeUserForAccess(userID, whitelistedUserIDs, false))
157 | require.EqualError(
158 | t,
159 | authorizeUserForAccess(userID, whitelistedUserIDs, true),
160 | fmt.Sprintf("supplied userID: [%s] is not authorized", userID),
161 | )
162 | }
163 |
164 | func TestHealthCheck(t *testing.T) {
165 | a := testConfig.NewSimpleAuth()
166 | f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
167 | v, err := a.GetUser(r.Context())
168 | require.NoError(t, err)
169 | require.Equal(t, "testHeader", v)
170 | })
171 |
172 | wrappedCall := a.NewAuthHandler(NoAuthorization, f, writeAPIResponse)
173 | wrappedCall.ServeHTTP(httptest.NewRecorder(), &http.Request{})
174 | }
175 |
176 | func TestAuthenticateFailure(t *testing.T) {
177 | a := testConfig.NewSimpleAuth()
178 | f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
179 | v, err := a.GetUser(r.Context())
180 | require.NoError(t, err)
181 | require.Equal(t, "testHeader", v)
182 | })
183 | recorder := httptest.NewRecorder()
184 |
185 | wrappedCall := a.NewAuthHandler(NoAuthorization, f, writeAPIResponse)
186 | wrappedCall.ServeHTTP(recorder, &http.Request{})
187 | require.Equal(t, http.StatusUnauthorized, recorder.Code)
188 | require.Equal(t, "application/json", recorder.HeaderMap["Content-Type"][0])
189 | }
190 |
191 | func TestAuthenticateWithOriginatorID(t *testing.T) {
192 | req, err := http.NewRequest(http.MethodPost, "/update", nil)
193 | require.NoError(t, err)
194 | req.Header.Add(testUserIDHeader, testUser)
195 | req.Header.Add(testOriginatorIDHeader, testOriginator)
196 |
197 | a := testConfigWithOriginatorID.NewSimpleAuth()
198 | f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
199 | v, err := a.GetUser(r.Context())
200 | require.NoError(t, err)
201 | require.Equal(t, testOriginator, v)
202 | writeAPIResponse(w, http.StatusOK, "success!")
203 | })
204 | recorder := httptest.NewRecorder()
205 | wrappedCall := a.NewAuthHandler(NoAuthorization, f, writeAPIResponse)
206 | wrappedCall.ServeHTTP(recorder, req)
207 | require.Equal(t, http.StatusOK, recorder.Code)
208 | require.Equal(t, "application/json", recorder.HeaderMap["Content-Type"][0])
209 | }
210 |
211 | func TestAuthorizeFailure(t *testing.T) {
212 | a := testConfig.NewSimpleAuth()
213 | f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
214 | v, err := a.GetUser(r.Context())
215 | require.NoError(t, err)
216 | require.Equal(t, "testHeader", v)
217 | })
218 | recorder := httptest.NewRecorder()
219 | req, err := http.NewRequest("Get", "/create", bytes.NewBuffer(nil))
220 | require.NoError(t, err)
221 | req.Header.Add("testHeader", "validUserID")
222 |
223 | wrappedCall := a.NewAuthHandler(ReadOnlyAuthorization, f, writeAPIResponse)
224 | wrappedCall.ServeHTTP(recorder, req)
225 | require.Equal(t, http.StatusForbidden, recorder.Code)
226 | require.Equal(t, "application/json", recorder.HeaderMap["Content-Type"][0])
227 | }
228 |
229 | func writeAPIResponse(w http.ResponseWriter, code int, msg string) error {
230 | w.Header().Set("Content-Type", "application/json")
231 | w.WriteHeader(code)
232 | _, err := w.Write([]byte(msg))
233 |
234 | return err
235 | }
236 |
--------------------------------------------------------------------------------
/config/base.yaml:
--------------------------------------------------------------------------------
1 | logging:
2 | level: info
3 | stdout: true
4 |
5 | http:
6 | host: 0.0.0.0
7 | port: 9000
8 | readTimeout: 10s
9 | writeTimeout: 10s
10 |
11 | metrics:
12 | m3:
13 | hostPort: 127.0.0.1:5000 # local collector host port for m3 metrics
14 | service: r2ctl
15 | env: test
16 | includeHost: true
17 | samplingRate: 0.01
18 |
19 | store:
20 | stub: true
21 |
22 |
--------------------------------------------------------------------------------
/generated/mocks/generate.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | // mockgen rules for generating mocks using file mode.
22 | //go:generate sh -c "mockgen -package=store $PACKAGE/service/r2/store Store | mockclean -pkg $PACKAGE/store -out $GOPATH/src/$PACKAGE/service/r2/store/store_mock.go"
23 |
24 | package mocks
25 |
--------------------------------------------------------------------------------
/glide.lock:
--------------------------------------------------------------------------------
1 | hash: a3bc31056f5227f10e9890dcd5c1fed4ee7dd460a2a32778715917686ac6879c
2 | updated: 2018-10-29T13:34:52.22873-04:00
3 | imports:
4 | - name: github.com/apache/thrift
5 | version: 9549b25c77587b29be4e0b5c258221a4ed85d37a
6 | subpackages:
7 | - lib/go/thrift
8 | - name: github.com/beorn7/perks
9 | version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
10 | subpackages:
11 | - quantile
12 | - name: github.com/coreos/etcd
13 | version: 694728c496e22dfa5719c78ff23cc982e15bcb2f
14 | subpackages:
15 | - auth/authpb
16 | - clientv3
17 | - clientv3/concurrency
18 | - etcdserver/api/v3rpc/rpctypes
19 | - etcdserver/etcdserverpb
20 | - mvcc/mvccpb
21 | - name: github.com/facebookgo/clock
22 | version: 600d898af40aa09a7a93ecb9265d87b0504b6f03
23 | - name: github.com/ghodss/yaml
24 | version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
25 | - name: github.com/go-ole/go-ole
26 | version: de8695c8edbf8236f30d6e1376e20b198a028d42
27 | - name: github.com/go-playground/locales
28 | version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
29 | subpackages:
30 | - currency
31 | - name: github.com/go-playground/universal-translator
32 | version: 71201497bace774495daed26a3874fd339e0b538
33 | - name: github.com/gogo/protobuf
34 | version: 636bf0302bc95575d69441b25a2603156ffdddf1
35 | subpackages:
36 | - gogoproto
37 | - proto
38 | - protoc-gen-gogo/descriptor
39 | - name: github.com/golang/mock
40 | version: c34cdb4725f4c3844d095133c6e40e448b86589b
41 | subpackages:
42 | - gomock
43 | - name: github.com/golang/protobuf
44 | version: 5a0f697c9ed9d68fef0116532c6e05cfeae00e55
45 | subpackages:
46 | - proto
47 | - protoc-gen-go/descriptor
48 | - ptypes
49 | - ptypes/any
50 | - ptypes/duration
51 | - ptypes/timestamp
52 | - name: github.com/google/uuid
53 | version: 9b3b1e0f5f99ae461456d768e7d301a7acdaa2d8
54 | - name: github.com/gorilla/context
55 | version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
56 | - name: github.com/gorilla/mux
57 | version: 53c1911da2b537f792e7cafcb446b05ffe33b996
58 | - name: github.com/grpc-ecosystem/go-grpc-prometheus
59 | version: 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
60 | - name: github.com/grpc-ecosystem/grpc-gateway
61 | version: 84398b94e188ee336f307779b57b3aa91af7063c
62 | - name: github.com/m3db/m3
63 | version: 16e2dfb2206a8ead03f733a7032b1bc2b83d4b93
64 | subpackages:
65 | - src/cluster/client
66 | - src/cluster/client/etcd
67 | - src/cluster/etcd/watchmanager
68 | - src/cluster/generated/proto/commonpb
69 | - src/cluster/generated/proto/metadatapb
70 | - src/cluster/generated/proto/placementpb
71 | - src/cluster/kv
72 | - src/cluster/kv/etcd
73 | - src/cluster/kv/util
74 | - src/cluster/kv/util/runtime
75 | - src/cluster/placement
76 | - src/cluster/placement/algo
77 | - src/cluster/placement/selector
78 | - src/cluster/placement/service
79 | - src/cluster/placement/storage
80 | - src/cluster/services
81 | - src/cluster/services/heartbeat/etcd
82 | - src/cluster/services/leader
83 | - src/cluster/services/leader/campaign
84 | - src/cluster/services/leader/election
85 | - src/cluster/shard
86 | - src/metrics/aggregation
87 | - src/metrics/errors
88 | - src/metrics/filters
89 | - src/metrics/generated/proto/aggregationpb
90 | - src/metrics/generated/proto/metricpb
91 | - src/metrics/generated/proto/pipelinepb
92 | - src/metrics/generated/proto/policypb
93 | - src/metrics/generated/proto/rulepb
94 | - src/metrics/generated/proto/transformationpb
95 | - src/metrics/metadata
96 | - src/metrics/metric
97 | - src/metrics/metric/id
98 | - src/metrics/pipeline
99 | - src/metrics/pipeline/applied
100 | - src/metrics/policy
101 | - src/metrics/rules
102 | - src/metrics/rules/store/kv
103 | - src/metrics/rules/validator
104 | - src/metrics/rules/validator/namespace
105 | - src/metrics/rules/validator/namespace/kv
106 | - src/metrics/rules/validator/namespace/static
107 | - src/metrics/rules/view
108 | - src/metrics/rules/view/changes
109 | - src/metrics/transformation
110 | - src/metrics/x/bytes
111 | - name: github.com/m3db/m3x
112 | version: 943173a151c8a6b2da1f93f814c27c4e4a1e2050
113 | subpackages:
114 | - checked
115 | - clock
116 | - close
117 | - config
118 | - errors
119 | - instrument
120 | - log
121 | - pool
122 | - process
123 | - resource
124 | - retry
125 | - sync
126 | - time
127 | - watch
128 | - name: github.com/m3db/prometheus_client_golang
129 | version: 8ae269d24972b8695572fa6b2e3718b5ea82d6b4
130 | subpackages:
131 | - prometheus
132 | - prometheus/promhttp
133 | - name: github.com/m3db/prometheus_client_model
134 | version: 8b2299a4bf7d7fc10835527021716d4b4a6e8700
135 | subpackages:
136 | - go
137 | - name: github.com/m3db/prometheus_common
138 | version: 25aaa3dff79bb48116615ebe1dea6a494b74ce77
139 | subpackages:
140 | - expfmt
141 | - internal/bitbucket.org/ww/goautoneg
142 | - model
143 | - name: github.com/m3db/prometheus_procfs
144 | version: 1878d9fbb537119d24b21ca07effd591627cd160
145 | - name: github.com/matttproud/golang_protobuf_extensions
146 | version: c12348ce28de40eed0136aa2b644d0ee0650e56c
147 | subpackages:
148 | - pbutil
149 | - name: github.com/MichaelTJones/pcg
150 | version: df440c6ed7ed8897ac98a408365e5e89c7becf1a
151 | - name: github.com/pborman/uuid
152 | version: adf5a7427709b9deb95d29d3fa8a2bf9cfd388f1
153 | - name: github.com/prometheus/client_golang
154 | version: c5b7fccd204277076155f10851dad72b76a49317
155 | - name: github.com/prometheus/client_model
156 | version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6
157 | - name: github.com/prometheus/common
158 | version: 195bde7883f7c39ea62b0d92ab7359b5327065cb
159 | - name: github.com/prometheus/procfs
160 | version: 1878d9fbb537119d24b21ca07effd591627cd160
161 | - name: github.com/rakyll/statik
162 | version: 19b88da8fc15428620782ba18f68423130e7ac7d
163 | subpackages:
164 | - fs
165 | - name: github.com/shirou/gopsutil
166 | version: b62e301a8b9958eebb7299683eb57fab229a9501
167 | - name: github.com/shirou/w32
168 | version: bb4de0191aa41b5507caa14b0650cdbddcd9280b
169 | - name: github.com/StackExchange/wmi
170 | version: e542ed97d15e640bdc14b5c12162d59e8fc67324
171 | - name: github.com/uber-go/atomic
172 | version: e682c1008ac17bf26d2e4b5ad6cdd08520ed0b22
173 | - name: github.com/uber-go/tally
174 | version: ff17f3c43c065c3c2991f571e740eee43ea3a14a
175 | subpackages:
176 | - m3
177 | - m3/customtransports
178 | - m3/thrift
179 | - m3/thriftudp
180 | - multi
181 | - prometheus
182 | - name: github.com/willf/bitset
183 | version: e553b05586428962bf7058d1044519d87ca72d74
184 | - name: go.uber.org/atomic
185 | version: 1ea20fb1cbb1cc08cbd0d913a96dead89aa18289
186 | - name: go.uber.org/multierr
187 | version: 3c4937480c32f4c13a875a1829af76c98ca3d40a
188 | - name: go.uber.org/zap
189 | version: f85c78b1dd998214c5f2138155b320a4a43fbe36
190 | subpackages:
191 | - buffer
192 | - internal/bufferpool
193 | - internal/color
194 | - internal/exit
195 | - zapcore
196 | - name: golang.org/x/net
197 | version: ab5485076ff3407ad2d02db054635913f017b0ed
198 | subpackages:
199 | - context
200 | - http2
201 | - http2/hpack
202 | - idna
203 | - internal/timeseries
204 | - lex/httplex
205 | - trace
206 | - name: golang.org/x/text
207 | version: 4ee4af566555f5fbe026368b75596286a312663a
208 | subpackages:
209 | - secure/bidirule
210 | - transform
211 | - unicode/bidi
212 | - unicode/norm
213 | - name: google.golang.org/genproto
214 | version: 09f6ed296fc66555a25fe4ce95173148778dfa85
215 | subpackages:
216 | - googleapis/api/annotations
217 | - googleapis/rpc/status
218 | - name: google.golang.org/grpc
219 | version: 401e0e00e4bb830a10496d64cd95e068c5bf50de
220 | subpackages:
221 | - balancer
222 | - codes
223 | - connectivity
224 | - credentials
225 | - grpclb/grpc_lb_v1/messages
226 | - grpclog
227 | - health/grpc_health_v1
228 | - internal
229 | - keepalive
230 | - metadata
231 | - naming
232 | - peer
233 | - resolver
234 | - stats
235 | - status
236 | - tap
237 | - transport
238 | - name: gopkg.in/go-playground/validator.v9
239 | version: a021b2ec9a8a8bb970f3f15bc42617cb520e8a64
240 | - name: gopkg.in/validator.v2
241 | version: 3e4f037f12a1221a0864cf0dd2e81c452ab22448
242 | - name: gopkg.in/yaml.v2
243 | version: 5420a8b6744d3b0345ab293f6fcba19c978f1183
244 | repo: https://github.com/go-yaml/yaml.git
245 | testImports:
246 | - name: github.com/davecgh/go-spew
247 | version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
248 | subpackages:
249 | - spew
250 | - name: github.com/pmezard/go-difflib
251 | version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
252 | subpackages:
253 | - difflib
254 | - name: github.com/stretchr/testify
255 | version: 6fe211e493929a8aac0469b93f28b1d0688a9a3a
256 | subpackages:
257 | - assert
258 | - require
259 |
--------------------------------------------------------------------------------
/glide.yaml:
--------------------------------------------------------------------------------
1 | package: github.com/m3db/m3ctl
2 |
3 | import:
4 | - package: github.com/m3db/m3
5 | version: 16e2dfb2206a8ead03f733a7032b1bc2b83d4b93
6 | - package: github.com/m3db/m3x
7 | version: 943173a151c8a6b2da1f93f814c27c4e4a1e2050
8 | - package: github.com/apache/thrift
9 | version: 9549b25c77587b29be4e0b5c258221a4ed85d37a
10 | - package: github.com/beorn7/perks
11 | version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
12 | - package: github.com/coreos/etcd
13 | version: 3.2.10
14 | - package: google.golang.org/grpc
15 | version: 1.7.3
16 | - package: gopkg.in/validator.v2
17 | version: 3e4f037f12a1221a0864cf0dd2e81c452ab22448
18 | - package: github.com/facebookgo/clock
19 | version: 600d898af40aa09a7a93ecb9265d87b0504b6f03
20 | - package: github.com/ghodss/yaml
21 | version: 73d445a93680fa1a78ae23a5839bad48f32ba1ee
22 | - package: github.com/go-ole/go-ole
23 | version: de8695c8edbf8236f30d6e1376e20b198a028d42
24 | - package: github.com/golang/mock
25 | version: ^1
26 | - package: github.com/golang/protobuf
27 | version: 5a0f697c9ed9d68fef0116532c6e05cfeae00e55
28 | - package: github.com/grpc-ecosystem/go-grpc-prometheus
29 | version: 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
30 | - package: github.com/grpc-ecosystem/grpc-gateway
31 | version: 84398b94e188ee336f307779b57b3aa91af7063c
32 | - package: github.com/matttproud/golang_protobuf_extensions
33 | version: c12348ce28de40eed0136aa2b644d0ee0650e56c
34 | - package: github.com/prometheus/client_golang
35 | version: c5b7fccd204277076155f10851dad72b76a49317
36 | - package: github.com/prometheus/client_model
37 | version: fa8ad6fec33561be4280a8f0514318c79d7f6cb6
38 | - package: github.com/prometheus/common
39 | version: 195bde7883f7c39ea62b0d92ab7359b5327065cb
40 | - package: github.com/prometheus/procfs
41 | version: 1878d9fbb537119d24b21ca07effd591627cd160
42 | - package: github.com/shirou/gopsutil
43 | version: b62e301a8b9958eebb7299683eb57fab229a9501
44 | - package: github.com/shirou/w32
45 | version: bb4de0191aa41b5507caa14b0650cdbddcd9280b
46 | - package: github.com/StackExchange/wmi
47 | version: e542ed97d15e640bdc14b5c12162d59e8fc67324
48 | - package: github.com/uber-go/atomic
49 | version: e682c1008ac17bf26d2e4b5ad6cdd08520ed0b22
50 | - package: github.com/uber-go/tally
51 | version: <4.0.0
52 | - package: golang.org/x/net
53 | version: ab5485076ff3407ad2d02db054635913f017b0ed
54 | - package: github.com/gorilla/mux
55 | version: 1.6.1
56 | - package: github.com/go-playground/locales
57 | version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
58 | - package: github.com/go-playground/universal-translator
59 | version: 71201497bace774495daed26a3874fd339e0b538
60 | - package: gopkg.in/go-playground/validator.v9
61 | version: a021b2ec9a8a8bb970f3f15bc42617cb520e8a64
62 | - package: github.com/rakyll/statik/fs
63 | version: 19b88da8fc15428620782ba18f68423130e7ac7d
64 | testImport:
65 | - package: github.com/stretchr/testify
66 | version: 6fe211e493929a8aac0469b93f28b1d0688a9a3a
67 | - package: github.com/pmezard/go-difflib
68 | version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
69 | - package: github.com/davecgh/go-spew
70 | version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d
71 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Public
5 |
6 |
7 |
8 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/public/r2/v1/swagger/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m3db/m3ctl/65204284e1d73ef9b6d56163cfcddfd93b627921/public/r2/v1/swagger/favicon-16x16.png
--------------------------------------------------------------------------------
/public/r2/v1/swagger/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m3db/m3ctl/65204284e1d73ef9b6d56163cfcddfd93b627921/public/r2/v1/swagger/favicon-32x32.png
--------------------------------------------------------------------------------
/public/r2/v1/swagger/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Swagger UI
7 |
8 |
9 |
10 |
11 |
30 |
31 |
32 |
33 |
34 |
67 |
68 |
69 |
70 |
71 |
72 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/public/r2/v1/swagger/oauth2-redirect.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
54 |
--------------------------------------------------------------------------------
/public/r2/v1/swagger/swagger-ui-bundle.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui-bundle.js","sources":["webpack:///swagger-ui-bundle.js"],"mappings":"AAAA;;;;;AAoyKA;;;;;;AAy+EA;;;;;;;;;;;;;;;;;;;;;;;;;;AAw1TA;;;;;;;;;;;;;;AAs8JA;;;;;;;;;AAw6oBA;;;;;AAirQA;AAm4DA;;;;;;AAo4YA;;;;;;AA8jaA;AAumvBA","sourceRoot":""}
--------------------------------------------------------------------------------
/public/r2/v1/swagger/swagger-ui-standalone-preset.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui-standalone-preset.js","sources":["webpack:///swagger-ui-standalone-preset.js"],"mappings":"AAAA;;;;;AA00CA;;;;;;AAmlFA","sourceRoot":""}
--------------------------------------------------------------------------------
/public/r2/v1/swagger/swagger-ui.css.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui.css","sources":[],"mappings":"","sourceRoot":""}
--------------------------------------------------------------------------------
/public/r2/v1/swagger/swagger-ui.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"swagger-ui.js","sources":["webpack:///swagger-ui.js"],"mappings":"AAAA;;;;;;AAowcA","sourceRoot":""}
--------------------------------------------------------------------------------
/server/http/options.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package http
22 |
23 | import (
24 | "time"
25 |
26 | "github.com/m3db/m3x/instrument"
27 | )
28 |
29 | const (
30 | defaultReadTimeout = 10 * time.Second
31 | defaultWriteTimeout = 10 * time.Second
32 | )
33 |
34 | // Options is a set of server options.
35 | type Options interface {
36 | // SetReadTimeout sets the read timeout.
37 | SetReadTimeout(value time.Duration) Options
38 |
39 | // ReadTimeout returns the read timeout.
40 | ReadTimeout() time.Duration
41 |
42 | // SetWriteTimeout sets the write timeout.
43 | SetWriteTimeout(value time.Duration) Options
44 |
45 | // WriteTimeout returns the write timeout.
46 | WriteTimeout() time.Duration
47 |
48 | // SetInstrumentOptions returns the write timeout.
49 | SetInstrumentOptions(value instrument.Options) Options
50 |
51 | // InstrumentOptions returns the write timeout.
52 | InstrumentOptions() instrument.Options
53 | }
54 |
55 | type options struct {
56 | instrumentOpts instrument.Options
57 | readTimeout time.Duration
58 | writeTimeout time.Duration
59 | }
60 |
61 | // NewOptions creates a new set of server options.
62 | func NewOptions() Options {
63 | return &options{
64 | readTimeout: defaultReadTimeout,
65 | writeTimeout: defaultWriteTimeout,
66 | instrumentOpts: instrument.NewOptions(),
67 | }
68 | }
69 |
70 | func (o *options) SetInstrumentOptions(value instrument.Options) Options {
71 | opts := *o
72 | opts.instrumentOpts = value
73 | return &opts
74 | }
75 |
76 | func (o *options) InstrumentOptions() instrument.Options {
77 | return o.instrumentOpts
78 | }
79 |
80 | func (o *options) SetReadTimeout(value time.Duration) Options {
81 | opts := *o
82 | opts.readTimeout = value
83 | return &opts
84 | }
85 |
86 | func (o *options) ReadTimeout() time.Duration {
87 | return o.readTimeout
88 | }
89 |
90 | func (o *options) SetWriteTimeout(value time.Duration) Options {
91 | opts := *o
92 | opts.writeTimeout = value
93 | return &opts
94 | }
95 |
96 | func (o *options) WriteTimeout() time.Duration {
97 | return o.writeTimeout
98 | }
99 |
--------------------------------------------------------------------------------
/server/http/server.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package http
22 |
23 | import (
24 | "net/http"
25 | "sync"
26 |
27 | _ "github.com/m3db/m3ctl/generated/ui/statik" // Generated UI statik package
28 | mserver "github.com/m3db/m3ctl/server"
29 | "github.com/m3db/m3ctl/service"
30 | "github.com/m3db/m3x/log"
31 |
32 | "github.com/gorilla/mux"
33 | "github.com/rakyll/statik/fs"
34 | )
35 |
36 | const (
37 | publicPathPrefix = "/public"
38 | staticPathPrefix = "/static"
39 | indexFile = "/index.html"
40 | )
41 |
42 | var (
43 | indexPaths = []string{"/", indexFile}
44 | )
45 |
46 | type server struct {
47 | server *http.Server
48 | services []service.Service
49 | logger log.Logger
50 | wg sync.WaitGroup
51 | }
52 |
53 | // NewServer creates a new HTTP server.
54 | func NewServer(address string, opts Options, services ...service.Service) (mserver.Server, error) {
55 | // Make a copy of the services passed in so they cannot be mutated externally
56 | // once the server is constructed.
57 | cloned := make([]service.Service, len(services))
58 | copy(cloned, services)
59 | handler, err := initRouter(cloned)
60 | if err != nil {
61 | return nil, err
62 | }
63 | s := &http.Server{
64 | Addr: address,
65 | Handler: handler,
66 | ReadTimeout: opts.ReadTimeout(),
67 | WriteTimeout: opts.WriteTimeout(),
68 | }
69 | return &server{
70 | server: s,
71 | services: cloned,
72 | logger: opts.InstrumentOptions().Logger(),
73 | }, nil
74 | }
75 |
76 | func (s *server) ListenAndServe() error {
77 | s.wg.Add(1)
78 | go func() {
79 | defer s.wg.Done()
80 | if err := s.server.ListenAndServe(); err != nil {
81 | s.logger.Errorf("could not start listening and serving traffic: %v", err)
82 | }
83 | }()
84 | return nil
85 | }
86 |
87 | func (s *server) Close() {
88 | s.server.Close()
89 | s.wg.Wait()
90 | for _, service := range s.services {
91 | service.Close()
92 | }
93 | }
94 |
95 | func initRouter(services []service.Service) (http.Handler, error) {
96 | router := mux.NewRouter()
97 | if err := registerStaticRoutes(router); err != nil {
98 | return nil, err
99 | }
100 | if err := registerServiceRoutes(router, services); err != nil {
101 | return nil, err
102 | }
103 | return router, nil
104 | }
105 |
106 | func registerStaticRoutes(router *mux.Router) error {
107 | // Register static and public handler.
108 | fileServer, err := fs.New()
109 | if err != nil {
110 | return err
111 | }
112 |
113 | fileServerHandler := http.FileServer(fileServer)
114 | router.PathPrefix(publicPathPrefix).Handler(fileServerHandler)
115 | router.PathPrefix(staticPathPrefix).Handler(fileServerHandler)
116 |
117 | // Register index handlers.
118 | indexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
119 | fileServerHandler.ServeHTTP(w, r)
120 | })
121 | for _, path := range indexPaths {
122 | router.Path(path).HandlerFunc(indexHandler)
123 | }
124 |
125 | return nil
126 | }
127 |
128 | func registerServiceRoutes(router *mux.Router, services []service.Service) error {
129 | for _, service := range services {
130 | pathPrefix := service.URLPrefix()
131 | subRouter := router.PathPrefix(pathPrefix).Subrouter()
132 | if err := service.RegisterHandlers(subRouter); err != nil {
133 | return err
134 | }
135 | }
136 | return nil
137 | }
138 |
--------------------------------------------------------------------------------
/server/server.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE
20 |
21 | package server
22 |
23 | // Server is a server capable of listening to incoming traffic and closing itself
24 | // when it's shut down.
25 | type Server interface {
26 | // ListenAndServe forever listens to new incoming connections and
27 | // handles data from those connections.
28 | ListenAndServe() error
29 |
30 | // Close closes the server.
31 | Close()
32 | }
33 |
--------------------------------------------------------------------------------
/service/health/service.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE
20 |
21 | package health
22 |
23 | import (
24 | "encoding/json"
25 | "fmt"
26 | "net/http"
27 | "os"
28 | "time"
29 |
30 | mservice "github.com/m3db/m3ctl/service"
31 | "github.com/m3db/m3x/instrument"
32 |
33 | "github.com/gorilla/mux"
34 | )
35 |
36 | const (
37 | ok healthStatus = "OK"
38 | healthURL = "/health"
39 | unknownName = "unknown"
40 | )
41 |
42 | type healthStatus string
43 |
44 | type healthCheckResult struct {
45 | Host string `json:"host"`
46 | Timestamp time.Time `json:"timestamp"`
47 | ResponseTime time.Duration `json:"response_time"`
48 | Status healthStatus `json:"status"`
49 | }
50 |
51 | type service struct {
52 | iOpts instrument.Options
53 | }
54 |
55 | // NewService creates a new rules controller.
56 | func NewService(iOpts instrument.Options) mservice.Service {
57 | return &service{iOpts: iOpts}
58 | }
59 |
60 | func (s *service) URLPrefix() string {
61 | return healthURL
62 | }
63 |
64 | func (s *service) RegisterHandlers(router *mux.Router) error {
65 | log := s.iOpts.Logger()
66 | router.HandleFunc("", healthCheck)
67 | log.Infof("Registered health endpoints")
68 | return nil
69 | }
70 |
71 | func (s *service) Close() {}
72 |
73 | func status() healthStatus {
74 | return ok
75 | }
76 |
77 | func hostName() string {
78 | host, err := os.Hostname()
79 | if err != nil {
80 | host = unknownName
81 | }
82 | return host
83 | }
84 |
85 | func healthCheck(w http.ResponseWriter, r *http.Request) {
86 | start := time.Now()
87 | host := hostName()
88 | status := status()
89 | h := healthCheckResult{Host: host, Timestamp: start, Status: status}
90 | h.ResponseTime = time.Since(start)
91 |
92 | body, err := json.Marshal(h)
93 | if err != nil {
94 | w.WriteHeader(http.StatusInternalServerError)
95 | fmt.Fprintf(w, "Could not generate health check result")
96 | return
97 | }
98 |
99 | w.Header().Set("Content-Type", "application/json")
100 | w.Write(body)
101 | }
102 |
--------------------------------------------------------------------------------
/service/health/service_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE
20 |
21 | package health
22 |
23 | import (
24 | "encoding/json"
25 | "net/http"
26 | "net/http/httptest"
27 | "os"
28 | "testing"
29 |
30 | "github.com/gorilla/mux"
31 | "github.com/m3db/m3x/instrument"
32 | "github.com/stretchr/testify/require"
33 | )
34 |
35 | func TestHostName(t *testing.T) {
36 | expectedName, err := os.Hostname()
37 | require.NoError(t, err, "Failed to get system hostname")
38 | actualName := hostName()
39 |
40 | require.Equal(t, expectedName, actualName)
41 | }
42 |
43 | func TestHealthCheck(t *testing.T) {
44 | rr := httptest.NewRecorder()
45 | // Create a request to pass to our handler. We don't have any query parameters for now, so we'll
46 | // pass 'nil' as the third parameter.
47 | req, err := http.NewRequest("GET", "/health", nil)
48 | require.NoError(t, err)
49 |
50 | opts := instrument.NewOptions()
51 | service := NewService(opts)
52 | mux := mux.NewRouter().PathPrefix(service.URLPrefix()).Subrouter()
53 | err = service.RegisterHandlers(mux)
54 | require.NoError(t, err)
55 |
56 | mux.ServeHTTP(rr, req)
57 |
58 | rawResult := make([]byte, rr.Body.Len())
59 | _, err = rr.Body.Read(rawResult)
60 | require.NoError(t, err, "Encountered error parsing response")
61 |
62 | var actualResult healthCheckResult
63 | json.Unmarshal(rawResult, &actualResult)
64 |
65 | name, _ := os.Hostname()
66 |
67 | require.Equal(t, name, actualResult.Host)
68 | require.Equal(t, ok, actualResult.Status)
69 | }
70 |
--------------------------------------------------------------------------------
/service/r2/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package r2
22 |
23 | import "errors"
24 |
25 | // NewInternalError returns a new error that isn't covered by the other error types.
26 | func NewInternalError(msg string) error { return errors.New(msg) }
27 |
28 | // ConflictError represents either a version mismatch writing data or a data conflict issue.
29 | type conflictError string
30 |
31 | // NewConflictError creates a new Conflict Error
32 | func NewConflictError(msg string) error { return conflictError(msg) }
33 |
34 | func (e conflictError) Error() string { return string(e) }
35 |
36 | // VersionError represents a mismatch in the Namespaces or Ruleset version specified in the request
37 | // and the latest one.
38 | type versionError string
39 |
40 | // NewVersionError creates a new Version Error
41 | func NewVersionError(msg string) error { return versionError(msg) }
42 |
43 | func (e versionError) Error() string { return string(e) }
44 |
45 | // BadInputError represents an error due to malformed or invalid metrics.
46 | type badInputError string
47 |
48 | // NewBadInputError creates a new Bad Input Error.
49 | func NewBadInputError(msg string) error { return badInputError(msg) }
50 |
51 | func (e badInputError) Error() string { return string(e) }
52 |
53 | // NotFoundError represents an error due to malformed or invalid metrics.
54 | type notFoundError string
55 |
56 | // NewNotFoundError creates a new not found Error.
57 | func NewNotFoundError(msg string) error { return notFoundError(msg) }
58 |
59 | func (e notFoundError) Error() string { return string(e) }
60 |
61 | // AuthError represents an error due to missing or invalid auth information.
62 | type authError string
63 |
64 | // NewAuthError creates a new not found Error.
65 | func NewAuthError(msg string) error { return authError(msg) }
66 |
67 | func (e authError) Error() string { return string(e) }
68 |
--------------------------------------------------------------------------------
/service/r2/handler.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package r2
22 |
23 | import (
24 | "fmt"
25 | "net/http"
26 |
27 | "github.com/m3db/m3ctl/auth"
28 | "github.com/m3db/m3x/log"
29 | )
30 |
31 | type r2HandlerFunc func(http.ResponseWriter, *http.Request) error
32 |
33 | type r2Handler struct {
34 | logger log.Logger
35 | auth auth.HTTPAuthService
36 | }
37 |
38 | func (h r2Handler) wrap(authType auth.AuthorizationType, fn r2HandlerFunc) http.Handler {
39 | f := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
40 | if err := fn(w, r); err != nil {
41 | h.handleError(w, err)
42 | }
43 | })
44 | return h.auth.NewAuthHandler(authType, f, writeAPIResponse)
45 | }
46 |
47 | func (h r2Handler) handleError(w http.ResponseWriter, opError error) {
48 | h.logger.Errorf(opError.Error())
49 |
50 | var err error
51 | switch opError.(type) {
52 | case conflictError:
53 | err = writeAPIResponse(w, http.StatusConflict, opError.Error())
54 | case badInputError:
55 | err = writeAPIResponse(w, http.StatusBadRequest, opError.Error())
56 | case versionError:
57 | err = writeAPIResponse(w, http.StatusConflict, opError.Error())
58 | case notFoundError:
59 | err = writeAPIResponse(w, http.StatusNotFound, opError.Error())
60 | case authError:
61 | err = writeAPIResponse(w, http.StatusUnauthorized, opError.Error())
62 | default:
63 | err = writeAPIResponse(w, http.StatusInternalServerError, opError.Error())
64 | }
65 |
66 | // Getting here means that the error handling failed. Trying to convey what was supposed to happen.
67 | if err != nil {
68 | msg := fmt.Sprintf("Could not generate error response for: %s", opError.Error())
69 | h.logger.Errorf(msg)
70 | http.Error(w, msg, http.StatusInternalServerError)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/service/r2/io.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package r2
22 |
23 | import (
24 | "encoding/json"
25 | "fmt"
26 | "io"
27 | "net/http"
28 | "reflect"
29 | "strings"
30 |
31 | "github.com/m3db/m3/src/metrics/rules/view/changes"
32 |
33 | validator "gopkg.in/go-playground/validator.v9"
34 | )
35 |
36 | // TODO(dgromov): Make this return a list of validation errors
37 | func parseRequest(s interface{}, body io.ReadCloser) error {
38 | if err := json.NewDecoder(body).Decode(s); err != nil {
39 | return NewBadInputError(fmt.Sprintf("Malformed Json: %s", err.Error()))
40 | }
41 |
42 | // Invoking the validation explictely to have control over the format of the error output.
43 | validate := validator.New()
44 | validate.RegisterTagNameFunc(func(fld reflect.StructField) string {
45 | parts := strings.SplitN(fld.Tag.Get("json"), ",", 2)
46 | if len(parts) > 0 {
47 | return parts[0]
48 | }
49 | return fld.Name
50 | })
51 |
52 | var required []string
53 | if err := validate.Struct(s); err != nil {
54 | for _, e := range err.(validator.ValidationErrors) {
55 | if e.ActualTag() == "required" {
56 | required = append(required, e.Namespace())
57 | }
58 | }
59 | }
60 |
61 | if len(required) > 0 {
62 | return NewBadInputError(fmt.Sprintf("Required: [%v]", strings.Join(required, ", ")))
63 | }
64 | return nil
65 | }
66 |
67 | func writeAPIResponse(w http.ResponseWriter, code int, msg string) error {
68 | j, err := json.Marshal(apiResponse{Code: code, Message: msg})
69 | if err != nil {
70 | return err
71 | }
72 | return sendResponse(w, j, code)
73 | }
74 |
75 | func sendResponse(w http.ResponseWriter, data []byte, status int) error {
76 | w.Header().Set("Content-Type", "application/json")
77 | w.WriteHeader(status)
78 | _, err := w.Write(data)
79 | return err
80 | }
81 |
82 | type apiResponse struct {
83 | Code int `json:"code"`
84 | Message string `json:"message"`
85 | }
86 |
87 | type updateRuleSetRequest struct {
88 | RuleSetChanges changes.RuleSetChanges `json:"rulesetChanges"`
89 | RuleSetVersion int `json:"rulesetVersion"`
90 | }
91 |
--------------------------------------------------------------------------------
/service/r2/routes.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package r2
22 |
23 | import (
24 | "fmt"
25 | "net/http"
26 |
27 | "github.com/m3db/m3/src/metrics/rules/view"
28 |
29 | "github.com/gorilla/mux"
30 | )
31 |
32 | func fetchNamespaces(s *service, _ *http.Request) (data interface{}, err error) {
33 | return s.store.FetchNamespaces()
34 | }
35 |
36 | func fetchNamespace(s *service, r *http.Request) (data interface{}, err error) {
37 | return s.store.FetchRuleSetSnapshot(mux.Vars(r)[namespaceIDVar])
38 | }
39 |
40 | func createNamespace(s *service, r *http.Request) (data interface{}, err error) {
41 | var n view.Namespace
42 | if err := parseRequest(&n, r.Body); err != nil {
43 | return nil, err
44 | }
45 |
46 | uOpts, err := s.newUpdateOptions(r)
47 | if err != nil {
48 | return nil, err
49 | }
50 |
51 | return s.store.CreateNamespace(n.ID, uOpts)
52 | }
53 |
54 | func validateRuleSet(s *service, r *http.Request) (data interface{}, err error) {
55 | vars := mux.Vars(r)
56 | var ruleset view.RuleSet
57 | if err := parseRequest(&ruleset, r.Body); err != nil {
58 | return nil, err
59 | }
60 |
61 | if vars[namespaceIDVar] != ruleset.Namespace {
62 | return nil, fmt.Errorf(
63 | "namespaceID param %s and ruleset namespaceID %s do not match",
64 | vars[namespaceIDVar],
65 | ruleset.Namespace,
66 | )
67 | }
68 |
69 | if err := s.store.ValidateRuleSet(ruleset); err != nil {
70 | return nil, err
71 | }
72 |
73 | return "Ruleset is valid", nil
74 | }
75 |
76 | func updateRuleSet(s *service, r *http.Request) (data interface{}, err error) {
77 | var req updateRuleSetRequest
78 | if err := parseRequest(&req, r.Body); err != nil {
79 | return nil, NewBadInputError(err.Error())
80 | }
81 | if len(req.RuleSetChanges.MappingRuleChanges) == 0 &&
82 | len(req.RuleSetChanges.RollupRuleChanges) == 0 {
83 | return nil, NewBadInputError(
84 | "invalid request: no ruleset changes detected",
85 | )
86 | }
87 |
88 | uOpts, err := s.newUpdateOptions(r)
89 | if err != nil {
90 | return nil, err
91 | }
92 |
93 | return s.store.UpdateRuleSet(req.RuleSetChanges, req.RuleSetVersion, uOpts)
94 | }
95 |
96 | func deleteNamespace(s *service, r *http.Request) (data interface{}, err error) {
97 | vars := mux.Vars(r)
98 | namespaceID := vars[namespaceIDVar]
99 |
100 | uOpts, err := s.newUpdateOptions(r)
101 | if err != nil {
102 | return nil, err
103 | }
104 |
105 | if err := s.store.DeleteNamespace(namespaceID, uOpts); err != nil {
106 | return nil, err
107 | }
108 | return fmt.Sprintf("Deleted namespace %s", namespaceID), nil
109 | }
110 |
111 | func fetchMappingRule(s *service, r *http.Request) (data interface{}, err error) {
112 | vars := mux.Vars(r)
113 | return s.store.FetchMappingRule(vars[namespaceIDVar], vars[ruleIDVar])
114 | }
115 |
116 | func createMappingRule(s *service, r *http.Request) (data interface{}, err error) {
117 | vars := mux.Vars(r)
118 | var mr view.MappingRule
119 | if err := parseRequest(&mr, r.Body); err != nil {
120 | return nil, err
121 | }
122 |
123 | uOpts, err := s.newUpdateOptions(r)
124 | if err != nil {
125 | return nil, err
126 | }
127 |
128 | return s.store.CreateMappingRule(vars[namespaceIDVar], mr, uOpts)
129 | }
130 |
131 | func updateMappingRule(s *service, r *http.Request) (data interface{}, err error) {
132 | vars := mux.Vars(r)
133 |
134 | var mrj view.MappingRule
135 | if err := parseRequest(&mrj, r.Body); err != nil {
136 | return nil, err
137 | }
138 |
139 | uOpts, err := s.newUpdateOptions(r)
140 | if err != nil {
141 | return nil, err
142 | }
143 |
144 | return s.store.UpdateMappingRule(vars[namespaceIDVar], vars[ruleIDVar], mrj, uOpts)
145 | }
146 |
147 | func deleteMappingRule(s *service, r *http.Request) (data interface{}, err error) {
148 | vars := mux.Vars(r)
149 | namespaceID := vars[namespaceIDVar]
150 | mappingRuleID := vars[ruleIDVar]
151 |
152 | uOpts, err := s.newUpdateOptions(r)
153 | if err != nil {
154 | return nil, err
155 | }
156 |
157 | if err := s.store.DeleteMappingRule(namespaceID, mappingRuleID, uOpts); err != nil {
158 | return nil, err
159 | }
160 |
161 | return fmt.Sprintf("Deleted mapping rule: %s in namespace %s", mappingRuleID, namespaceID), nil
162 | }
163 |
164 | func fetchMappingRuleHistory(s *service, r *http.Request) (data interface{}, err error) {
165 | vars := mux.Vars(r)
166 | snapshots, err := s.store.FetchMappingRuleHistory(vars[namespaceIDVar], vars[ruleIDVar])
167 | if err != nil {
168 | return nil, err
169 | }
170 | return view.MappingRuleSnapshots{MappingRules: snapshots}, nil
171 | }
172 |
173 | func fetchRollupRule(s *service, r *http.Request) (data interface{}, err error) {
174 | vars := mux.Vars(r)
175 | return s.store.FetchRollupRule(vars[namespaceIDVar], vars[ruleIDVar])
176 | }
177 |
178 | func createRollupRule(s *service, r *http.Request) (data interface{}, err error) {
179 | vars := mux.Vars(r)
180 | namespaceID := vars[namespaceIDVar]
181 |
182 | var rrj view.RollupRule
183 | if err := parseRequest(&rrj, r.Body); err != nil {
184 | return nil, err
185 | }
186 |
187 | uOpts, err := s.newUpdateOptions(r)
188 | if err != nil {
189 | return nil, err
190 | }
191 |
192 | return s.store.CreateRollupRule(namespaceID, rrj, uOpts)
193 | }
194 |
195 | func updateRollupRule(s *service, r *http.Request) (data interface{}, err error) {
196 | vars := mux.Vars(r)
197 | var rrj view.RollupRule
198 | if err := parseRequest(&rrj, r.Body); err != nil {
199 | return nil, err
200 | }
201 |
202 | uOpts, err := s.newUpdateOptions(r)
203 | if err != nil {
204 | return nil, err
205 | }
206 |
207 | return s.store.UpdateRollupRule(vars[namespaceIDVar], vars[ruleIDVar], rrj, uOpts)
208 | }
209 |
210 | func deleteRollupRule(s *service, r *http.Request) (data interface{}, err error) {
211 | vars := mux.Vars(r)
212 | namespaceID := vars[namespaceIDVar]
213 | rollupRuleID := vars[ruleIDVar]
214 |
215 | uOpts, err := s.newUpdateOptions(r)
216 | if err != nil {
217 | return nil, err
218 | }
219 |
220 | if err := s.store.DeleteRollupRule(namespaceID, rollupRuleID, uOpts); err != nil {
221 | return nil, err
222 | }
223 |
224 | return fmt.Sprintf("Deleted rollup rule: %s in namespace %s", rollupRuleID, namespaceID), nil
225 | }
226 |
227 | func fetchRollupRuleHistory(s *service, r *http.Request) (data interface{}, err error) {
228 | vars := mux.Vars(r)
229 | snapshots, err := s.store.FetchRollupRuleHistory(vars[namespaceIDVar], vars[ruleIDVar])
230 | if err != nil {
231 | return nil, err
232 | }
233 | return view.RollupRuleSnapshots{RollupRules: snapshots}, nil
234 | }
235 |
--------------------------------------------------------------------------------
/service/r2/service_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 | package r2
21 |
22 | import (
23 | "net/http"
24 | "testing"
25 |
26 | "github.com/m3db/m3ctl/auth"
27 |
28 | "github.com/stretchr/testify/require"
29 | )
30 |
31 | func TestDefaultAuthorizationTypeForHTTPMethodGet(t *testing.T) {
32 | actual, err := defaultAuthorizationTypeForHTTPMethod(http.MethodGet)
33 | require.NoError(t, err)
34 | require.EqualValues(t, auth.ReadOnlyAuthorization, actual)
35 | }
36 | func TestDefaultAuthorizationTypeForHTTPMethodPost(t *testing.T) {
37 | actual, err := defaultAuthorizationTypeForHTTPMethod(http.MethodPost)
38 | require.NoError(t, err)
39 | require.EqualValues(t, auth.ReadWriteAuthorization, actual)
40 | }
41 |
42 | func TestDefaultAuthorizationTypeForHTTPMethodPut(t *testing.T) {
43 | actual, err := defaultAuthorizationTypeForHTTPMethod(http.MethodPut)
44 | require.NoError(t, err)
45 | require.EqualValues(t, auth.ReadWriteAuthorization, actual)
46 | }
47 |
48 | func TestDefaultAuthorizationTypeForHTTPMethodPatch(t *testing.T) {
49 | actual, err := defaultAuthorizationTypeForHTTPMethod(http.MethodPatch)
50 | require.NoError(t, err)
51 | require.EqualValues(t, auth.ReadWriteAuthorization, actual)
52 | }
53 |
54 | func TestDefaultAuthorizationTypeForHTTPMethodDelete(t *testing.T) {
55 | actual, err := defaultAuthorizationTypeForHTTPMethod(http.MethodDelete)
56 | require.NoError(t, err)
57 | require.EqualValues(t, auth.ReadWriteAuthorization, actual)
58 |
59 | }
60 |
61 | func TestDefaultAuthorizationTypeForHTTPMethodUnrecognizedMethod(t *testing.T) {
62 | actual, err := defaultAuthorizationTypeForHTTPMethod(http.MethodOptions)
63 | require.Error(t, err)
64 | require.EqualValues(t, auth.UnknownAuthorization, actual)
65 | }
66 |
--------------------------------------------------------------------------------
/service/r2/store/kv/options.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package kv
22 |
23 | import (
24 | "time"
25 |
26 | "github.com/m3db/m3/src/metrics/rules"
27 | "github.com/m3db/m3x/clock"
28 | "github.com/m3db/m3x/instrument"
29 | )
30 |
31 | const (
32 | defaultRuleUpdatePropagationDelay = time.Minute
33 | )
34 |
35 | // StoreOptions is a set of options for a kv backed store.
36 | type StoreOptions interface {
37 | // SetClockOptions sets the clock options.
38 | SetClockOptions(value clock.Options) StoreOptions
39 |
40 | // ClockOptions returns the clock options
41 | ClockOptions() clock.Options
42 |
43 | // SetInstrumentOptions sets the instrument options.
44 | SetInstrumentOptions(value instrument.Options) StoreOptions
45 |
46 | // InstrumentOptions returns the instrument options.
47 | InstrumentOptions() instrument.Options
48 |
49 | // SetRuleUpdatePropagationDelay sets the propagation delay for rule updates.
50 | SetRuleUpdatePropagationDelay(value time.Duration) StoreOptions
51 |
52 | // RuleUpdatePropagationDelay returns the propagation delay for rule updates.
53 | RuleUpdatePropagationDelay() time.Duration
54 |
55 | // SetStoreValidator sets the validator for the store.
56 | SetValidator(value rules.Validator) StoreOptions
57 |
58 | // ValidatprOptions returns the validator for the store.
59 | Validator() rules.Validator
60 | }
61 |
62 | type storeOptions struct {
63 | clockOpts clock.Options
64 | instrumentOpts instrument.Options
65 | ruleUpdatePropagationDelay time.Duration
66 | validator rules.Validator
67 | }
68 |
69 | // NewStoreOptions creates a new set of store options.
70 | func NewStoreOptions() StoreOptions {
71 | return &storeOptions{
72 | clockOpts: clock.NewOptions(),
73 | instrumentOpts: instrument.NewOptions(),
74 | ruleUpdatePropagationDelay: defaultRuleUpdatePropagationDelay,
75 | }
76 | }
77 |
78 | func (o *storeOptions) SetClockOptions(value clock.Options) StoreOptions {
79 | opts := *o
80 | opts.clockOpts = value
81 | return &opts
82 | }
83 |
84 | func (o *storeOptions) ClockOptions() clock.Options {
85 | return o.clockOpts
86 | }
87 |
88 | func (o *storeOptions) SetInstrumentOptions(value instrument.Options) StoreOptions {
89 | opts := *o
90 | opts.instrumentOpts = value
91 | return &opts
92 | }
93 |
94 | func (o *storeOptions) InstrumentOptions() instrument.Options {
95 | return o.instrumentOpts
96 | }
97 |
98 | func (o *storeOptions) SetRuleUpdatePropagationDelay(value time.Duration) StoreOptions {
99 | opts := *o
100 | opts.ruleUpdatePropagationDelay = value
101 | return &opts
102 | }
103 |
104 | func (o *storeOptions) RuleUpdatePropagationDelay() time.Duration {
105 | return o.ruleUpdatePropagationDelay
106 | }
107 |
108 | func (o *storeOptions) SetValidator(value rules.Validator) StoreOptions {
109 | opts := *o
110 | opts.validator = value
111 | return &opts
112 | }
113 |
114 | func (o *storeOptions) Validator() rules.Validator {
115 | return o.validator
116 | }
117 |
--------------------------------------------------------------------------------
/service/r2/store/kv/store_test.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE
20 |
21 | package kv
22 |
23 | import (
24 | "testing"
25 | "time"
26 |
27 | "github.com/m3db/m3/src/metrics/aggregation"
28 | merrors "github.com/m3db/m3/src/metrics/errors"
29 | "github.com/m3db/m3/src/metrics/pipeline"
30 | "github.com/m3db/m3/src/metrics/policy"
31 | "github.com/m3db/m3/src/metrics/rules"
32 | "github.com/m3db/m3/src/metrics/rules/view"
33 | "github.com/m3db/m3/src/metrics/rules/view/changes"
34 | "github.com/m3db/m3ctl/service/r2"
35 | r2store "github.com/m3db/m3ctl/service/r2/store"
36 | "github.com/m3db/m3x/clock"
37 |
38 | "github.com/golang/mock/gomock"
39 | "github.com/stretchr/testify/require"
40 | )
41 |
42 | func TestUpdateRuleSet(t *testing.T) {
43 | helper := rules.NewRuleSetUpdateHelper(time.Minute)
44 | initialRuleSet, err := testRuleSet(1, helper.NewUpdateMetadata(100, "validUser"))
45 | require.NoError(t, err)
46 |
47 | mrs, err := initialRuleSet.MappingRules()
48 | require.NoError(t, err)
49 | rrs, err := initialRuleSet.RollupRules()
50 | require.NoError(t, err)
51 | rsChanges := newTestRuleSetChanges(mrs, rrs)
52 | require.NoError(t, err)
53 |
54 | proto, err := initialRuleSet.ToMutableRuleSet().Proto()
55 | require.NoError(t, err)
56 | expected, err := rules.NewRuleSetFromProto(1, proto, rules.NewOptions())
57 | require.NoError(t, err)
58 | expectedMutable := expected.ToMutableRuleSet()
59 | err = expectedMutable.ApplyRuleSetChanges(rsChanges, helper.NewUpdateMetadata(200, "validUser"))
60 | require.NoError(t, err)
61 |
62 | ctrl := gomock.NewController(t)
63 | defer ctrl.Finish()
64 | mockedStore := rules.NewMockStore(ctrl)
65 | mockedStore.EXPECT().ReadRuleSet("testNamespace").Return(
66 | initialRuleSet,
67 | nil,
68 | ).Times(2)
69 |
70 | mockedStore.EXPECT().WriteRuleSet(gomock.Any()).Do(func(rs rules.MutableRuleSet) {
71 | // mock library can not match rules.MutableRuleSet interface so use this function
72 | expectedProto, err := expectedMutable.Proto()
73 | require.NoError(t, err)
74 | rsProto, err := rs.Proto()
75 | require.NoError(t, err)
76 | require.Equal(t, expectedProto, rsProto)
77 | }).Return(nil)
78 |
79 | storeOpts := NewStoreOptions().SetClockOptions(
80 | clock.NewOptions().SetNowFn(func() time.Time {
81 | return time.Unix(0, 200)
82 | }),
83 | )
84 | rulesStore := NewStore(mockedStore, storeOpts)
85 | uOpts := r2store.NewUpdateOptions().SetAuthor("validUser")
86 | _, err = rulesStore.UpdateRuleSet(rsChanges, 1, uOpts)
87 | require.NoError(t, err)
88 | }
89 |
90 | func TestUpdateRuleSetVersionMisMatch(t *testing.T) {
91 | helper := rules.NewRuleSetUpdateHelper(time.Minute)
92 | initialRuleSet, err := newEmptyTestRuleSet(2, helper.NewUpdateMetadata(100, "validUser"))
93 | require.NoError(t, err)
94 |
95 | rsChanges := newTestRuleSetChanges(
96 | view.MappingRules{},
97 | view.RollupRules{},
98 | )
99 |
100 | ctrl := gomock.NewController(t)
101 | defer ctrl.Finish()
102 | mockedStore := rules.NewMockStore(ctrl)
103 | mockedStore.EXPECT().ReadRuleSet("testNamespace").Return(
104 | initialRuleSet,
105 | nil,
106 | )
107 |
108 | storeOpts := NewStoreOptions().SetClockOptions(
109 | clock.NewOptions().SetNowFn(func() time.Time {
110 | return time.Unix(0, 200)
111 | }),
112 | )
113 | rulesStore := NewStore(mockedStore, storeOpts)
114 | uOpts := r2store.NewUpdateOptions().SetAuthor("validUser")
115 | _, err = rulesStore.UpdateRuleSet(rsChanges, 1, uOpts)
116 | require.Error(t, err)
117 | require.IsType(t, r2.NewConflictError(""), err)
118 | }
119 |
120 | func TestUpdateRuleSetFetchNotFound(t *testing.T) {
121 | rsChanges := newTestRuleSetChanges(
122 | view.MappingRules{},
123 | view.RollupRules{},
124 | )
125 |
126 | ctrl := gomock.NewController(t)
127 | defer ctrl.Finish()
128 | mockedStore := rules.NewMockStore(ctrl)
129 | mockedStore.EXPECT().ReadRuleSet("testNamespace").Return(
130 | nil,
131 | merrors.NewNotFoundError("something bad has happened"),
132 | )
133 |
134 | storeOpts := NewStoreOptions().SetClockOptions(
135 | clock.NewOptions().SetNowFn(func() time.Time {
136 | return time.Unix(0, 200)
137 | }),
138 | )
139 | rulesStore := NewStore(mockedStore, storeOpts)
140 | uOpts := r2store.NewUpdateOptions().SetAuthor("validUser")
141 | _, err := rulesStore.UpdateRuleSet(rsChanges, 1, uOpts)
142 | require.Error(t, err)
143 | require.IsType(t, r2.NewNotFoundError(""), err)
144 | }
145 |
146 | func TestUpdateRuleSetFetchFailure(t *testing.T) {
147 | rsChanges := newTestRuleSetChanges(
148 | view.MappingRules{},
149 | view.RollupRules{},
150 | )
151 |
152 | ctrl := gomock.NewController(t)
153 | defer ctrl.Finish()
154 | mockedStore := rules.NewMockStore(ctrl)
155 | mockedStore.EXPECT().ReadRuleSet("testNamespace").Return(
156 | nil,
157 | merrors.NewValidationError("something bad has happened"),
158 | )
159 |
160 | storeOpts := NewStoreOptions().SetClockOptions(
161 | clock.NewOptions().SetNowFn(func() time.Time {
162 | return time.Unix(0, 200)
163 | }),
164 | )
165 | rulesStore := NewStore(mockedStore, storeOpts)
166 | uOpts := r2store.NewUpdateOptions().SetAuthor("validUser")
167 | _, err := rulesStore.UpdateRuleSet(rsChanges, 1, uOpts)
168 | require.Error(t, err)
169 | require.IsType(t, r2.NewBadInputError(""), err)
170 | }
171 |
172 | func TestUpdateRuleSetMutationFail(t *testing.T) {
173 | helper := rules.NewRuleSetUpdateHelper(time.Minute)
174 | initialRuleSet, err := newEmptyTestRuleSet(1, helper.NewUpdateMetadata(100, "validUser"))
175 |
176 | rsChanges := newTestRuleSetChanges(
177 | view.MappingRules{
178 | "invalidMappingRule": []view.MappingRule{},
179 | },
180 | view.RollupRules{},
181 | )
182 | require.NoError(t, err)
183 |
184 | ctrl := gomock.NewController(t)
185 | defer ctrl.Finish()
186 | mockedStore := rules.NewMockStore(ctrl)
187 | mockedStore.EXPECT().ReadRuleSet("testNamespace").Return(
188 | initialRuleSet,
189 | nil,
190 | )
191 |
192 | storeOpts := NewStoreOptions().SetClockOptions(
193 | clock.NewOptions().SetNowFn(func() time.Time {
194 | return time.Unix(0, 200)
195 | }),
196 | )
197 | rulesStore := NewStore(mockedStore, storeOpts)
198 | uOpts := r2store.NewUpdateOptions().SetAuthor("validUser")
199 | _, err = rulesStore.UpdateRuleSet(rsChanges, 1, uOpts)
200 | require.Error(t, err)
201 | require.IsType(t, r2.NewConflictError(""), err)
202 | }
203 |
204 | func TestUpdateRuleSetWriteFailure(t *testing.T) {
205 | helper := rules.NewRuleSetUpdateHelper(time.Minute)
206 | initialRuleSet, err := testRuleSet(1, helper.NewUpdateMetadata(100, "validUser"))
207 | require.NoError(t, err)
208 |
209 | mrs, err := initialRuleSet.MappingRules()
210 | require.NoError(t, err)
211 | rrs, err := initialRuleSet.RollupRules()
212 | require.NoError(t, err)
213 | rsChanges := newTestRuleSetChanges(mrs, rrs)
214 | require.NoError(t, err)
215 |
216 | proto, err := initialRuleSet.ToMutableRuleSet().Proto()
217 | require.NoError(t, err)
218 | expected, err := rules.NewRuleSetFromProto(1, proto, rules.NewOptions())
219 | require.NoError(t, err)
220 | expectedMutable := expected.ToMutableRuleSet()
221 | err = expectedMutable.ApplyRuleSetChanges(rsChanges, helper.NewUpdateMetadata(200, "validUser"))
222 | require.NoError(t, err)
223 |
224 | ctrl := gomock.NewController(t)
225 | defer ctrl.Finish()
226 | mockedStore := rules.NewMockStore(ctrl)
227 | mockedStore.EXPECT().ReadRuleSet("testNamespace").Return(
228 | initialRuleSet,
229 | nil,
230 | )
231 |
232 | mockedStore.EXPECT().WriteRuleSet(gomock.Any()).Do(func(rs rules.MutableRuleSet) {
233 | // mock library can not match rules.MutableRuleSet interface so use this function
234 | expectedProto, err := expectedMutable.Proto()
235 | require.NoError(t, err)
236 | rsProto, err := rs.Proto()
237 | require.NoError(t, err)
238 | require.Equal(t, expectedProto, rsProto)
239 | }).Return(merrors.NewStaleDataError("something has gone wrong"))
240 |
241 | storeOpts := NewStoreOptions().SetClockOptions(
242 | clock.NewOptions().SetNowFn(func() time.Time {
243 | return time.Unix(0, 200)
244 | }),
245 | )
246 | rulesStore := NewStore(mockedStore, storeOpts)
247 | uOpts := r2store.NewUpdateOptions().SetAuthor("validUser")
248 | _, err = rulesStore.UpdateRuleSet(rsChanges, 1, uOpts)
249 | require.Error(t, err)
250 | require.IsType(t, r2.NewConflictError(""), err)
251 | }
252 |
253 | func newTestRuleSetChanges(mrs view.MappingRules, rrs view.RollupRules) changes.RuleSetChanges {
254 | mrChanges := make([]changes.MappingRuleChange, 0, len(mrs))
255 | for uuid := range mrs {
256 | mrChanges = append(
257 | mrChanges,
258 | changes.MappingRuleChange{
259 | Op: changes.ChangeOp,
260 | RuleID: &uuid,
261 | RuleData: &view.MappingRule{
262 | ID: uuid,
263 | Name: "updateMappingRule",
264 | },
265 | },
266 | )
267 | }
268 |
269 | rrChanges := make([]changes.RollupRuleChange, 0, len(rrs))
270 | for uuid := range rrs {
271 | rrChanges = append(
272 | rrChanges,
273 | changes.RollupRuleChange{
274 | Op: changes.ChangeOp,
275 | RuleID: &uuid,
276 | RuleData: &view.RollupRule{
277 | ID: uuid,
278 | Name: "updateRollupRule",
279 | },
280 | },
281 | )
282 | }
283 |
284 | return changes.RuleSetChanges{
285 | Namespace: "testNamespace",
286 | RollupRuleChanges: rrChanges,
287 | MappingRuleChanges: mrChanges,
288 | }
289 | }
290 |
291 | // nolint: unparam
292 | func testRuleSet(version int, meta rules.UpdateMetadata) (rules.RuleSet, error) {
293 | mutable := rules.NewEmptyRuleSet("testNamespace", meta)
294 | err := mutable.ApplyRuleSetChanges(
295 | changes.RuleSetChanges{
296 | Namespace: "testNamespace",
297 | RollupRuleChanges: []changes.RollupRuleChange{
298 | changes.RollupRuleChange{
299 | Op: changes.AddOp,
300 | RuleData: &view.RollupRule{
301 | Name: "rollupRule3",
302 | Targets: []view.RollupTarget{
303 | {
304 | Pipeline: pipeline.NewPipeline([]pipeline.OpUnion{
305 | {
306 | Type: pipeline.RollupOpType,
307 | Rollup: pipeline.RollupOp{
308 | NewName: []byte("testTarget"),
309 | Tags: [][]byte{[]byte("tag1"), []byte("tag2")},
310 | AggregationID: aggregation.MustCompressTypes(aggregation.Min),
311 | },
312 | },
313 | }),
314 | StoragePolicies: policy.StoragePolicies{
315 | policy.MustParseStoragePolicy("1m:10d"),
316 | },
317 | },
318 | },
319 | },
320 | },
321 | },
322 | MappingRuleChanges: []changes.MappingRuleChange{
323 | changes.MappingRuleChange{
324 | Op: changes.AddOp,
325 | RuleData: &view.MappingRule{
326 | Name: "mappingRule3",
327 | StoragePolicies: policy.StoragePolicies{
328 | policy.MustParseStoragePolicy("1s:6h"),
329 | },
330 | },
331 | },
332 | },
333 | },
334 | meta,
335 | )
336 | if err != nil {
337 | return nil, err
338 | }
339 | proto, err := mutable.Proto()
340 | if err != nil {
341 | return nil, err
342 | }
343 | ruleSet, err := rules.NewRuleSetFromProto(version, proto, rules.NewOptions())
344 | if err != nil {
345 | return nil, err
346 | }
347 |
348 | return ruleSet, nil
349 | }
350 |
351 | func newEmptyTestRuleSet(version int, meta rules.UpdateMetadata) (rules.RuleSet, error) {
352 | proto, err := rules.NewEmptyRuleSet("testNamespace", meta).Proto()
353 | if err != nil {
354 | return nil, err
355 | }
356 | ruleSet, err := rules.NewRuleSetFromProto(version, proto, rules.NewOptions())
357 | if err != nil {
358 | return nil, err
359 | }
360 |
361 | return ruleSet, nil
362 | }
363 |
--------------------------------------------------------------------------------
/service/r2/store/store.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE
20 |
21 | package store
22 |
23 | import (
24 | "github.com/m3db/m3/src/metrics/rules/view"
25 | "github.com/m3db/m3/src/metrics/rules/view/changes"
26 | )
27 |
28 | // Store is a construct that can perform operations against a backing rule store.
29 | type Store interface {
30 | // FetchNamespaces fetches namespaces.
31 | FetchNamespaces() (view.Namespaces, error)
32 |
33 | // CreateNamespace creates a namespace for the given namespace ID.
34 | CreateNamespace(namespaceID string, uOpts UpdateOptions) (view.Namespace, error)
35 |
36 | // DeleteNamespace deletes the namespace for the given namespace ID.
37 | DeleteNamespace(namespaceID string, uOpts UpdateOptions) error
38 |
39 | // FetchRuleSetSnapshot fetches the latest ruleset snapshot for the given namespace ID.
40 | FetchRuleSetSnapshot(namespaceID string) (view.RuleSet, error)
41 |
42 | // ValidateRuleSet validates a namespace's ruleset.
43 | ValidateRuleSet(rs view.RuleSet) error
44 |
45 | // UpdateRuleSet updates a ruleset with a given namespace.
46 | UpdateRuleSet(rsChanges changes.RuleSetChanges, version int, uOpts UpdateOptions) (view.RuleSet, error)
47 |
48 | // FetchMappingRule fetches the mapping rule for the given namespace ID and rule ID.
49 | FetchMappingRule(namespaceID, mappingRuleID string) (view.MappingRule, error)
50 |
51 | // CreateMappingRule creates a mapping rule for the given namespace ID and rule data.
52 | CreateMappingRule(namespaceID string, mrv view.MappingRule, uOpts UpdateOptions) (view.MappingRule, error)
53 |
54 | // UpdateMappingRule updates a mapping rule for the given namespace ID and rule data.
55 | UpdateMappingRule(namespaceID, mappingRuleID string, mrv view.MappingRule, uOpts UpdateOptions) (view.MappingRule, error)
56 |
57 | // DeleteMappingRule deletes the mapping rule for the given namespace ID and rule ID.
58 | DeleteMappingRule(namespaceID, mappingRuleID string, uOpts UpdateOptions) error
59 |
60 | // FetchMappingRuleHistory fetches the history of the mapping rule for the given namespace ID
61 | // and rule ID.
62 | FetchMappingRuleHistory(namespaceID, mappingRuleID string) ([]view.MappingRule, error)
63 |
64 | // FetchRollupRule fetches the rollup rule for the given namespace ID and rule ID.
65 | FetchRollupRule(namespaceID, rollupRuleID string) (view.RollupRule, error)
66 |
67 | // CreateRollupRule creates a rollup rule for the given namespace ID and rule data.
68 | CreateRollupRule(namespaceID string, rrv view.RollupRule, uOpts UpdateOptions) (view.RollupRule, error)
69 |
70 | // UpdateRollupRule updates a rollup rule for the given namespace ID and rule data.
71 | UpdateRollupRule(namespaceID, rollupRuleID string, rrv view.RollupRule, uOpts UpdateOptions) (view.RollupRule, error)
72 |
73 | // DeleteRollupRule deletes the rollup rule for the given namespace ID and rule ID.
74 | DeleteRollupRule(namespaceID, rollupRuleID string, uOpts UpdateOptions) error
75 |
76 | // FetchRollupRuleHistory fetches the history of the rollup rule for the given namespace ID
77 | // and rule ID.
78 | FetchRollupRuleHistory(namespaceID, rollupRuleID string) ([]view.RollupRule, error)
79 |
80 | // Close closes the store.
81 | Close()
82 | }
83 |
--------------------------------------------------------------------------------
/service/r2/store/store_mock.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | // Automatically generated by MockGen. DO NOT EDIT!
22 | // Source: github.com/m3db/m3ctl/service/r2/store (interfaces: Store)
23 |
24 | package store
25 |
26 | import (
27 | "github.com/m3db/m3/src/metrics/rules/view"
28 | "github.com/m3db/m3/src/metrics/rules/view/changes"
29 |
30 | "github.com/golang/mock/gomock"
31 | )
32 |
33 | // Mock of Store interface
34 | type MockStore struct {
35 | ctrl *gomock.Controller
36 | recorder *_MockStoreRecorder
37 | }
38 |
39 | // Recorder for MockStore (not exported)
40 | type _MockStoreRecorder struct {
41 | mock *MockStore
42 | }
43 |
44 | func NewMockStore(ctrl *gomock.Controller) *MockStore {
45 | mock := &MockStore{ctrl: ctrl}
46 | mock.recorder = &_MockStoreRecorder{mock}
47 | return mock
48 | }
49 |
50 | func (_m *MockStore) EXPECT() *_MockStoreRecorder {
51 | return _m.recorder
52 | }
53 |
54 | func (_m *MockStore) Close() {
55 | _m.ctrl.Call(_m, "Close")
56 | }
57 |
58 | func (_mr *_MockStoreRecorder) Close() *gomock.Call {
59 | return _mr.mock.ctrl.RecordCall(_mr.mock, "Close")
60 | }
61 |
62 | func (_m *MockStore) CreateMappingRule(_param0 string, _param1 view.MappingRule, _param2 UpdateOptions) (view.MappingRule, error) {
63 | ret := _m.ctrl.Call(_m, "CreateMappingRule", _param0, _param1, _param2)
64 | ret0, _ := ret[0].(view.MappingRule)
65 | ret1, _ := ret[1].(error)
66 | return ret0, ret1
67 | }
68 |
69 | func (_mr *_MockStoreRecorder) CreateMappingRule(arg0, arg1, arg2 interface{}) *gomock.Call {
70 | return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateMappingRule", arg0, arg1, arg2)
71 | }
72 |
73 | func (_m *MockStore) CreateNamespace(_param0 string, _param1 UpdateOptions) (view.Namespace, error) {
74 | ret := _m.ctrl.Call(_m, "CreateNamespace", _param0, _param1)
75 | ret0, _ := ret[0].(view.Namespace)
76 | ret1, _ := ret[1].(error)
77 | return ret0, ret1
78 | }
79 |
80 | func (_mr *_MockStoreRecorder) CreateNamespace(arg0, arg1 interface{}) *gomock.Call {
81 | return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateNamespace", arg0, arg1)
82 | }
83 |
84 | func (_m *MockStore) CreateRollupRule(_param0 string, _param1 view.RollupRule, _param2 UpdateOptions) (view.RollupRule, error) {
85 | ret := _m.ctrl.Call(_m, "CreateRollupRule", _param0, _param1, _param2)
86 | ret0, _ := ret[0].(view.RollupRule)
87 | ret1, _ := ret[1].(error)
88 | return ret0, ret1
89 | }
90 |
91 | func (_mr *_MockStoreRecorder) CreateRollupRule(arg0, arg1, arg2 interface{}) *gomock.Call {
92 | return _mr.mock.ctrl.RecordCall(_mr.mock, "CreateRollupRule", arg0, arg1, arg2)
93 | }
94 |
95 | func (_m *MockStore) DeleteMappingRule(_param0 string, _param1 string, _param2 UpdateOptions) error {
96 | ret := _m.ctrl.Call(_m, "DeleteMappingRule", _param0, _param1, _param2)
97 | ret0, _ := ret[0].(error)
98 | return ret0
99 | }
100 |
101 | func (_mr *_MockStoreRecorder) DeleteMappingRule(arg0, arg1, arg2 interface{}) *gomock.Call {
102 | return _mr.mock.ctrl.RecordCall(_mr.mock, "DeleteMappingRule", arg0, arg1, arg2)
103 | }
104 |
105 | func (_m *MockStore) DeleteNamespace(_param0 string, _param1 UpdateOptions) error {
106 | ret := _m.ctrl.Call(_m, "DeleteNamespace", _param0, _param1)
107 | ret0, _ := ret[0].(error)
108 | return ret0
109 | }
110 |
111 | func (_mr *_MockStoreRecorder) DeleteNamespace(arg0, arg1 interface{}) *gomock.Call {
112 | return _mr.mock.ctrl.RecordCall(_mr.mock, "DeleteNamespace", arg0, arg1)
113 | }
114 |
115 | func (_m *MockStore) DeleteRollupRule(_param0 string, _param1 string, _param2 UpdateOptions) error {
116 | ret := _m.ctrl.Call(_m, "DeleteRollupRule", _param0, _param1, _param2)
117 | ret0, _ := ret[0].(error)
118 | return ret0
119 | }
120 |
121 | func (_mr *_MockStoreRecorder) DeleteRollupRule(arg0, arg1, arg2 interface{}) *gomock.Call {
122 | return _mr.mock.ctrl.RecordCall(_mr.mock, "DeleteRollupRule", arg0, arg1, arg2)
123 | }
124 |
125 | func (_m *MockStore) FetchMappingRule(_param0 string, _param1 string) (view.MappingRule, error) {
126 | ret := _m.ctrl.Call(_m, "FetchMappingRule", _param0, _param1)
127 | ret0, _ := ret[0].(view.MappingRule)
128 | ret1, _ := ret[1].(error)
129 | return ret0, ret1
130 | }
131 |
132 | func (_mr *_MockStoreRecorder) FetchMappingRule(arg0, arg1 interface{}) *gomock.Call {
133 | return _mr.mock.ctrl.RecordCall(_mr.mock, "FetchMappingRule", arg0, arg1)
134 | }
135 |
136 | func (_m *MockStore) FetchMappingRuleHistory(_param0 string, _param1 string) ([]view.MappingRule, error) {
137 | ret := _m.ctrl.Call(_m, "FetchMappingRuleHistory", _param0, _param1)
138 | ret0, _ := ret[0].([]view.MappingRule)
139 | ret1, _ := ret[1].(error)
140 | return ret0, ret1
141 | }
142 |
143 | func (_mr *_MockStoreRecorder) FetchMappingRuleHistory(arg0, arg1 interface{}) *gomock.Call {
144 | return _mr.mock.ctrl.RecordCall(_mr.mock, "FetchMappingRuleHistory", arg0, arg1)
145 | }
146 |
147 | func (_m *MockStore) FetchNamespaces() (view.Namespaces, error) {
148 | ret := _m.ctrl.Call(_m, "FetchNamespaces")
149 | ret0, _ := ret[0].(view.Namespaces)
150 | ret1, _ := ret[1].(error)
151 | return ret0, ret1
152 | }
153 |
154 | func (_mr *_MockStoreRecorder) FetchNamespaces() *gomock.Call {
155 | return _mr.mock.ctrl.RecordCall(_mr.mock, "FetchNamespaces")
156 | }
157 |
158 | func (_m *MockStore) FetchRollupRule(_param0 string, _param1 string) (view.RollupRule, error) {
159 | ret := _m.ctrl.Call(_m, "FetchRollupRule", _param0, _param1)
160 | ret0, _ := ret[0].(view.RollupRule)
161 | ret1, _ := ret[1].(error)
162 | return ret0, ret1
163 | }
164 |
165 | func (_mr *_MockStoreRecorder) FetchRollupRule(arg0, arg1 interface{}) *gomock.Call {
166 | return _mr.mock.ctrl.RecordCall(_mr.mock, "FetchRollupRule", arg0, arg1)
167 | }
168 |
169 | func (_m *MockStore) FetchRollupRuleHistory(_param0 string, _param1 string) ([]view.RollupRule, error) {
170 | ret := _m.ctrl.Call(_m, "FetchRollupRuleHistory", _param0, _param1)
171 | ret0, _ := ret[0].([]view.RollupRule)
172 | ret1, _ := ret[1].(error)
173 | return ret0, ret1
174 | }
175 |
176 | func (_mr *_MockStoreRecorder) FetchRollupRuleHistory(arg0, arg1 interface{}) *gomock.Call {
177 | return _mr.mock.ctrl.RecordCall(_mr.mock, "FetchRollupRuleHistory", arg0, arg1)
178 | }
179 |
180 | func (_m *MockStore) FetchRuleSetSnapshot(_param0 string) (view.RuleSet, error) {
181 | ret := _m.ctrl.Call(_m, "FetchRuleSetSnapshot", _param0)
182 | ret0, _ := ret[0].(view.RuleSet)
183 | ret1, _ := ret[1].(error)
184 | return ret0, ret1
185 | }
186 |
187 | func (_mr *_MockStoreRecorder) FetchRuleSetSnapshot(arg0 interface{}) *gomock.Call {
188 | return _mr.mock.ctrl.RecordCall(_mr.mock, "FetchRuleSetSnapshot", arg0)
189 | }
190 |
191 | func (_m *MockStore) UpdateMappingRule(_param0 string, _param1 string, _param2 view.MappingRule, _param3 UpdateOptions) (view.MappingRule, error) {
192 | ret := _m.ctrl.Call(_m, "UpdateMappingRule", _param0, _param1, _param2, _param3)
193 | ret0, _ := ret[0].(view.MappingRule)
194 | ret1, _ := ret[1].(error)
195 | return ret0, ret1
196 | }
197 |
198 | func (_mr *_MockStoreRecorder) UpdateMappingRule(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
199 | return _mr.mock.ctrl.RecordCall(_mr.mock, "UpdateMappingRule", arg0, arg1, arg2, arg3)
200 | }
201 |
202 | func (_m *MockStore) UpdateRollupRule(_param0 string, _param1 string, _param2 view.RollupRule, _param3 UpdateOptions) (view.RollupRule, error) {
203 | ret := _m.ctrl.Call(_m, "UpdateRollupRule", _param0, _param1, _param2, _param3)
204 | ret0, _ := ret[0].(view.RollupRule)
205 | ret1, _ := ret[1].(error)
206 | return ret0, ret1
207 | }
208 |
209 | func (_mr *_MockStoreRecorder) UpdateRollupRule(arg0, arg1, arg2, arg3 interface{}) *gomock.Call {
210 | return _mr.mock.ctrl.RecordCall(_mr.mock, "UpdateRollupRule", arg0, arg1, arg2, arg3)
211 | }
212 |
213 | func (_m *MockStore) UpdateRuleSet(_param0 changes.RuleSetChanges, _param1 int, _param2 UpdateOptions) (view.RuleSet, error) {
214 | ret := _m.ctrl.Call(_m, "UpdateRuleSet", _param0, _param1, _param2)
215 | ret0, _ := ret[0].(view.RuleSet)
216 | ret1, _ := ret[1].(error)
217 | return ret0, ret1
218 | }
219 |
220 | func (_mr *_MockStoreRecorder) UpdateRuleSet(arg0, arg1, arg2 interface{}) *gomock.Call {
221 | return _mr.mock.ctrl.RecordCall(_mr.mock, "UpdateRuleSet", arg0, arg1, arg2)
222 | }
223 |
224 | func (_m *MockStore) ValidateRuleSet(_param0 view.RuleSet) error {
225 | ret := _m.ctrl.Call(_m, "ValidateRuleSet", _param0)
226 | ret0, _ := ret[0].(error)
227 | return ret0
228 | }
229 |
230 | func (_mr *_MockStoreRecorder) ValidateRuleSet(arg0 interface{}) *gomock.Call {
231 | return _mr.mock.ctrl.RecordCall(_mr.mock, "ValidateRuleSet", arg0)
232 | }
233 |
--------------------------------------------------------------------------------
/service/r2/store/update_options.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2018 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package store
22 |
23 | // UpdateOptions is a set of ruleset or namespace update options.
24 | type UpdateOptions interface {
25 | // SetAuthor sets the author for an update.
26 | SetAuthor(value string) UpdateOptions
27 |
28 | // Author returns the author for an update.
29 | Author() string
30 | }
31 |
32 | type updateOptions struct {
33 | author string
34 | }
35 |
36 | // NewUpdateOptions creates a new set of update options.
37 | func NewUpdateOptions() UpdateOptions {
38 | return &updateOptions{}
39 | }
40 |
41 | func (o *updateOptions) SetAuthor(value string) UpdateOptions {
42 | opts := *o
43 | opts.author = value
44 | return &opts
45 | }
46 |
47 | func (o *updateOptions) Author() string {
48 | return o.author
49 | }
50 |
--------------------------------------------------------------------------------
/service/service.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE
20 |
21 | package service
22 |
23 | import "github.com/gorilla/mux"
24 |
25 | // Service defines routes and handlers for a given entity.
26 | type Service interface {
27 | // URLPrefix returns the prefix for all routes of this service.
28 | URLPrefix() string
29 |
30 | // RegisterHandlers wires the http handlers for this Service with the given router.
31 | RegisterHandlers(router *mux.Router) error
32 |
33 | // Close closes the service.
34 | Close()
35 | }
36 |
--------------------------------------------------------------------------------
/services/r2ctl/config/r2ctl.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package config
22 |
23 | import (
24 | "errors"
25 | "time"
26 |
27 | "github.com/m3db/m3/src/cluster/client/etcd"
28 | clusterkv "github.com/m3db/m3/src/cluster/kv"
29 | "github.com/m3db/m3/src/metrics/rules"
30 | ruleskv "github.com/m3db/m3/src/metrics/rules/store/kv"
31 | "github.com/m3db/m3/src/metrics/rules/validator"
32 | "github.com/m3db/m3ctl/auth"
33 | r2store "github.com/m3db/m3ctl/service/r2/store"
34 | r2kv "github.com/m3db/m3ctl/service/r2/store/kv"
35 | "github.com/m3db/m3ctl/service/r2/store/stub"
36 | "github.com/m3db/m3x/instrument"
37 | "github.com/m3db/m3x/log"
38 | )
39 |
40 | var (
41 | errKVConfigRequired = errors.New("must provide kv configuration if not using stub store")
42 | )
43 |
44 | // Configuration is the global configuration for r2ctl.
45 | type Configuration struct {
46 | // Logging configuration.
47 | Logging log.Configuration `yaml:"logging"`
48 |
49 | // HTTP server configuration.
50 | HTTP serverConfig `yaml:"http"`
51 |
52 | // Metrics configuration.
53 | Metrics instrument.MetricsConfiguration `yaml:"metrics"`
54 |
55 | // Store configuration.
56 | Store r2StoreConfiguration `yaml:"store"`
57 |
58 | // Simple Auth Config.
59 | Auth *auth.SimpleAuthConfig `yaml:"auth"`
60 | }
61 |
62 | // r2StoreConfiguration has all the fields necessary for an R2 store.
63 | type r2StoreConfiguration struct {
64 | // Stub means use the stub store.
65 | Stub bool `yaml:"stub"`
66 |
67 | // KV is the configuration for the etcd backed implementation of the kv store.
68 | KV *kvStoreConfig `yaml:"kv,omitempty"`
69 | }
70 |
71 | // NewR2Store creates a new R2 store.
72 | func (c r2StoreConfiguration) NewR2Store(instrumentOpts instrument.Options) (r2store.Store, error) {
73 | if c.Stub {
74 | return stub.NewStore(instrumentOpts), nil
75 | }
76 |
77 | if c.KV == nil {
78 | return nil, errKVConfigRequired
79 | }
80 |
81 | return c.KV.NewStore(instrumentOpts)
82 | }
83 |
84 | // kvStoreConfig is the configuration for the KV backed implementation of the R2 store.
85 | type kvStoreConfig struct {
86 | // KVClient configures the client for key value store.
87 | KVClient *etcd.Configuration `yaml:"kvClient" validate:"nonzero"`
88 |
89 | // KV configuration for the rules store.
90 | KVConfig clusterkv.OverrideConfiguration `yaml:"kvConfig"`
91 |
92 | // NamespacesKey is KV key associated with namespaces..
93 | NamespacesKey string `yaml:"namespacesKey" validate:"nonzero"`
94 |
95 | // RuleSet key format.
96 | RuleSetKeyFmt string `yaml:"ruleSetKeyFmt" validate:"nonzero"`
97 |
98 | // Propagation delay for rule updates.
99 | PropagationDelay time.Duration `yaml:"propagationDelay" validate:"nonzero"`
100 |
101 | // Validation configuration.
102 | Validation *validator.Configuration `yaml:"validation"`
103 | }
104 |
105 | // NewStore creates a new KV backed R2 store.
106 | func (c kvStoreConfig) NewStore(instrumentOpts instrument.Options) (r2store.Store, error) {
107 | // Create rules store.
108 | kvClient, err := c.KVClient.NewClient(instrumentOpts)
109 | if err != nil {
110 | return nil, err
111 | }
112 | kvOpts, err := c.KVConfig.NewOverrideOptions()
113 | if err != nil {
114 | return nil, err
115 | }
116 | kvStore, err := kvClient.TxnStore(kvOpts)
117 | if err != nil {
118 | return nil, err
119 | }
120 | var validator rules.Validator
121 | if c.Validation != nil {
122 | validator, err = c.Validation.NewValidator(kvClient)
123 | if err != nil {
124 | return nil, err
125 | }
126 | }
127 | rulesStoreOpts := ruleskv.NewStoreOptions(c.NamespacesKey, c.RuleSetKeyFmt, validator)
128 | rulesStore := ruleskv.NewStore(kvStore, rulesStoreOpts)
129 |
130 | // Create kv store.
131 | r2StoreOpts := r2kv.NewStoreOptions().
132 | SetInstrumentOptions(instrumentOpts).
133 | SetRuleUpdatePropagationDelay(c.PropagationDelay).
134 | SetValidator(validator)
135 | return r2kv.NewStore(rulesStore, r2StoreOpts), nil
136 | }
137 |
--------------------------------------------------------------------------------
/services/r2ctl/config/server.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package config
22 |
23 | import (
24 | "time"
25 |
26 | httpserver "github.com/m3db/m3ctl/server/http"
27 | "github.com/m3db/m3x/instrument"
28 | )
29 |
30 | type serverConfig struct {
31 | // Host is the host name the HTTP server shoud listen on.
32 | Host string `yaml:"host" validate:"nonzero"`
33 |
34 | // Port is the port the HTTP server should listen on.
35 | Port int `yaml:"port"`
36 |
37 | // ReadTimeout is the HTTP server read timeout.
38 | ReadTimeout time.Duration `yaml:"readTimeout"`
39 |
40 | // WriteTimeout HTTP server write timeout.
41 | WriteTimeout time.Duration `yaml:"writeTimeout"`
42 | }
43 |
44 | func (c *serverConfig) NewServerOptions(
45 | instrumentOpts instrument.Options,
46 | ) httpserver.Options {
47 | opts := httpserver.NewOptions().SetInstrumentOptions(instrumentOpts)
48 | if c.ReadTimeout != 0 {
49 | opts = opts.SetReadTimeout(c.ReadTimeout)
50 | }
51 | if c.WriteTimeout != 0 {
52 | opts = opts.SetWriteTimeout(c.WriteTimeout)
53 | }
54 |
55 | return opts
56 | }
57 |
--------------------------------------------------------------------------------
/services/r2ctl/main/main.go:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | package main
22 |
23 | import (
24 | "flag"
25 | "fmt"
26 | "os"
27 | "os/signal"
28 | "strconv"
29 | "syscall"
30 | "time"
31 |
32 | "github.com/m3db/m3ctl/auth"
33 | "github.com/m3db/m3ctl/server/http"
34 | "github.com/m3db/m3ctl/service/health"
35 | "github.com/m3db/m3ctl/service/r2"
36 | "github.com/m3db/m3ctl/services/r2ctl/config"
37 | "github.com/m3db/m3x/clock"
38 | xconfig "github.com/m3db/m3x/config"
39 | "github.com/m3db/m3x/instrument"
40 | )
41 |
42 | const (
43 | portEnvVar = "R2CTL_PORT"
44 | r2apiPrefix = "/r2/v1/"
45 | gracefulShutdownTimeout = 15 * time.Second
46 | )
47 |
48 | func main() {
49 | configFile := flag.String("f", "config/base.yaml", "configuration file")
50 | flag.Parse()
51 |
52 | if len(*configFile) == 0 {
53 | flag.Usage()
54 | os.Exit(1)
55 | }
56 |
57 | var cfg config.Configuration
58 | if err := xconfig.LoadFile(&cfg, *configFile, xconfig.Options{}); err != nil {
59 | fmt.Printf("error loading config file: %v\n", err)
60 | os.Exit(1)
61 | }
62 |
63 | logger, err := cfg.Logging.BuildLogger()
64 | if err != nil {
65 | fmt.Printf("error creating logger: %v\n", err)
66 | os.Exit(1)
67 | }
68 |
69 | envPort := os.Getenv(portEnvVar)
70 | if envPort != "" {
71 | if p, err := strconv.Atoi(envPort); err == nil {
72 | logger.Infof("using env supplied port var: %s=%d", portEnvVar, p)
73 | cfg.HTTP.Port = p
74 | } else {
75 | logger.Fatalf("%s (%s) is not a valid port number", envPort, portEnvVar)
76 | }
77 | }
78 |
79 | if cfg.HTTP.Port == 0 {
80 | logger.Fatalf("no valid port configured. Can't start.")
81 | }
82 |
83 | scope, closer, err := cfg.Metrics.NewRootScope()
84 | if err != nil {
85 | logger.Fatalf("error creating metrics root scope: %v", err)
86 | }
87 | defer closer.Close()
88 |
89 | instrumentOpts := instrument.NewOptions().
90 | SetLogger(logger).
91 | SetMetricsScope(scope).
92 | SetMetricsSamplingRate(cfg.Metrics.SampleRate()).
93 | SetReportInterval(cfg.Metrics.ReportInterval())
94 |
95 | // Create R2 store.
96 | storeScope := scope.SubScope("r2-store")
97 | store, err := cfg.Store.NewR2Store(instrumentOpts.SetMetricsScope(storeScope))
98 | if err != nil {
99 | logger.Fatalf("error initializing backing store: %v", err)
100 | }
101 |
102 | // Create R2 service.
103 | authService := auth.NewNoopAuth()
104 | if cfg.Auth != nil {
105 | authService = cfg.Auth.NewSimpleAuth()
106 | }
107 | r2ServiceScope := scope.Tagged(map[string]string{
108 | "service-name": "r2",
109 | })
110 | r2ServiceInstrumentOpts := instrumentOpts.SetMetricsScope(r2ServiceScope)
111 | r2Service := r2.NewService(
112 | r2apiPrefix,
113 | authService,
114 | store,
115 | r2ServiceInstrumentOpts,
116 | clock.NewOptions(),
117 | )
118 |
119 | // Create health service.
120 | healthServiceScope := scope.Tagged(map[string]string{
121 | "service-name": "health",
122 | })
123 | healthServiceInstrumentOpts := instrumentOpts.SetMetricsScope(healthServiceScope)
124 | healthService := health.NewService(healthServiceInstrumentOpts)
125 |
126 | // Create HTTP server.
127 | listenAddr := fmt.Sprintf("%s:%d", cfg.HTTP.Host, cfg.HTTP.Port)
128 | httpServerScope := scope.Tagged(map[string]string{
129 | "server-type": "http",
130 | })
131 | httpServerInstrumentOpts := instrumentOpts.SetMetricsScope(httpServerScope)
132 | httpServerOpts := cfg.HTTP.NewServerOptions(httpServerInstrumentOpts)
133 | server, err := http.NewServer(listenAddr, httpServerOpts, r2Service, healthService)
134 | if err != nil {
135 | logger.Fatalf("could not create new server: %v", err)
136 | }
137 |
138 | logger.Infof("starting HTTP server on: %s", listenAddr)
139 | if err := server.ListenAndServe(); err != nil {
140 | logger.Fatalf("could not start serving traffic: %v", err)
141 | }
142 |
143 | // Handle interrupts.
144 | logger.Warnf("interrupt: %v", interrupt())
145 |
146 | doneCh := make(chan struct{})
147 | go func() {
148 | server.Close()
149 | logger.Infof("HTTP server closed")
150 | doneCh <- struct{}{}
151 | }()
152 |
153 | select {
154 | case <-doneCh:
155 | logger.Infof("clean shutdown")
156 | case <-time.After(gracefulShutdownTimeout):
157 | logger.Warnf("forced shutdown due to timeout after waiting for %v", gracefulShutdownTimeout)
158 | }
159 | }
160 |
161 | func interrupt() error {
162 | c := make(chan os.Signal, 1)
163 | signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
164 | return fmt.Errorf("%s", <-c)
165 | }
166 |
--------------------------------------------------------------------------------
/tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "Tools": [
3 | {
4 | "Repository": "github.com/rakyll/statik",
5 | "Commit": "19b88da8fc15428620782ba18f68423130e7ac7d"
6 | }
7 | ],
8 | "RetoolVersion": "1.3.7"
9 | }
10 |
--------------------------------------------------------------------------------
/ui/.env:
--------------------------------------------------------------------------------
1 | NODE_PATH=src
--------------------------------------------------------------------------------
/ui/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": [
4 | "react-app",
5 | "plugin:flowtype/recommended",
6 | "plugin:react/recommended",
7 | "eslint-config-uber-es2015",
8 | "eslint-config-uber-jsx",
9 | "prettier",
10 | "prettier/flowtype",
11 | "prettier/react"
12 | ],
13 |
14 | "plugins": [
15 | "import",
16 | "flowtype",
17 | "react",
18 | "prettier"
19 | ],
20 |
21 | "env": {
22 | "jest": true,
23 | "browser": true
24 | },
25 |
26 | "rules": {
27 | "no-inline-comments": 1,
28 | "max-len": 0,
29 | "func-style": 1,
30 | "valid-jsdoc": 2,
31 | "import/no-unresolved": "off",
32 | "import/named": 2,
33 | "import/namespace": 2,
34 | "import/default": 2,
35 | "import/export": 2,
36 | "flowtype/generic-spacing": 0,
37 | "prettier/prettier": ["error", "fb"],
38 | "react/no-string-refs": 0,
39 | "react/jsx-key": 0,
40 | "react/no-children-prop": 0
41 | }
42 | }
--------------------------------------------------------------------------------
/ui/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/ui/.nvmrc:
--------------------------------------------------------------------------------
1 | 6
2 |
--------------------------------------------------------------------------------
/ui/README.md:
--------------------------------------------------------------------------------
1 | # R2 UI
2 |
3 | Note: Work in progess
4 |
5 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
6 |
7 | ### Running the Application
8 |
9 | First, install yarn for faster npm install
10 | ```
11 | brew install yarn
12 | ```
13 |
14 | We need atleast Node v6, to switch use nvm:
15 | ```
16 | nvm install 6
17 | nvm use 6
18 | ```
19 |
20 | Install dependencies
21 |
22 | ```
23 | yarn install
24 | ```
25 |
26 | Run the UI in dev mode. The API is expected to be available at: http://localhost:9000/r2/v1/
27 |
28 | ```
29 | npm start
30 | ```
--------------------------------------------------------------------------------
/ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "r2-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "proxy": "http://localhost:9000",
6 | "dependencies": {
7 | "antd": "^2.12.5",
8 | "axios": "^0.16.2",
9 | "basscss": "^8.0.3",
10 | "date-fns": "^1.29.0",
11 | "formik": "^0.9.4",
12 | "lodash": "^4.17.4",
13 | "moment": "2.18.1",
14 | "nprogress": "^0.2.0",
15 | "qs": "^6.5.1",
16 | "react": "^15.6.1",
17 | "react-dom": "^15.6.1",
18 | "react-refetch": "^1.0.0",
19 | "react-router-dom": "^4.1.2",
20 | "react-scripts": "1.0.10",
21 | "recompose": "^0.24.0",
22 | "yup": "^0.22.0"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "build": "react-scripts build",
27 | "test": "react-scripts test --env=jsdom",
28 | "eject": "react-scripts eject",
29 | "add-license": "cd src && uber-licence",
30 | "lint": "eslint src"
31 | },
32 | "devDependencies": {
33 | "babel-eslint": "7.2.3",
34 | "eslint": "3.19.0",
35 | "eslint-config-prettier": "^2.3.0",
36 | "eslint-config-react-app": "^1.0.5",
37 | "eslint-config-uber-es2015": "^3.1.2",
38 | "eslint-config-uber-jsx": "^3.3.3",
39 | "eslint-plugin-flowtype": "2.33.0",
40 | "eslint-plugin-import": "2.2.0",
41 | "eslint-plugin-jsx-a11y": "5.0.1",
42 | "eslint-plugin-prettier": "^2.1.2",
43 | "eslint-plugin-react": "7.0.1",
44 | "prettier": "^1.5.3",
45 | "uber-licence": "^3.1.1"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/ui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/m3db/m3ctl/65204284e1d73ef9b6d56163cfcddfd93b627921/ui/public/favicon.ico
--------------------------------------------------------------------------------
/ui/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | R2Ctrl
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/ui/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/ui/src/components/HelpTooltip.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Popover, Icon} from 'antd';
3 | import {getHelpText} from 'utils/helpText';
4 |
5 | export default function HelpTooltip({helpTextKey, title, ...rest}) {
6 | return (
7 | {getHelpText(helpTextKey)}}
10 | trigger="click"
11 | {...rest}>
12 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/ui/src/components/MappingRuleEditor.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {Button, Input, Form} from 'antd';
23 | import {withFormik} from 'formik';
24 | import * as util from 'utils';
25 | import yup from 'yup';
26 | import PoliciesEditor from './PolicyEditor';
27 | import {filterPoliciesBasedOnTag} from 'utils';
28 | import {getHelpText} from 'utils/helpText';
29 | const schema = yup.object().shape({
30 | name: yup.string('Name filter is required').required(),
31 | filter: yup.string().required('Metric filter is required'),
32 | });
33 |
34 | const FormItem = Form.Item;
35 | const formItemLayout = {
36 | labelCol: {
37 | xs: {span: 24},
38 | sm: {span: 4},
39 | },
40 | wrapperCol: {
41 | xs: {span: 24},
42 | sm: {span: 18},
43 | },
44 | };
45 | function MappingRuleEditor({
46 | mappingRule,
47 | values,
48 | handleChange,
49 | handleSubmit,
50 | setFieldValue,
51 | ...rest
52 | }) {
53 | const typeTag = util.getTypeTag(values.filter);
54 |
55 | return (
56 |
113 | );
114 | }
115 |
116 | export default withFormik({
117 | enableReinitialize: true,
118 | mapPropsToValues: ({mappingRule}) => {
119 | return mappingRule || {};
120 | },
121 | handleSubmit: (values, {props}) => {
122 | props.onSubmit(values);
123 | },
124 | validationSchema: schema,
125 | })(MappingRuleEditor);
126 |
--------------------------------------------------------------------------------
/ui/src/components/MappingRulesTable.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {Table, Tag} from 'antd';
23 | import _ from 'lodash';
24 | import MappingRuleEditor from 'components/MappingRuleEditor';
25 | import TableActions from './TableActions';
26 | import {compose} from 'recompose';
27 | import {connectR2API} from 'hocs';
28 | import {formatTimestampMilliseconds} from 'utils';
29 | import HelpTooltip from './HelpTooltip';
30 | const {Column} = Table;
31 |
32 | function MappingRuleHistoryBase(props) {
33 | const loading = _.get(props.mappingRuleHistory, 'pending');
34 | const mappingRules = _.get(props.mappingRuleHistory, 'value.mappingRules');
35 | return (
36 | index}
38 | mappingRules={mappingRules}
39 | loading={loading}
40 | showActions={false}
41 | />
42 | );
43 | }
44 | export const MappingRuleHistory = compose(
45 | connectR2API(props => {
46 | const {mappingRuleID, namespaceID} = props;
47 | return {
48 | mappingRuleHistory: {
49 | url: `/namespaces/${namespaceID}/mapping-rules/${mappingRuleID}/history`,
50 | },
51 | };
52 | }),
53 | )(MappingRuleHistoryBase);
54 |
55 | function MappingRulesTable(props) {
56 | const {
57 | namespaceID,
58 | loading,
59 | mappingRules,
60 | showActions = true,
61 | rowKey = 'id',
62 | saveMappingRule,
63 | deleteMappingRule,
64 | } = props;
65 | return (
66 |
232 | );
233 | }
234 |
235 | export default PoliciesEditor;
236 |
--------------------------------------------------------------------------------
/ui/src/components/RollupRuleEditor.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {Button, Input, Form, Icon, Select, Card, Tag} from 'antd';
23 | import {toClass} from 'recompose';
24 | import _ from 'lodash';
25 | import {withFormik} from 'formik';
26 | import * as util from 'utils';
27 | import PoliciesEditor from './PolicyEditor';
28 | import {filterPoliciesBasedOnTag} from 'utils';
29 | import {getHelpText} from 'utils/helpText';
30 | import HelpTooltip from './HelpTooltip';
31 |
32 | // @TODO Move to config service
33 | const REQUIRED_TAGS = ['dc', 'env', 'service', 'type'];
34 |
35 | const FormItem = Form.Item;
36 | const formItemLayout = {
37 | labelCol: {
38 | xs: {span: 24},
39 | sm: {span: 4},
40 | },
41 | wrapperCol: {
42 | xs: {span: 24},
43 | sm: {span: 18},
44 | },
45 | };
46 |
47 | function removeRequiredTags(tags, requiredTags) {
48 | return _.filter(tags, t => !_.includes(requiredTags, t));
49 | }
50 |
51 | function addRequiredTags(tags, requiredTags) {
52 | return _.concat(tags, requiredTags);
53 | }
54 |
55 | function RollupRuleEditor({values, handleChange, handleSubmit, setFieldValue}) {
56 | const typeTag = util.getTypeTag(values.filter);
57 | return (
58 |
121 | );
122 | }
123 |
124 | const TargetsEditorBase = props => {
125 | const {value: targets = [], onChange, typeTag} = props;
126 | const handleChange = (index, property, newValue) => {
127 | targets[index][property] = newValue;
128 | onChange(targets);
129 | };
130 | return (
131 |
189 | );
190 | };
191 | const TargetsEditor = toClass(TargetsEditorBase);
192 |
193 | export default withFormik({
194 | enableReinitialize: true,
195 | mapPropsToValues: ({rollupRule}) => {
196 | return rollupRule || {};
197 | },
198 | handleSubmit: (values, {props}) => {
199 | props.onSubmit({
200 | ...values,
201 | targets: _.map(values.targets, target => {
202 | return {
203 | ...target,
204 | tags: _.union(target.tags, REQUIRED_TAGS),
205 | };
206 | }),
207 | });
208 | },
209 | })(RollupRuleEditor);
210 |
--------------------------------------------------------------------------------
/ui/src/components/RollupRulesTable.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {Table, Popover, Tag} from 'antd';
23 | import _ from 'lodash';
24 |
25 | import RollupRuleEditor from 'components/RollupRuleEditor';
26 | import TableActions from './TableActions';
27 | import HelpTooltip from './HelpTooltip';
28 | import {compose} from 'recompose';
29 | import {connectR2API} from 'hocs';
30 | import {formatTimestampMilliseconds} from 'utils';
31 |
32 | const {Column} = Table;
33 |
34 | function TargetPreview({target}) {
35 | const content = (
36 |
192 | );
193 | }
194 |
195 | export default RollupRulesTable;
196 |
--------------------------------------------------------------------------------
/ui/src/components/TableActions.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {Icon, Popconfirm, Tooltip} from 'antd';
23 |
24 | function TableActions(props) {
25 | const {
26 | onDeleteClicked,
27 | onEditClicked,
28 | onHistoryClicked = noop => noop,
29 | } = props;
30 | return (
31 |
54 | );
55 | }
56 |
57 | export default TableActions;
58 |
--------------------------------------------------------------------------------
/ui/src/hocs/api.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {message} from 'antd';
23 | import NProgress from 'nprogress';
24 | import _ from 'lodash';
25 | import {connect} from 'react-refetch';
26 |
27 | const BASE_URL = '/r2/v1';
28 | function getErrorMessage(errorMessage) {
29 | return (
30 |
31 | {errorMessage}
32 |
33 | );
34 | }
35 | function customFetch(mapping) {
36 | const meta = _.get(mapping, 'meta', {});
37 | if (meta.loadingMessage || meta.successMessage) {
38 | message.destroy();
39 | message.loading(
40 | _.isString(meta.loadingMessage) ? meta.loadingMessage : 'Loading...',
41 | 0,
42 | );
43 | }
44 | const options = {
45 | method: mapping.method,
46 | cache: 'force-cache',
47 | headers: mapping.headers,
48 | credentials: mapping.credentials,
49 | redirect: mapping.redirect,
50 | body: JSON.stringify(mapping.body),
51 | };
52 | const req = new Request(BASE_URL + mapping.url, options);
53 |
54 | NProgress.start();
55 | return fetch(req).then(response => {
56 | NProgress.done();
57 | if (!response.ok) {
58 | const clonedResponse = response.clone();
59 | clonedResponse.json().then(t => {
60 | message.destroy();
61 | message.error(getErrorMessage(`[${response.status}] ${t.message}`), 5);
62 | });
63 | } else if (meta.successMessage) {
64 | message.destroy();
65 | message.success(
66 | getErrorMessage(
67 | _.isString(meta.successMessage) ? meta.successMessage : 'Success!',
68 | ),
69 | 1,
70 | );
71 | }
72 | return response;
73 | });
74 | }
75 |
76 | export const connectR2API = connect.defaults({
77 | fetch: customFetch,
78 | refetching: true,
79 | force: true,
80 | buildRequest: mapping => mapping,
81 | });
82 |
--------------------------------------------------------------------------------
/ui/src/hocs/filter.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash';
2 | import {withProps, withHandlers, compose} from 'recompose';
3 | import qs from 'query-string';
4 |
5 | export const withFilter = ({
6 | propMapper,
7 | propName,
8 | propField,
9 | queryParam = 'q',
10 | filterPropName = 'filter',
11 | filterChangeHandlerName = 'setFilter',
12 | }) => {
13 | return compose(
14 | withProps(props => {
15 | const prop = propMapper(props);
16 | const filter = qs.parse(props.location.search.replace('?', ''))[
17 | queryParam
18 | ];
19 |
20 | return {
21 | [filterPropName]: filter,
22 | [propName]: _.isEmpty(filter)
23 | ? prop
24 | : _.filter(prop, n => n[propField].indexOf(filter) !== -1),
25 | };
26 | }),
27 | withHandlers({
28 | [filterChangeHandlerName]: props => filter => {
29 | props.history.replace({
30 | pathname: window.location.pathname,
31 | search: filter ? `?${queryParam}=${filter}` : '',
32 | });
33 | },
34 | }),
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/ui/src/hocs/index.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | export * from './promiseState';
22 | export * from './api';
23 | export * from './filter';
24 |
--------------------------------------------------------------------------------
/ui/src/hocs/promiseState.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import {lifecycle} from 'recompose';
22 | import {PromiseState} from 'react-refetch';
23 | import _ from 'lodash';
24 |
25 | export function withPromiseStateChangeCallback(keys, fn) {
26 | return lifecycle({
27 | componentDidUpdate(prevProps) {
28 | const props = this.props;
29 | const filteredProps = keys ? _.pick(props, keys) : props;
30 | _.forEach(filteredProps, (prop, propName) => {
31 | if (prop instanceof PromiseState) {
32 | const promiseState = prop;
33 | const prevPromiseState = prevProps[propName];
34 | // Dont do anything
35 | if (
36 | prevPromiseState &&
37 | (prevPromiseState.pending !== promiseState.pending ||
38 | prevPromiseState.refreshing !== promiseState.refreshing)
39 | ) {
40 | fn(props);
41 | }
42 | }
43 | });
44 | },
45 | });
46 | }
47 |
48 | export default {
49 | withPromiseStateChangeCallback,
50 | };
51 |
--------------------------------------------------------------------------------
/ui/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | #root {
7 | height: 100%
8 | }
9 |
10 | .ant-form-item-control {
11 | line-height: inherit
12 | }
--------------------------------------------------------------------------------
/ui/src/index.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import ReactDOM from 'react-dom';
23 | import {
24 | BrowserRouter as Router,
25 | Route,
26 | Link,
27 | withRouter,
28 | matchPath,
29 | Redirect,
30 | } from 'react-router-dom';
31 | import {Layout, Menu, Icon, LocaleProvider} from 'antd';
32 | import enUS from 'antd/lib/locale-provider/en_US';
33 |
34 | import 'antd/dist/antd.css';
35 | import 'basscss/css/basscss.css';
36 | import 'nprogress/nprogress.css';
37 | import './index.css';
38 |
39 | import NamespacesPage from 'pages/Namespaces';
40 | import NamespacePage from 'pages/Namespace';
41 | const {Sider, Content} = Layout;
42 |
43 | const MenuSideBar = withRouter(({location}) => {
44 | let selectedKey;
45 | if (
46 | matchPath(location.pathname, {
47 | path: '/namespaces',
48 | })
49 | ) {
50 | selectedKey = 'namespaces';
51 | }
52 |
53 | return (
54 |
67 | );
68 | });
69 |
70 | function AppContainer() {
71 | return (
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
86 |
87 |
88 |
93 |
94 | }
98 | />
99 |
100 |
101 |
102 |
103 |
104 | );
105 | }
106 |
107 | ReactDOM.render(, document.getElementById('root'));
108 |
--------------------------------------------------------------------------------
/ui/src/pages/Namespace.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2017 Uber Technologies, Inc.
2 | //
3 | // Permission is hereby granted, free of charge, to any person obtaining a copy
4 | // of this software and associated documentation files (the "Software"), to deal
5 | // in the Software without restriction, including without limitation the rights
6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | // copies of the Software, and to permit persons to whom the Software is
8 | // furnished to do so, subject to the following conditions:
9 | //
10 | // The above copyright notice and this permission notice shall be included in
11 | // all copies or substantial portions of the Software.
12 | //
13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | // THE SOFTWARE.
20 |
21 | import React from 'react';
22 | import {Breadcrumb, Card, Tabs, Button, Modal, Input} from 'antd';
23 | import {Link} from 'react-router-dom';
24 | import _ from 'lodash';
25 | import {compose, withProps, withReducer} from 'recompose';
26 | import {connectR2API, withPromiseStateChangeCallback, withFilter} from 'hocs';
27 | import MappingRuleEditor from 'components/MappingRuleEditor';
28 | import MappingRulesTable from 'components/MappingRulesTable';
29 | import RollupRuleEditor from 'components/RollupRuleEditor';
30 | import RollupRulesTable from 'components/RollupRulesTable';
31 | import {getHelpText} from 'utils/helpText';
32 |
33 | const TabPane = Tabs.TabPane;
34 |
35 | function Namespace(props) {
36 | const {namespaceID, mappingRules, rollupRules, loading} = props;
37 | return (
38 |