├── test ├── boilerplate │ ├── boilerplate.js.preamble │ ├── boilerplate.py.preamble │ ├── boilerplate.sh.preamble │ ├── boilerplate.xml.preamble │ ├── boilerplate.go.preamble │ ├── boilerplate.html.preamble │ ├── boilerplate.css.txt │ ├── boilerplate.go.txt │ ├── boilerplate.tf.txt │ ├── boilerplate.bzl.txt │ ├── boilerplate.py.txt │ ├── boilerplate.sh.txt │ ├── boilerplate.yaml.txt │ ├── boilerplate.BUILD.txt │ ├── boilerplate.Makefile.txt │ ├── boilerplate.WORKSPACE.txt │ ├── boilerplate.bazel.txt │ ├── boilerplate.xml.txt │ ├── boilerplate.Dockerfile.txt │ ├── boilerplate.html.txt │ ├── boilerplate.java.txt │ ├── boilerplate.js.txt │ ├── boilerplate.scss.txt │ └── boilerplate.ts.txt ├── make.sh └── verify_boilerplate.py ├── public ├── favicon.ico ├── manifest.json └── index.html ├── .gitignore ├── .github └── workflows │ └── main.yml ├── src ├── gke-ipam.js ├── App.test.js ├── index.css ├── App.css ├── index.js ├── App.js ├── rules │ ├── output │ │ ├── ServiceOutpuRules.js │ │ ├── NodeOutputRules.test.js │ │ ├── PodOutputRules.js │ │ └── NodeOutputRules.js │ └── input │ │ ├── PodNetmaskRules.js │ │ ├── AvailableNetmaskRules.js │ │ ├── BaseNetmaskRules.test.js │ │ ├── BaseNetmaskRules.js │ │ ├── node │ │ └── NodeRules.js │ │ ├── cluster │ │ └── ClusterRules.js │ │ ├── service │ │ └── ServiceRules.js │ │ └── InputRules.js ├── ui │ ├── output │ │ ├── LogicOutputFreeNetworkRow.js │ │ ├── Results.js │ │ ├── LogicOutputNetworkRow.js │ │ ├── LogicOutputDropdown.js │ │ └── LogicOutput.js │ └── input │ │ └── Parameters.js ├── calculate.js ├── logic │ ├── IPUtils.test.js │ ├── Packer.js │ ├── IPUtils.js │ ├── Packer.test.js │ └── Logic.js ├── components │ ├── TextDropdownInput.js │ ├── NetmaskInput.js │ ├── TextDropdown.js │ ├── NetmaskDropdown.js │ └── StateButtons.js ├── logo.svg ├── Calculator.js └── serviceWorker.js ├── package.json ├── CONTRIBUTING.md ├── .eslintrc.js ├── Jenkinsfile ├── Makefile ├── README.md └── LICENSE /test/boilerplate/boilerplate.js.preamble: -------------------------------------------------------------------------------- 1 | #! 2 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.py.preamble: -------------------------------------------------------------------------------- 1 | #! 2 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.sh.preamble: -------------------------------------------------------------------------------- 1 | #! 2 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.xml.preamble: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.Dockerfile.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.html.txt: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.java.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.js.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.scss.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /test/boilerplate/boilerplate.ts.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master # Trigger the workflow on push to master 7 | 8 | jobs: 9 | build: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v1 15 | - name: Create the production build 16 | run: make basefiles npm-install npm-test npm-build 17 | env: 18 | IPAM_HOST_URL: https://googlecloudplatform.github.io/gke-ip-address-management/ 19 | - name: Push changes 20 | uses: peaceiris/actions-gh-pages@v2 21 | env: 22 | ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }} 23 | PUBLISH_BRANCH: gh-pages 24 | PUBLISH_DIR: ./build 25 | -------------------------------------------------------------------------------- /src/gke-ipam.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | // Copyright 2019 Google LLC 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | 16 | // Transpile all code following this line with babel and use 'env' (aka ES6) preset. 17 | require('babel-register')({ 18 | presets: ['env'], 19 | }); 20 | 21 | // Import the rest of our application. 22 | module.exports = require('./calculate.js'); 23 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React from 'react'; 16 | import ReactDOM from 'react-dom'; 17 | import App from './App'; 18 | 19 | // This test will execute a lot of code, so it will skew the coverage report. 20 | it('renders without crashing', () => { 21 | const div = document.createElement('div'); 22 | ReactDOM.render(, div); 23 | ReactDOM.unmountComponentAtNode(div); 24 | }); 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | body { 18 | margin: 0; 19 | padding: 0; 20 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 21 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 22 | sans-serif; 23 | -webkit-font-smoothing: antialiased; 24 | -moz-osx-font-smoothing: grayscale; 25 | } 26 | 27 | code { 28 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 29 | monospace; 30 | } 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gke-net", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "babel-preset-env": "^1.7.0", 7 | "babel-register": "^6.26.0", 8 | "bootstrap": "^4.3.1", 9 | "minimist": "^1.2.3", 10 | "react": "^16.6.0", 11 | "react-dom": "^16.6.0", 12 | "react-scripts": "^4.0.0", 13 | "reactstrap": "^6.5.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject", 20 | "lint": "eslint src", 21 | "calculate": "src/calculate.js" 22 | }, 23 | "eslintConfig": { 24 | "extends": "react-app" 25 | }, 26 | "browserslist": [ 27 | ">0.2%", 28 | "not dead", 29 | "not ie <= 11", 30 | "not op_mini all" 31 | ], 32 | "devDependencies": { 33 | "eslint-config-airbnb": "^17.1.1", 34 | "eslint-config-google": "^0.13.0", 35 | "eslint-plugin-import": "^2.18.2", 36 | "eslint-plugin-jsx-a11y": "^6.2.3", 37 | "eslint-plugin-react": "^7.14.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 Google LLC 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | .App { 18 | text-align: center; 19 | } 20 | 21 | .App-logo { 22 | animation: App-logo-spin infinite 20s linear; 23 | height: 40vmin; 24 | } 25 | 26 | 27 | .App-header { 28 | background-color: #222; 29 | height: 115px; 30 | padding: 10px; 31 | color: white; 32 | } 33 | 34 | 35 | .App-link { 36 | color: #61dafb; 37 | } 38 | 39 | @keyframes App-logo-spin { 40 | from { 41 | transform: rotate(0deg); 42 | } 43 | to { 44 | transform: rotate(360deg); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | Contributions to this project must be accompanied by a Contributor License 8 | Agreement. You (or your employer) retain the copyright to your contribution; 9 | this simply gives us permission to use and redistribute your contributions as 10 | part of the project. Head over to https://cla.developers.google.com/ to see your 11 | current agreements on file or to sign a new one. 12 | 13 | You generally only need to submit a CLA once, so if you've already submitted one 14 | (even if it was for a different project), you probably don't need to do it again. 15 | 16 | ## Code reviews 17 | All submissions, including submissions by project members, require review. We 18 | use GitHub pull requests for this purpose. Consult GitHub Help for more 19 | information on using pull requests. 20 | 21 | ## Community Guidelines 22 | This project follows [Google's Open Source Community 23 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React from 'react'; 16 | import ReactDOM from 'react-dom'; 17 | import './index.css'; 18 | import App from './App'; 19 | import * as serviceWorker from './serviceWorker'; 20 | import 'bootstrap/dist/css/bootstrap.min.css'; 21 | 22 | ReactDOM.render( < App / >, document.getElementById('root')); 23 | 24 | // If you want your app to work offline and load faster, you can change 25 | // unregister() to register() below. Note this comes with some pitfalls. 26 | // Learn more about service workers: http://bit.ly/CRA-PWA 27 | serviceWorker.unregister(); 28 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, { 16 | Component, 17 | } from 'react'; 18 | import './App.css'; 19 | import Calculator from './Calculator'; 20 | 21 | /** 22 | * Main application class. 23 | */ 24 | class App extends Component { 25 | /** 26 | * Render the main application component. 27 | * 28 | * @return {Object} the main
for the application. 29 | */ 30 | render() { 31 | return (
32 |
33 |

GKE IP Address Management

34 |
35 | 36 | 37 | 38 |
39 | ); 40 | } 41 | } 42 | 43 | export default App; 44 | -------------------------------------------------------------------------------- /src/rules/output/ServiceOutpuRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import IPUtils from '../../logic/IPUtils'; 16 | 17 | /** 18 | * Rules for the Service subnet range 19 | */ 20 | class ServiceOutputRules { 21 | /** 22 | * Constructor. 23 | * 24 | * @param {Input} input 25 | */ 26 | constructor(input) { 27 | this.input = input; 28 | } 29 | 30 | /** 31 | * Returns the maximum number of usable IPs. 32 | * 33 | * @return {number} maximum number of usable IPs. 34 | */ 35 | maximum() { 36 | const serviceIps = IPUtils.netmaskToUsableIps(this.input.serviceNetmask); 37 | return serviceIps; 38 | } 39 | } 40 | 41 | export default ServiceOutputRules; 42 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | module.exports = { 16 | env: { 17 | browser: true, 18 | node: true, 19 | es6: true, 20 | jest: true 21 | }, 22 | parser: "babel-eslint", 23 | extends: ["eslint:recommended", "plugin:react/recommended", "google"], 24 | globals: { 25 | Atomics: "readonly", 26 | SharedArrayBuffer: "readonly" 27 | }, 28 | parserOptions: { 29 | ecmaFeatures: { 30 | jsx: true 31 | }, 32 | ecmaVersion: 2018, 33 | sourceType: "module" 34 | }, 35 | plugins: ["react"], 36 | rules: { 37 | "max-len": ["error", { code: 120 }], 38 | "no-invalid-this": ["off"] 39 | }, 40 | settings: { 41 | react: { 42 | version: "detect" 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/rules/output/NodeOutputRules.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {NodeNetworkRules, ClusterNetworkRules} from './NodeOutputRules'; 16 | 17 | it('NodeNetworkRules calculates the right value', () => { 18 | const r1 = new NodeNetworkRules({nodeNetmask: 24}); 19 | expect(r1.maximum()).toEqual(252); 20 | const r2 = new NodeNetworkRules({nodeNetmask: 16}); 21 | expect(r2.maximum()).toEqual(65532); 22 | }); 23 | 24 | it('ClusterNetworkRules calculates the right value', () => { 25 | const r1 = new ClusterNetworkRules({clusterNetmask: 22, nodePodNetmask: 24}); 26 | expect(r1.maximum()).toEqual(4); 27 | const r2 = new ClusterNetworkRules({clusterNetmask: 20, nodePodNetmask: 24}); 28 | expect(r2.maximum()).toEqual(16); 29 | }); 30 | -------------------------------------------------------------------------------- /src/rules/output/PodOutputRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import {nodePodNetmaskOptions} from '../../ui/input/Parameters'; 16 | 17 | /** 18 | * Rules for the Pod subnet range 19 | */ 20 | class PodOutputRules { 21 | /** 22 | * Constructor. 23 | * 24 | * @param {Input} input 25 | */ 26 | constructor(input) { 27 | this.input = input; 28 | } 29 | 30 | /** 31 | * Returns the maximum number of usable IPs. 32 | * 33 | * @return {number} maximum number of usable IPs. 34 | */ 35 | maximum() { 36 | let i; 37 | for (i = 0; i < nodePodNetmaskOptions.length; ++i) { 38 | if (nodePodNetmaskOptions[i].value === this.input.nodePodNetmask) { 39 | return nodePodNetmaskOptions[i].max; 40 | } 41 | } 42 | return NaN; 43 | } 44 | } 45 | 46 | export default PodOutputRules; 47 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | /* 3 | Copyright 2019 Google LLC 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | https://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | 17 | */ 18 | 19 | def label = "gke-ipam-${UUID.randomUUID().toString()}" 20 | podTemplate(label: label, containers: [ 21 | containerTemplate(name: 'node', image: 'node:12.7.0-stretch', ttyEnabled: true, command: 'cat'), 22 | containerTemplate(name: 'python', image: 'python:3.7.4-buster', ttyEnabled: true, command: 'cat'), 23 | ]) { 24 | node(label) { 25 | container('python') { 26 | stage('Checkout') { 27 | checkout scm 28 | } 29 | stage('Verify Headers') { 30 | sh 'make verify-header' 31 | } 32 | } 33 | container('node') { 34 | stage('Install dependencies') { 35 | sh 'make npm-install' 36 | } 37 | stage('Run tests') { 38 | sh 'make npm-test' 39 | } 40 | stage('Run lint') { 41 | sh 'make lint' 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/rules/input/PodNetmaskRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import BaseNetmaskRules from './BaseNetmaskRules'; 16 | 17 | /** 18 | * Hardcoded minimum limit for pod ranges 19 | */ 20 | class HardcodedMin { 21 | /** 22 | * Return the minimum for the range 23 | * 24 | * @return {number} range minimum 25 | */ 26 | minimum() { 27 | return 28; 28 | } 29 | } 30 | 31 | /** 32 | * Hardcoded max limit for pod ranges 33 | */ 34 | class HardcodedMax { 35 | /** 36 | * Return the max for the range 37 | * 38 | * @return {number} range max 39 | */ 40 | maximum() { 41 | return 24; 42 | } 43 | } 44 | 45 | 46 | /** 47 | * Rules for available range of pod netmask options. 48 | */ 49 | class PodNetmastRules extends BaseNetmaskRules { 50 | /** 51 | * Constructor. 52 | */ 53 | constructor() { 54 | super([new HardcodedMin()], [new HardcodedMax()]); 55 | } 56 | } 57 | 58 | export default PodNetmastRules; 59 | -------------------------------------------------------------------------------- /src/rules/input/AvailableNetmaskRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import BaseNetmaskRules from './BaseNetmaskRules'; 16 | 17 | /** 18 | * Rule that define the minimum available netmask 19 | */ 20 | class HardcodedMin { 21 | /** 22 | * Calculate the minimum netmask possible. 23 | * 24 | * @return {number} minimum netmask possible. 25 | */ 26 | minimum() { 27 | return 29; 28 | } 29 | } 30 | 31 | /** 32 | * Rule that define the maximum available netmask 33 | */ 34 | class HardcodedMax { 35 | /** 36 | * Calculate the maximum netmask possible. 37 | * 38 | * @return {number} maximum netmask possible. 39 | */ 40 | maximum() { 41 | return 8; 42 | } 43 | } 44 | 45 | /** 46 | * Rules that define the available netmasks to show 47 | */ 48 | class AvailableNetmaskRules extends BaseNetmaskRules { 49 | /** 50 | * 51 | * Constructor. 52 | */ 53 | constructor() { 54 | super([new HardcodedMin()], [new HardcodedMax()]); 55 | } 56 | } 57 | 58 | export default AvailableNetmaskRules; 59 | -------------------------------------------------------------------------------- /src/ui/output/LogicOutputFreeNetworkRow.js: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import '../../App.css'; 17 | import IPUtils from '../../logic/IPUtils'; 18 | import PropTypes from 'prop-types'; 19 | 20 | /** 21 | * A row for an available or free subnet. 22 | */ 23 | class LogicOutputFreeNetworkRow extends Component { 24 | /** 25 | * Render the row. 26 | * 27 | * @return {Object} the table row for the network. 28 | */ 29 | render() { 30 | return ( 31 | 32 | 33 | {' '} 34 | {IPUtils.decToDot(this.props.network.netStart)}/ 35 | {this.props.network.mask} 36 | 37 | {IPUtils.decToDot(this.props.network.netStart)} 38 | {IPUtils.decToDot(this.props.network.netEnd)} 39 | /{this.props.network.mask} 40 | 41 | ); 42 | } 43 | } 44 | 45 | LogicOutputFreeNetworkRow.propTypes = { 46 | network: PropTypes.object, 47 | }; 48 | 49 | export default LogicOutputFreeNetworkRow; 50 | -------------------------------------------------------------------------------- /src/rules/input/BaseNetmaskRules.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import BaseNetmaskRules from './BaseNetmaskRules'; 16 | 17 | /** 18 | * Mock rule 19 | */ 20 | class TestRule { 21 | /** 22 | * 23 | * Constructor. 24 | * 25 | * @param {number} min Hardcoded min. 26 | * @param {number} max Hardcoded max. 27 | */ 28 | constructor(min, max) { 29 | this.min = min; 30 | this.max = max; 31 | } 32 | 33 | /** 34 | * Calculate the minimum netmask possible. 35 | * 36 | * @return {number} minimum netmask possible. 37 | */ 38 | minimum() { 39 | return this.min; 40 | } 41 | 42 | /** 43 | * Calculate the max netmask possible. 44 | * 45 | * @return {number} max netmask possible. 46 | */ 47 | maximum() { 48 | return this.max; 49 | } 50 | } 51 | 52 | it('Find min', () => { 53 | const rule1 = new TestRule(1, 10); 54 | const rule2 = new TestRule(2, 15); 55 | const r = new BaseNetmaskRules([rule1, rule2], [rule1, rule2]); 56 | expect(r.maximum().maximum()).toEqual(15); 57 | expect(r.minimum().minimum()).toEqual(1); 58 | }); 59 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Make will use bash instead of sh 16 | SHELL := /usr/bin/env bash 17 | 18 | ROOT := ${CURDIR} 19 | 20 | 21 | 22 | # All is the first target in the file so it will get picked up when you just run 'make' on its own 23 | all: basefiles check_trailing_whitespace npm-install npm-test 24 | 25 | run: npm-install npm-start 26 | 27 | 28 | .PHONY: basefiles 29 | basefiles: 30 | @source test/make.sh && basefiles 31 | 32 | .PHONY: check_trailing_whitespace 33 | check_trailing_whitespace: 34 | @source test/make.sh && check_trailing_whitespace 35 | 36 | .PHONY: npm-install 37 | npm-install: 38 | npm install 39 | 40 | .PHONY: npm-test 41 | npm-test: export CI=true # This will instruct react-scripts to run in non-interative mode 42 | npm-test: 43 | npm test 44 | 45 | .PHONY: npm-start 46 | npm-start: 47 | npm start 48 | 49 | .PHONY: lint 50 | lint: 51 | npm run lint 52 | 53 | .PHONY: npm-build 54 | npm-build: 55 | @source build_scripts.sh && npm_build 56 | 57 | .PHONY: verify-header 58 | verify-header: 59 | python test/verify_boilerplate.py 60 | @echo "\n Test passed - Verified all file Apache 2 headers" -------------------------------------------------------------------------------- /src/calculate.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* eslint no-console: ["off"] */ 16 | import Logic from './Logic'; 17 | 18 | const argv = require('minimist')(process.argv.slice(2)); 19 | console.dir(argv); 20 | 21 | /** 22 | * Evaluates the input provided to the application, and outputs the result. 23 | * 24 | * @param {string} text Text input provided by the user 25 | */ 26 | function evaluateInput(text) { 27 | console.log(text); 28 | const input = JSON.parse(text); 29 | const logic = new Logic(input); 30 | console.log(JSON.stringify(logic.getCombinations())); 31 | } 32 | 33 | 34 | if ('input' in argv && argv['input'] != '-') { 35 | // Read from file 36 | const fs = require('fs'); 37 | 38 | const contents = fs.readFileSync(argv['input'], 'utf8'); 39 | evaluateInput(contents); 40 | } else { 41 | // Read from STDIN 42 | 43 | const stdin = process.stdin; 44 | const inputChunks = []; 45 | stdin.setEncoding('utf8'); 46 | 47 | stdin.on('data', function(chunk) { 48 | inputChunks.push(chunk); 49 | }); 50 | 51 | stdin.on('end', function() { 52 | const input = inputChunks.join(''); 53 | evaluateInput(input); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /src/rules/input/BaseNetmaskRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | const reducerMax = (accumulator, currentValue) => 16 | accumulator == null || currentValue.maximum() > accumulator.maximum() 17 | ? currentValue 18 | : accumulator; 19 | 20 | const reducerMin = (accumulator, currentValue) => 21 | accumulator == null || currentValue.maximum() < accumulator.maximum() 22 | ? currentValue 23 | : accumulator; 24 | 25 | /** 26 | * Base class for the rules that define the maximum and minimum netmasks to show 27 | */ 28 | class BaseNetmaskRules { 29 | /** 30 | * 31 | * Constructor. 32 | * 33 | * @param {Object} minRules 34 | * @param {Object} maxRules 35 | */ 36 | constructor(minRules, maxRules) { 37 | this.minRules = minRules; 38 | this.maxRules = maxRules; 39 | } 40 | 41 | /** 42 | * Calculate the minimum netmask possible. 43 | * 44 | * @return {number} minimum netmask possible. 45 | */ 46 | minimum() { 47 | return this.minRules.reduce(reducerMin); 48 | } 49 | 50 | /** 51 | * Calculate the max netmask possible. 52 | * 53 | * @return {number} max netmask possible. 54 | */ 55 | maximum() { 56 | return this.maxRules.reduce(reducerMax); 57 | } 58 | } 59 | 60 | export default BaseNetmaskRules; 61 | -------------------------------------------------------------------------------- /src/ui/output/Results.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import '../../App.css'; 17 | import LogicOutput from './LogicOutput'; 18 | import PropTypes from 'prop-types'; 19 | 20 | 21 | /** 22 | * Class for the results of the calculations 23 | */ 24 | class Results extends Component { 25 | /** 26 | * Render the actual results. 27 | * 28 | * @return {Object} the results or error message. 29 | */ 30 | printReslts() { 31 | if ( 32 | this.props.state.logic && 33 | this.props.state.logic.getCombinations() && 34 | this.props.state.logic.getCombinations().length > 0 35 | ) { 36 | return ( 37 | 41 | ); 42 | } else { 43 | return
Invalid Input
; 44 | } 45 | } 46 | 47 | 48 | /** 49 | * Render the results component. 50 | * 51 | * @return {Object} the main
for the results. 52 | */ 53 | render() { 54 | return ( 55 |
56 |
57 |

Results

58 | {this.printReslts()} 59 |
60 | ); 61 | } 62 | } 63 | 64 | Results.propTypes = { 65 | state: PropTypes.object, 66 | }; 67 | 68 | export default Results; 69 | -------------------------------------------------------------------------------- /src/rules/input/node/NodeRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import BaseNetmaskRules from '../BaseNetmaskRules'; 16 | 17 | /** 18 | * Hardcoded minimum limit for node ranges 19 | */ 20 | class HardcodedMin { 21 | /** 22 | * Return the minimum for the range 23 | * 24 | * @return {number} range minimum 25 | */ 26 | minimum() { 27 | return 29; 28 | } 29 | 30 | /** 31 | * Returns the reference documentation URL. 32 | * 33 | * @return {string} reference URL. 34 | */ 35 | ref() { 36 | return 'https://cloud.google.com/vpc/docs/vpc#manually_created_subnet_ip_ranges'; 37 | } 38 | } 39 | 40 | /** 41 | * Maximum limit for node ranges, based on available netmask 42 | */ 43 | class NetmaskMax { 44 | /** 45 | * Constructor. 46 | * 47 | * @param {Object} state Current state. 48 | */ 49 | constructor(state) { 50 | this.state = state; 51 | } 52 | 53 | /** 54 | * Return the max for the range 55 | * 56 | * @return {number} range max 57 | */ 58 | maximum() { 59 | return this.state.netmask; 60 | } 61 | } 62 | 63 | /** 64 | * Rules for available range of node netmask options. 65 | */ 66 | class NodeRules extends BaseNetmaskRules { 67 | /** 68 | * Constructor. 69 | * 70 | * @param {Object} state Current state. 71 | */ 72 | constructor(state) { 73 | super([new HardcodedMin()], [new NetmaskMax(state)]); 74 | } 75 | } 76 | 77 | export default NodeRules; 78 | -------------------------------------------------------------------------------- /src/rules/input/cluster/ClusterRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import BaseNetmaskRules from '../BaseNetmaskRules'; 16 | 17 | /** 18 | * Hardcoded minimum limit for cluster ranges 19 | */ 20 | class HardcodedMin { 21 | /** 22 | * Return the minimum for the range 23 | * 24 | * @return {number} range minimum 25 | */ 26 | minimum() { 27 | return 24; 28 | } 29 | 30 | 31 | /** 32 | * Returns the reference documentation URL. 33 | * 34 | * @return {string} reference URL. 35 | */ 36 | ref() { 37 | return 'https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips#defaults_limits'; 38 | } 39 | } 40 | 41 | 42 | /** 43 | * Maximum limit for cluster ranges, based on available netmask 44 | */ 45 | class NetmaskMax { 46 | /** 47 | * Constructor. 48 | * 49 | * @param {Object} state Current state. 50 | */ 51 | constructor(state) { 52 | this.state = state; 53 | } 54 | 55 | /** 56 | * Return the max for the range 57 | * 58 | * @return {number} range max 59 | */ 60 | maximum() { 61 | return this.state.netmask; 62 | } 63 | } 64 | 65 | /** 66 | * Rules for available range of cluster netmask options. 67 | */ 68 | class ClusterRules extends BaseNetmaskRules { 69 | /** 70 | * Constructor. 71 | * 72 | * @param {Object} state Current state. 73 | */ 74 | constructor(state) { 75 | super([new HardcodedMin()], [new NetmaskMax(state)]); 76 | } 77 | } 78 | 79 | export default ClusterRules; 80 | -------------------------------------------------------------------------------- /src/rules/input/service/ServiceRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import BaseNetmaskRules from '../BaseNetmaskRules'; 16 | 17 | /** 18 | * Hardcoded minimum limit for service ranges 19 | */ 20 | class HardcodedMin { 21 | /** 22 | * Return the minimum for the range 23 | * 24 | * @return {number} range minimum 25 | */ 26 | minimum() { 27 | return 27; 28 | } 29 | 30 | 31 | /** 32 | * Returns the reference documentation URL. 33 | * 34 | * @return {string} reference URL. 35 | */ 36 | ref() { 37 | return 'https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips#defaults_limits'; 38 | } 39 | } 40 | 41 | 42 | /** 43 | * Maximum limit for service ranges, based on available netmask 44 | */ 45 | class NetmaskMax { 46 | /** 47 | * Constructor. 48 | * 49 | * @param {Object} state Current state. 50 | */ 51 | constructor(state) { 52 | this.state = state; 53 | } 54 | 55 | /** 56 | * Return the max for the range 57 | * 58 | * @return {number} range max 59 | */ 60 | maximum() { 61 | return this.state.netmask; 62 | } 63 | } 64 | 65 | 66 | /** 67 | * Rules for available range of service netmask options. 68 | */ 69 | class ServiceRules extends BaseNetmaskRules { 70 | /** 71 | * Constructor. 72 | * 73 | * @param {Object} state Current state. 74 | */ 75 | constructor(state) { 76 | super([new HardcodedMin()], [new NetmaskMax(state)]); 77 | } 78 | } 79 | 80 | export default ServiceRules; 81 | -------------------------------------------------------------------------------- /src/logic/IPUtils.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import IPUtils from './IPUtils'; 16 | 17 | it('netStart calculation', () => { 18 | const dec = IPUtils.dotToDec('192.168.3.10'); 19 | expect(IPUtils.decToDot(IPUtils.netStart(dec, 24))).toEqual('192.168.3.0'); 20 | expect(IPUtils.decToDot(IPUtils.netStart(dec, 16))).toEqual('192.168.0.0'); 21 | }); 22 | 23 | it('dot notation to decimal', () => { 24 | expect(IPUtils.dotToDec('0.0.0.3')).toEqual(3); 25 | expect(IPUtils.dotToDec('0.0.1.50')).toEqual(306); 26 | expect(IPUtils.dotToDec('254.3.1.50')).toEqual(4261609778); 27 | }); 28 | 29 | it('decimal notation to dot', () => { 30 | expect(IPUtils.decToDot(3)).toEqual('0.0.0.3'); 31 | expect(IPUtils.decToDot(306)).toEqual('0.0.1.50'); 32 | expect(IPUtils.decToDot(4261609778)).toEqual('254.3.1.50'); 33 | }); 34 | 35 | it('netEnd calculation', () => { 36 | const dec = IPUtils.dotToDec('104.129.196.0'); 37 | expect(IPUtils.netEnd(dec, 24)).toEqual(IPUtils.dotToDec('104.129.196.255')); 38 | expect(IPUtils.netEnd(dec, 16)).toEqual(IPUtils.dotToDec('104.129.255.255')); 39 | }); 40 | 41 | it('Netmask calculation', () => { 42 | expect(IPUtils.netmaskToDecimal(24)).toEqual( 43 | IPUtils.dotToDec('255.255.255.0'), 44 | ); 45 | expect(IPUtils.netmaskToDecimal(8)).toEqual(IPUtils.dotToDec('255.0.0.0')); 46 | }); 47 | 48 | it('Find Largest Mask', () =>{ 49 | expect(IPUtils.findLargestMask(0, 255)).toEqual(24); 50 | expect(IPUtils.findLargestMask(0, 256)).toEqual(24); 51 | expect(IPUtils.findLargestMask(0, 257)).toEqual(23); 52 | expect(IPUtils.findLargestMask(0, 120)).toEqual(25); 53 | }); 54 | -------------------------------------------------------------------------------- /src/ui/output/LogicOutputNetworkRow.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import '../../App.css'; 17 | import IPUtils from '../../logic/IPUtils'; 18 | import PropTypes from 'prop-types'; 19 | 20 | /** 21 | * A row for a particular subnet definition 22 | */ 23 | class LogicOutputNetworkRow extends Component { 24 | /** 25 | * Pretty format the subnet type. 26 | * @param {type} type The subnet type. 27 | * @return {string} subnet type text. 28 | */ 29 | subnetTypeText(type) { 30 | if (type === 'PRIMARY') { 31 | return 'Primary'; 32 | } else if (type === 'SECONDARY') { 33 | return 'Secondary'; 34 | } else if (type === 'MANAGED') { 35 | return 'Google Managed'; 36 | } 37 | return 'N/A'; 38 | } 39 | 40 | /** 41 | * Render the row. 42 | * 43 | * @return {Object} the table row for the network. 44 | */ 45 | render() { 46 | return ( 47 | 48 | 49 | {' '} 50 | {IPUtils.decToDot(this.props.network.netStart)}/ 51 | {this.props.network.mask} 52 | 53 | {IPUtils.decToDot(this.props.network.netStart)} 54 | {IPUtils.decToDot(this.props.network.netEnd)} 55 | {this.props.network.name} 56 | {this.props.network.vpcName} 57 | {this.props.network.subnetRangeName} 58 | {this.subnetTypeText(this.props.network.type)} 59 | {this.props.network.description} 60 | 61 | ); 62 | } 63 | } 64 | 65 | LogicOutputNetworkRow.propTypes = { 66 | network: PropTypes.object, 67 | }; 68 | 69 | export default LogicOutputNetworkRow; 70 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 37 | GKE IPAM 38 | 39 | 40 | 43 |
44 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/components/TextDropdownInput.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import {FormGroup, Label, Col} from 'reactstrap'; 17 | import TextDropdown from './TextDropdown'; 18 | import PropTypes from 'prop-types'; 19 | 20 | /** 21 | * Component to display a Form element for a text dropdown 22 | */ 23 | class TextDropdownInput extends Component { 24 | /** 25 | * Constructor. 26 | * 27 | * @param {Object} props 28 | */ 29 | constructor(props) { 30 | super(props); 31 | this.handleChange = this.handleChange.bind(this); 32 | } 33 | 34 | /** 35 | * Handle selecting a different value. 36 | * 37 | * @param {Object} value the selection value. 38 | */ 39 | handleChange(value) { 40 | this.props.changeHandler(this.props.propName, value); 41 | } 42 | 43 | /** 44 | * Render the From element. 45 | * 46 | * @return {Object} the From element for a text dropdown. 47 | */ 48 | render() { 49 | return ( 50 | 51 | 54 | 55 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | TextDropdownInput.propTypes = { 69 | propName: PropTypes.string, 70 | label: PropTypes.string, 71 | options: PropTypes.array, 72 | name: PropTypes.string, 73 | value: PropTypes.string, 74 | changeHandler: PropTypes.func, 75 | }; 76 | 77 | 78 | export default TextDropdownInput; 79 | -------------------------------------------------------------------------------- /src/components/NetmaskInput.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import {FormGroup, Label, Col} from 'reactstrap'; 17 | import NetmaskDropdown from './NetmaskDropdown'; 18 | import PropTypes from 'prop-types'; 19 | 20 | 21 | /** 22 | * Component to display an FormGroup element that contains a dropdown to select netmask values. 23 | */ 24 | class NetmaskInput extends Component { 25 | /** 26 | * Constructor. 27 | * 28 | * @param {Object} props 29 | */ 30 | constructor(props) { 31 | super(props); 32 | this.handleChange = this.handleChange.bind(this); 33 | } 34 | 35 | /** 36 | * Handle selecting a different value. 37 | * 38 | * @param {Object} value the selected value. 39 | */ 40 | handleChange(value) { 41 | this.props.changeHandler(this.props.propName, value); 42 | } 43 | 44 | /** 45 | * Render the FormGroup element. 46 | * 47 | * @return {Object} the FormGroup element that contains the netmask dropdown. 48 | */ 49 | render() { 50 | return ( 51 | 52 | 55 | 56 | 62 | 63 | 64 | ); 65 | } 66 | } 67 | 68 | NetmaskInput.propTypes = { 69 | rules: PropTypes.object, 70 | value: PropTypes.number, 71 | label: PropTypes.string, 72 | name: PropTypes.string, 73 | propName: PropTypes.string, 74 | changeHandler: PropTypes.func, 75 | }; 76 | 77 | export default NetmaskInput; 78 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/rules/input/InputRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import ClusterRules from './cluster/ClusterRules'; 16 | import NodeRules from './node/NodeRules'; 17 | import ServiceRules from './service/ServiceRules'; 18 | import AvailableNetmaskRules from './AvailableNetmaskRules'; 19 | import PodNetmastRules from './PodNetmaskRules'; 20 | 21 | /** 22 | * Set of rules used to display the input UI 23 | */ 24 | class InputRules { 25 | /** 26 | * Constructor. 27 | * 28 | * @param {Object} input Current input. 29 | */ 30 | constructor(input) { 31 | this.clusterRules = new ClusterRules(input); 32 | this.nodeRules = new NodeRules(input); 33 | this.serviceRules = new ServiceRules(input); 34 | this.availableNetmaskRules = new AvailableNetmaskRules(); 35 | this.podRules = new PodNetmastRules(); 36 | } 37 | 38 | /** 39 | * Returns the rules currently in effect for the cluster netmask. 40 | * 41 | * @return {ClusterRules} rules currently in effect 42 | */ 43 | getClusterRules() { 44 | return this.clusterRules; 45 | } 46 | 47 | /** 48 | * Returns the rules currently in effect for the node netmask. 49 | * 50 | * @return {NodeRules} rules currently in effect 51 | */ 52 | getNodeRules() { 53 | return this.nodeRules; 54 | } 55 | 56 | 57 | /** 58 | * Returns the rules currently in effect for the service netmask. 59 | * 60 | * @return {ServiceRules} rules currently in effect 61 | */ 62 | getServiceRules() { 63 | return this.serviceRules; 64 | } 65 | 66 | /** 67 | * Returns the rules currently in effect for the available netmask. 68 | * 69 | * @return {AvailableNetmaskRules} rules currently in effect 70 | */ 71 | getAvailableNetmaskRules() { 72 | return this.availableNetmaskRules; 73 | } 74 | 75 | /** 76 | * Returns the rules currently in effect for the pod netmask. 77 | * 78 | * @return {PodNetmaskRules} rules currently in effect 79 | */ 80 | getPodRules() { 81 | return this.podRules; 82 | } 83 | } 84 | 85 | export default InputRules; 86 | -------------------------------------------------------------------------------- /src/components/TextDropdown.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import {Input} from 'reactstrap'; 17 | import PropTypes from 'prop-types'; 18 | 19 | /** 20 | * Component to display an Input element for a text dropdown 21 | */ 22 | class TextDropdown extends Component { 23 | /** 24 | * Constructor. 25 | * 26 | * @param {Object} props 27 | */ 28 | constructor(props) { 29 | super(props); 30 | this.handleChange = this.handleChange.bind(this); 31 | } 32 | 33 | 34 | /** 35 | * Handle selecting a different item. 36 | * 37 | * @param {Object} e the selection event. 38 | */ 39 | handleChange(e) { 40 | this.props.changeHandler(e.target.value); 41 | } 42 | 43 | /** 44 | * 45 | * Create the options. 46 | * 47 | * @return {Object} the options for the dropdown. 48 | */ 49 | createOptions = () => { 50 | const options = []; 51 | this.props.options.forEach((s) => { 52 | options.push(); 53 | }); 54 | return options; 55 | }; 56 | 57 | /** 58 | * Render the Input element. 59 | * 60 | * @return {Object} the Input element for a text dropdown. 61 | */ 62 | render() { 63 | if (this.props.value === '' || this.props.value === null) { 64 | return ( 65 | 71 | {this.createOptions()} 72 | 73 | ); 74 | } else { 75 | return ( 76 | 83 | {this.createOptions()} 84 | 85 | ); 86 | } 87 | } 88 | } 89 | 90 | 91 | TextDropdown.propTypes = { 92 | id: PropTypes.string, 93 | options: PropTypes.array, 94 | name: PropTypes.string, 95 | value: PropTypes.string, 96 | changeHandler: PropTypes.func, 97 | }; 98 | 99 | export default TextDropdown; 100 | -------------------------------------------------------------------------------- /src/components/NetmaskDropdown.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import {Input} from 'reactstrap'; 17 | import PropTypes from 'prop-types'; 18 | 19 | /** 20 | * Component to display an Input element for a dropdown to select netmask values. 21 | */ 22 | class NetmaskDropdown extends Component { 23 | /** 24 | * Constructor. 25 | * 26 | * @param {Object} props 27 | */ 28 | constructor(props) { 29 | super(props); 30 | 31 | this.handleChange = this.handleChange.bind(this); 32 | } 33 | 34 | /** 35 | * Handle selecting a different value. 36 | * 37 | * @param {Object} e the selection event. 38 | */ 39 | handleChange(e) { 40 | this.props.changeHandler(Number(e.target.value)); 41 | } 42 | 43 | /** 44 | * 45 | * Create the options. 46 | * 47 | * @return {Object} the options for the dropdown. 48 | */ 49 | createOptions = () => { 50 | const min = this.props.rules.minimum().minimum(); 51 | const max = this.props.rules.maximum().maximum(); 52 | const options = []; 53 | 54 | for (let i = min; i >= max; i--) { 55 | options.push(); 56 | } 57 | return options; 58 | }; 59 | 60 | /** 61 | * Render the Input element. 62 | * 63 | * @return {Object} the Input element for the netmask dropdown. 64 | */ 65 | render() { 66 | if (this.props.value === '' || this.props.value === null) { 67 | return ( 68 | 74 | {this.createOptions()} 75 | 76 | ); 77 | } else { 78 | return ( 79 | 86 | {this.createOptions()} 87 | 88 | ); 89 | } 90 | } 91 | } 92 | 93 | NetmaskDropdown.propTypes = { 94 | rules: PropTypes.object, 95 | value: PropTypes.number, 96 | changeHandler: PropTypes.func, 97 | }; 98 | 99 | export default NetmaskDropdown; 100 | -------------------------------------------------------------------------------- /test/make.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # This function checks to make sure that every 18 | # shebang has a '- e' flag, which causes it 19 | # to exit on error 20 | function check_bash() { 21 | find . -name "*.sh" | while IFS= read -d '' -r file; 22 | do 23 | if [[ "$file" != *"bash -e"* ]]; 24 | then 25 | echo "$file is missing shebang with -e"; 26 | exit 1; 27 | fi; 28 | done; 29 | } 30 | 31 | # This function makes sure that the required files for 32 | # releasing to OSS are present 33 | function basefiles() { 34 | echo "Checking for required files" 35 | test -f CONTRIBUTING.md || echo "Missing CONTRIBUTING.md" 36 | test -f LICENSE || echo "Missing LICENSE" 37 | test -f README.md || echo "Missing README.md" 38 | } 39 | 40 | # This function runs the hadolint linter on 41 | # every file named 'Dockerfile' 42 | function docker() { 43 | echo "Running hadolint on Dockerfiles" 44 | find . -name "Dockerfile" -exec hadolint {} \; 45 | } 46 | 47 | # This function runs 'terraform validate' against all 48 | # files ending in '.tf' 49 | function check_terraform() { 50 | echo "Running terraform validate" 51 | #shellcheck disable=SC2156 52 | find . -name "*.tf" -exec bash -c 'terraform init $(dirname "{}") && terraform validate $(dirname "{}")' \; 53 | } 54 | 55 | # This function runs 'go fmt' and 'go vet' on eery file 56 | # that ends in '.go' 57 | function golang() { 58 | echo "Running go fmt and go vet" 59 | find . -name "*.go" -exec go fmt {} \; 60 | find . -name "*.go" -exec go vet {} \; 61 | } 62 | 63 | # This function runs the flake8 linter on every file 64 | # ending in '.py' 65 | function check_python() { 66 | echo "Running flake8" 67 | find . -name "*.py" -exec flake8 {} \; 68 | } 69 | 70 | # This function runs the shellcheck linter on every 71 | # file ending in '.sh' 72 | function check_shell() { 73 | echo "Running shellcheck" 74 | find . -name "*.sh" -exec shellcheck -x {} \; 75 | } 76 | 77 | # This function makes sure that there is no trailing whitespace 78 | # in any files in the project. 79 | # There are some exclusions 80 | function check_trailing_whitespace() { 81 | echo "The following lines have trailing whitespace" 82 | grep -r '[[:blank:]]$' --exclude-dir=".terraform" --exclude-dir="node_modules" --exclude-dir="build" --exclude="*.png" --exclude-dir=".git" --exclude="*.pyc" --exclude="*.ico" . 83 | rc=$? 84 | if [ $rc = 0 ]; then 85 | exit 1 86 | fi 87 | } 88 | -------------------------------------------------------------------------------- /src/ui/output/LogicOutputDropdown.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import '../../App.css'; 17 | import {Input, Label, FormGroup, Col} from 'reactstrap'; 18 | import Logic from '../../logic/Logic'; 19 | import PropTypes from 'prop-types'; 20 | 21 | /** 22 | * Dropdown for the different combinations that were calculated 23 | */ 24 | class LogicOutputDropdown extends Component { 25 | /** 26 | * Constructor. 27 | * 28 | * @param {Object} props 29 | */ 30 | constructor(props) { 31 | super(props); 32 | 33 | this.handleChange = this.handleChange.bind(this); 34 | } 35 | 36 | /** 37 | * 38 | * Create the options, one per combination 39 | * 40 | * @return {Object} the options for the dropdown. 41 | */ 42 | createOptions = () => { 43 | const combinations = this.props.logic.getCombinations(); 44 | 45 | const options = []; 46 | combinations.forEach(function(element, index) { 47 | options.push( 48 | , 49 | ); 50 | }); 51 | return options; 52 | }; 53 | 54 | /** 55 | * Handle selecting a different combination. 56 | * 57 | * @param {Object} e the selection event. 58 | */ 59 | handleChange(e) { 60 | this.props.changeHandler(Number(e.target.value)); 61 | } 62 | 63 | /** 64 | * Render the dropdown. 65 | * 66 | * @return {Object} the dropdown for the combinations that were calculated. 67 | */ 68 | render() { 69 | return ( 70 |
71 | 72 | 76 | 77 | 83 | {' '} 84 | {this.createOptions()}{' '} 85 | {' '} 86 | {' '} 87 | 88 |
89 | ); 90 | } 91 | } 92 | 93 | LogicOutputDropdown.propTypes = { 94 | logic: PropTypes.instanceOf(Logic), 95 | input: PropTypes.object, 96 | changeHandler: PropTypes.func, 97 | }; 98 | 99 | export default LogicOutputDropdown; 100 | -------------------------------------------------------------------------------- /src/Calculator.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import './App.css'; 17 | import {Container, Row} from 'reactstrap'; 18 | 19 | import Parameters from './ui/input/Parameters'; 20 | import Results from './ui/output/Results'; 21 | import Logic from './logic/Logic'; 22 | 23 | import InputRules from './rules/input/InputRules'; 24 | import StateButtons from './components/StateButtons'; 25 | 26 | /** 27 | * Main component for the UI. It's the glue logic between the input, business logic, and output. 28 | */ 29 | class Calculator extends Component { 30 | /** 31 | * Constructor. 32 | * 33 | * @param {*} props Properties. 34 | */ 35 | constructor(props) { 36 | super(props); 37 | 38 | // TODO Need to use better defaults 39 | const input = { 40 | network: '10.0.0.0', 41 | netmask: 16, 42 | nodeNetmask: 29, 43 | clusterNetmask: 24, 44 | serviceNetmask: 24, 45 | nodePodNetmask: '24', 46 | masterNetwork: 'PUBLIC', 47 | locationType: 'ZONAL', 48 | extraZones: 1, 49 | }; 50 | const logic = new Logic(input); 51 | const inputRules = new InputRules(input); 52 | 53 | this.state = { 54 | input: input, 55 | logic: logic, 56 | inputRules: inputRules, 57 | }; 58 | 59 | this.handleUpdateInput = this.handleUpdateInput.bind(this); 60 | 61 | this.handleUploadConfig = this.handleUploadConfig.bind(this); 62 | } 63 | 64 | /** 65 | * Handle uploading a complete new config text. 66 | * 67 | * @param {string} configText the new config text. 68 | */ 69 | handleUploadConfig(configText) { 70 | const newInput = JSON.parse(configText); 71 | this.handleUpdateInput(newInput); 72 | } 73 | 74 | /** 75 | * Handle updating the input from the UI. 76 | * 77 | * @param {Object} input the new input. 78 | */ 79 | handleUpdateInput(input) { 80 | let logic; 81 | if (this.validateInput(input)) { 82 | logic = new Logic(input); 83 | } 84 | this.setState({ 85 | input: input, 86 | logic: logic, 87 | inputRules: new InputRules(input), 88 | }); 89 | } 90 | 91 | /** 92 | * Validates that the input is valid. 93 | * 94 | * @param {Object} input Input structure. 95 | * @return {boolean} True if valid, false otherwise. 96 | */ 97 | validateInput(input) { 98 | if (!Number.isInteger(input.netmask)) { 99 | return false; 100 | } 101 | return true; 102 | } 103 | 104 | /** 105 | * Render the div element that holds most of the application. 106 | * 107 | * @return {Object} the div element with the application. 108 | */ 109 | render() { 110 | return ( 111 |
112 |
113 | 114 | 115 | 119 | 120 | 121 |
122 | 127 | 128 |
129 |

Input

130 |
{JSON.stringify(this.state.input, null, ' ')}
131 | 132 | 133 |
134 | ); 135 | } 136 | } 137 | 138 | export default Calculator; 139 | -------------------------------------------------------------------------------- /src/logic/Packer.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import IPUtils from './IPUtils'; 16 | 17 | /** 18 | * Pack subnets as efficient as possible withtin a given address space 19 | */ 20 | class Packer { 21 | /** 22 | * Packs the provided subnets into the provided space. 23 | * If packing the subnets is not possible, it returns an object with 'state': 'bad' 24 | * 25 | * @param {*} space 26 | * @param {*} subnets 27 | * @return {Object} object with the packed subnets. 28 | */ 29 | pack(space, subnets) { 30 | const sortedSubnets = this.sortSubnets(subnets); 31 | let currentIp = IPUtils.netStart(IPUtils.dotToDec(space.net), space.mask); 32 | 33 | const endIp = IPUtils.netEnd(currentIp, space.mask); 34 | const packedNets = []; 35 | for (let i = 0; i < sortedSubnets.length; i++) { 36 | const subnet = sortedSubnets[i]; 37 | const fit = this.fit(currentIp, endIp, subnet); 38 | if (fit.fit) { 39 | packedNets.push({ 40 | name: subnet.name, 41 | mask: subnet.mask, 42 | netStart: currentIp, 43 | netEnd: fit.end, 44 | description: subnet.description, 45 | vpcName: subnet.vpcName, 46 | subnetRangeName: subnet.subnetRangeName, 47 | type: subnet.type, 48 | }); 49 | currentIp = fit.end + 1; 50 | } else { 51 | // Couldn't fit 52 | return { 53 | state: 'bad', 54 | }; 55 | } 56 | } 57 | 58 | const freeRanges=this.findEmptyRanges(space, currentIp); 59 | return { 60 | state: 'ok', 61 | packedNets: packedNets, 62 | freeRanges: freeRanges, 63 | }; 64 | } 65 | 66 | /** 67 | * Tries to fit a subnet between spaceStart and spaceEnd 68 | * @param {number} spaceStart 69 | * @param {number} spaceEnd 70 | * @param {number} currentNet 71 | * @return {Object} object with the result 72 | */ 73 | fit(spaceStart, spaceEnd, currentNet) { 74 | const currentNetEnd = IPUtils.netEnd(spaceStart, currentNet.mask); 75 | if (currentNetEnd > spaceEnd) { 76 | return { 77 | fit: false, 78 | end: currentNetEnd, 79 | }; 80 | } 81 | return { 82 | fit: true, 83 | end: currentNetEnd, 84 | }; 85 | } 86 | 87 | /** 88 | * Sorts the subnets from largest to smallest. 89 | * @param {Array} subnets Subnets to sort. 90 | * @return {Array} sorted subnets. 91 | */ 92 | sortSubnets(subnets) { 93 | return subnets.sort(function(a, b) { 94 | if (a.mask === b.mask) { 95 | return a.name < b.name ? -1 : a.name > b.name ? 1 : 0; 96 | } else { 97 | return a.mask - b.mask; 98 | } 99 | }); 100 | } 101 | 102 | /** 103 | * Finds the largest free CIDR blocks in a subnet, starting from a particular IP. 104 | * 105 | * @param {Object} space source address space. 106 | * @param {number} firstFreeIpDec first free IP. 107 | * @return {Array} sorted free subnet. 108 | */ 109 | findEmptyRanges(space, firstFreeIpDec) { 110 | const startIp = IPUtils.netStart(IPUtils.dotToDec(space.net), space.mask); 111 | const endIp = IPUtils.netEnd(startIp, space.mask); 112 | const maxIp = startIp + (endIp - firstFreeIpDec); 113 | const freeNets = []; 114 | let currentIp = startIp; 115 | for (let mask = space.mask; mask < 32; mask ++ ) { 116 | const subnet = { 117 | mask: mask, 118 | }; 119 | const fit = this.fit(currentIp, maxIp, subnet); 120 | if (fit.fit) { 121 | freeNets.unshift({ 122 | mask: mask, 123 | netStart: endIp - fit.end +startIp, 124 | netEnd: endIp - currentIp +startIp, 125 | }); 126 | currentIp = fit.end + 1; 127 | } 128 | } 129 | return freeNets; 130 | } 131 | } 132 | 133 | export default Packer; 134 | -------------------------------------------------------------------------------- /src/logic/IPUtils.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /** 16 | * Utilities to manipulate IPs. 17 | */ 18 | class IPUtils { 19 | /** 20 | * Converts an IP address from decimal to dot notation. 21 | * 22 | * @param {number} address address in decimal notation. 23 | * @return {string} address in dot notation. 24 | */ 25 | static decToDot(address) { 26 | const part1 = address & 255; 27 | const part2 = (address >> 8) & 255; 28 | const part3 = (address >> 16) & 255; 29 | const part4 = (address >> 24) & 255; 30 | return part4 + '.' + part3 + '.' + part2 + '.' + part1; 31 | } 32 | 33 | /** 34 | * Converts an IP address from dot to decimal notation. 35 | * 36 | * @param {string} address address in dot notation. 37 | * @return {number} address in decimal notation. 38 | */ 39 | static dotToDec(address) { 40 | const octets = address.match(/(\d+)/g); 41 | let total = 0; 42 | for (const i in octets) { 43 | if (Object.prototype.hasOwnProperty.call(octets, i)) { 44 | const mul = 2 ** ((3 - i) * 8); 45 | total += octets[i] * mul; 46 | } 47 | } 48 | return total; 49 | } 50 | 51 | /** 52 | * Calculate the last IP of a network with a particular netmask. 53 | * @param {number} networkIp network IP. 54 | * @param {number} netmask netwask for the network. 55 | * @return {number} last IP of the network. 56 | */ 57 | static netEnd(networkIp, netmask) { 58 | // Logical OR between the address and the NOT netmask (bits flipped). 59 | const netmaskDec = 2 ** (32 - netmask) - 1; 60 | return this.fixSigned(networkIp | netmaskDec); 61 | } 62 | 63 | /** 64 | * Calculate the first IP of a network with a particular netmask. 65 | * @param {number} networkIp network IP. 66 | * @param {number} netmask netwask for the network. 67 | * @return {number} first IP of the network. 68 | */ 69 | static netStart(networkIp, netmask) { 70 | // Logical AND between the address and the netmask. 71 | const netmaskDec = 4294967295 - (2 ** (32 - netmask) - 1); 72 | return this.fixSigned(networkIp & netmaskDec); 73 | } 74 | 75 | /** 76 | * Find the largest viable netmask between to IPs. 77 | * 78 | * @param {number} currentIp start IP. 79 | * @param {number} endIp end IP. 80 | * @return {number} the largest mask between the start and end IPs. 81 | */ 82 | static findLargestMask(currentIp, endIp) { 83 | const remaining = endIp - currentIp; 84 | let currentBits = 0; 85 | while (currentBits < 32) { 86 | const ips = 2**currentBits; 87 | if (remaining <= ips) { 88 | return 32 - currentBits; 89 | } 90 | currentBits ++; 91 | } 92 | return NaN; // ??? 93 | } 94 | 95 | /** 96 | * Converts a netmask into a decimal value (bits turned on). 97 | * 98 | * @param {number} netmask netmask value (from 0 to 32) 99 | * @return {number} decimal value of netmask in decimal format 100 | */ 101 | static netmaskToDecimal(netmask) { 102 | const v1 = 4294967295; // (2 ** 32) - 1 103 | const v2 = 2 ** (32 - netmask) - 1; 104 | return this.fixSigned(v1 ^ v2); 105 | } 106 | 107 | /** 108 | * Deals with the fact that in JS bitwise operations 109 | * 32 bit SIGNED numbers are used, so there's a possible overflow. 110 | * 111 | * @param {number} signed number in SIGNED format. 112 | * @return {number} unsigned number 113 | */ 114 | static fixSigned(signed) { 115 | if (signed < 0) { 116 | let ret = signed & 2147483647; 117 | ret += 2147483648; 118 | return ret; 119 | } 120 | return signed; 121 | } 122 | 123 | /** 124 | * Calculates the number of usable IPs. 125 | * 126 | * @param {number} netmask network netmask. 127 | * @return {number} number of usable IPs. 128 | */ 129 | static netmaskToUsableIps(netmask) { 130 | return 2 ** (32 - netmask); 131 | } 132 | } 133 | 134 | export default IPUtils; 135 | -------------------------------------------------------------------------------- /src/components/StateButtons.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React from 'react'; 16 | import { 17 | Button, 18 | Modal, 19 | ModalHeader, 20 | ModalBody, 21 | ModalFooter, 22 | Col, 23 | Input, 24 | } from 'reactstrap'; 25 | import PropTypes from 'prop-types'; 26 | 27 | /** 28 | * Component to hold buttons to manage the state of applications. 29 | */ 30 | class StateButtons extends React.Component { 31 | /** 32 | * Constructor. 33 | * 34 | * @param {Object} props 35 | */ 36 | constructor(props) { 37 | super(props); 38 | this.state = { 39 | downloadModal: false, 40 | uploadModal: false, 41 | configText: '', 42 | }; 43 | 44 | this.toggleDownload = this.toggleDownload.bind(this); 45 | this.toggleUpload = this.toggleUpload.bind(this); 46 | this.uploadConfig = this.uploadConfig.bind(this); 47 | this.handleConfigText = this.handleConfigText.bind(this); 48 | } 49 | 50 | /** 51 | * Handle ingesting new text for the configuration. 52 | * 53 | * @param {Object} e the selection event. 54 | */ 55 | handleConfigText(e) { 56 | this.setState( 57 | Object.assign({}, this.state.input, { 58 | configText: e.target.value, 59 | }), 60 | ); 61 | } 62 | 63 | /** 64 | * Show the download modal dialog 65 | */ 66 | toggleDownload() { 67 | this.setState((prevState) => ({ 68 | downloadModal: !prevState.downloadModal, 69 | uploadModal: prevState.uploadModal, 70 | configText: '', 71 | })); 72 | } 73 | 74 | /** 75 | * Show the upload modal dialog 76 | */ 77 | toggleUpload() { 78 | this.setState((prevState) => ({ 79 | downloadModal: prevState.downloadModal, 80 | uploadModal: !prevState.uploadModal, 81 | configText: '', 82 | })); 83 | } 84 | 85 | /** 86 | * Hide the upload modal dialog, and ingest the config text. 87 | */ 88 | uploadConfig() { 89 | this.toggleUpload(); 90 | this.props.handleUploadConfig(this.state.configText); 91 | } 92 | 93 | /** 94 | * Render the Col element. 95 | * 96 | * @return {Object} the Col element that contains the buttons. 97 | */ 98 | render() { 99 | return ( 100 | 101 | {' '} 104 | 105 | 106 | Current Configuration 107 | 108 | 109 |
{this.props.input}
110 |
111 | 112 | 115 | 116 |
117 | {' '} 120 | 121 | 122 | Upload Configuration 123 | 124 | 125 | 132 | 133 | 134 | 137 | 140 | 141 | 142 | 143 | ); 144 | } 145 | } 146 | 147 | StateButtons.propTypes = { 148 | input: PropTypes.string, 149 | handleUploadConfig: PropTypes.func, 150 | }; 151 | 152 | export default StateButtons; 153 | -------------------------------------------------------------------------------- /src/logic/Packer.test.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Packer from './Packer'; 16 | import IPUtils from './IPUtils'; 17 | 18 | it('Ok, with a single one', () => { 19 | const p = new Packer(); 20 | const result = p.pack( 21 | { 22 | net: '10.0.0.1', 23 | mask: 8, 24 | }, 25 | [ 26 | { 27 | name: 'test', 28 | mask: 16, 29 | }, 30 | ], 31 | ); 32 | expect(result.state).toEqual('ok'); 33 | expect(result.packedNets.length).toEqual(1); 34 | expect(result.packedNets[0].name).toEqual('test'); 35 | 36 | expect(IPUtils.decToDot(result.packedNets[0].netStart)).toEqual('10.0.0.0'); 37 | expect(IPUtils.decToDot(result.packedNets[0].netEnd)).toEqual('10.0.255.255'); 38 | }); 39 | 40 | it('Ok, with a two subnets', () => { 41 | const p = new Packer(); 42 | const result = p.pack( 43 | { 44 | net: '10.0.0.1', 45 | mask: 8, 46 | }, 47 | [ 48 | { 49 | name: 'test 1', 50 | mask: 16, 51 | }, 52 | { 53 | name: 'test 2', 54 | mask: 16, 55 | }, 56 | ], 57 | ); 58 | expect(result.state).toEqual('ok'); 59 | expect(result.packedNets.length).toEqual(2); 60 | expect(result.packedNets[0].name).toEqual('test 1'); 61 | expect(IPUtils.decToDot(result.packedNets[0].netStart)).toEqual('10.0.0.0'); 62 | 63 | expect(result.packedNets[1].name).toEqual('test 2'); 64 | expect(IPUtils.decToDot(result.packedNets[1].netStart)).toEqual('10.1.0.0'); 65 | }); 66 | 67 | it('Test sort (largest to smallest)', () => { 68 | const subnets = [ 69 | { 70 | name: 'small 2', 71 | mask: 16, 72 | }, 73 | { 74 | name: 'small 1', 75 | mask: 16, 76 | }, 77 | { 78 | name: 'large 2', 79 | mask: 8, 80 | }, 81 | { 82 | name: 'mid 1', 83 | mask: 12, 84 | }, 85 | { 86 | name: 'large 1', 87 | mask: 8, 88 | }, 89 | ]; 90 | const p = new Packer(); 91 | const sortedSubnets = p.sortSubnets(subnets); 92 | expect(sortedSubnets[0].name).toEqual('large 1'); 93 | expect(sortedSubnets[1].name).toEqual('large 2'); 94 | expect(sortedSubnets[2].name).toEqual('mid 1'); 95 | expect(sortedSubnets[3].name).toEqual('small 1'); 96 | expect(sortedSubnets[4].name).toEqual('small 2'); 97 | }); 98 | 99 | it('Bad, with a single one', () => { 100 | const p = new Packer(); 101 | const result = p.pack( 102 | { 103 | net: '10.0.0.1', 104 | mask: 24, 105 | }, 106 | [ 107 | { 108 | name: 'test', 109 | mask: 16, 110 | }, 111 | ], 112 | ); 113 | expect(result.state).toEqual('bad'); 114 | }); 115 | 116 | 117 | // //////// Internal 118 | 119 | it('check fit', () => { 120 | const p = new Packer(); 121 | expect( 122 | p.fit(IPUtils.dotToDec('192.168.0.1'), 123 | IPUtils.dotToDec('192.168.0.255'), { 124 | mask: 25, 125 | }).fit, 126 | ).toEqual(true); 127 | expect( 128 | p.fit(IPUtils.dotToDec('192.168.0.1'), 129 | IPUtils.dotToDec('192.168.0.255'), { 130 | mask: 24, 131 | }).fit, 132 | ).toEqual(true); 133 | expect( 134 | p.fit(IPUtils.dotToDec('192.168.0.1'), 135 | IPUtils.dotToDec('192.168.0.255'), { 136 | mask: 23, 137 | }).fit, 138 | ).toEqual(false); 139 | }); 140 | 141 | 142 | it('Find single free range', () =>{ 143 | const p = new Packer(); 144 | const result= p.findEmptyRanges({ 145 | net: '192.168.0.1', 146 | mask: 24, 147 | }, IPUtils.dotToDec('192.168.0.192')); 148 | expect(result.length).toEqual(1); 149 | expect(result[0].mask).toEqual(26); 150 | expect(IPUtils.decToDot(result[0].netStart)).toEqual('192.168.0.192'); 151 | expect(IPUtils.decToDot(result[0].netEnd)).toEqual('192.168.0.255'); 152 | }); 153 | 154 | it('Find 2 free ranges', () =>{ 155 | const p = new Packer(); 156 | const result= p.findEmptyRanges({ 157 | net: '192.168.0.1', 158 | mask: 24, 159 | }, IPUtils.dotToDec('192.168.0.160')); 160 | expect(result.length).toEqual(2); 161 | expect(result[0].mask).toEqual(27); 162 | expect(IPUtils.decToDot(result[0].netStart)).toEqual('192.168.0.160'); 163 | expect(IPUtils.decToDot(result[0].netEnd)).toEqual('192.168.0.191'); 164 | 165 | expect(result[1].mask).toEqual(26); 166 | expect(IPUtils.decToDot(result[1].netStart)).toEqual('192.168.0.192'); 167 | expect(IPUtils.decToDot(result[1].netEnd)).toEqual('192.168.0.255'); 168 | }); 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GKE IP Address Management 2 | 3 | A tool to do IP Address Management for VPC-native GKE(Google Kubernetes Engine) clusters. 4 | 5 | ## Introduction 6 | 7 | This tool provides an easy and interactive way to model the IP Address requirement to create [VPC-native GKE clusters using alias IP addresses](https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips). 8 | 9 | ## Architecture 10 | 11 | This application is a React single page application. The application has no backend requirements, and can be run locally or hosted in any standard web server. 12 | 13 | ## Prerequisites 14 | 15 | ### Tools 16 | 17 | To build and run the tool locally, Node.js and npm are required. Both tools can be installed by most popular package managers in Unix-like operating systems. 18 | Alternatively, native installers for Node.js are available from the Node.js site [downloads](https://nodejs.org/en/download/). Node.js installers include the required `npm` tool. 19 | Additionally, `jq` is required to build a distribution to deploy on a webserver. `jq` can be downloaded from the official [website](https://stedolan.github.io/jq/), but is normally installed using the native package manager of the platform you're running (such as `apt-get` or `yum`). Any reasonable recent version of `jq` would suffice. The process has been tested with version 1.5. 20 | 21 | ### Versions 22 | 23 | Any reasonable modern version of Node.js and npm should suffice to build and run the tool. It has been tested with node version `8.6.0` and npm version `5.4.2`. 24 | 25 | ## Running 26 | 27 | The tool can be run locally by executing: 28 | 29 | make run 30 | 31 | This will install the required node dependancies and start the developement server. The developement service will listen on [http://localhost:3000/](http://localhost:3000/). 32 | 33 | Alternatively you can just execute npm directly: 34 | 35 | npm install 36 | npm start 37 | 38 | ## Deployment 39 | 40 | The application can be deployed to a webserver. To create a optimized production build, run: 41 | 42 | make npm-build 43 | 44 | Simply copy the files located inside the `build` directory and serve from the root of a web server. 45 | 46 | ### Deployment from a different root URL 47 | 48 | To create a build that is served out of URL that is not a the root of the domain, pass the base url as an environment variable before building: 49 | 50 | export IPAM_HOST_URL=https://mydomain.com/gke-ipam/ 51 | make npm-build 52 | 53 | Copy the resulting files from the `build` directory to the webserver at the previously designated path. 54 | 55 | ## CLI 56 | 57 | A CLI is provided in the `src` directory. It currently takes the input in JSON format. For example: 58 | 59 | { 60 | "network": "10.0.0.0", 61 | "netmask": "16", 62 | "nodeNetmask": "29", 63 | "clusterNetmask": "24", 64 | "serviceNetmask": "24", 65 | "nodePodNetmask": "24", 66 | "masterNetwork": "PUBLIC", 67 | "locationType": "ZONAL", 68 | "extraZones": "1" 69 | } 70 | 71 | The input can be passed from a file using `--input=`: 72 | 73 | ./gke-ipam.js --input=test.tmp 74 | 75 | The input can be passed from `stdin`: 76 | 77 | cat test.tmp | ./gke-ipam.js 78 | 79 | ## Developing 80 | 81 | See [CONTRIBUTING](CONTRIBUTING.md) for details on Contribuiting to the project. 82 | 83 | ### Testing 84 | 85 | We use `jest` to run automated testing. During development it is useful to have `jest` running. 86 | constantly and monitoring changes to the files: 87 | 88 | npm test 89 | 90 | ### Code Quality and Format 91 | 92 | We use `eslint` for code quality. During development it's use full to run `eslint` periodically to catch any. 93 | issues and to fix any formatting issues: 94 | 95 | npm run lint -- --fix 96 | 97 | ### Test Coverage 98 | 99 | To see the test coverage of the codebase use the following command: 100 | 101 | npm test -- --coverage --watchAll=false 102 | 103 | There's a [bug](https://github.com/facebook/jest/issues/8491) in jest that prevents running coverage and watching files together. 104 | 105 | ## TODO 106 | 107 | * Display free ranges 108 | * Allow to share the Cluster range CIDR accross VPCs 109 | * Allow to add manually defined networks 110 | * Allow to reserve IPs in the main VPC ranges 111 | * Dispaly more info regarding what rules are constraining the results 112 | * Allow packing multiple GKE clusters into a single VPC (2 or 4 depending on whether we share the Cluster Alias range) 113 | 114 | ## Relevant Material 115 | 116 | The following links are relevant to nuances of creating VPC-native GKE clusters. 117 | 118 | [GKE Network overview](https://cloud.google.com/kubernetes-engine/docs/concepts/network-overview) 119 | [Creating VPC-native clusters using alias IP addresses](https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips) 120 | [Alias IP Ranges Overview](https://cloud.google.com/vpc/docs/alias-ip) 121 | 122 | ## Change Log 123 | 124 | * 5/13/2019 - Minimum size for `Services` changed to `/27`. 125 | 126 | ## License 127 | 128 | Apache 2.0. See [LICENSE](LICENSE) for more information. 129 | 130 | ## Disclaimer 131 | 132 | This is not an official Google product. 133 | -------------------------------------------------------------------------------- /src/logic/Logic.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import Packer from './Packer'; 16 | 17 | /** 18 | * Object to encapsulate the business logic for doing IPAM for GKE clusters. 19 | */ 20 | class Logic { 21 | /** 22 | * Constructor. 23 | * 24 | * @param {*} input Input provided by the user. 25 | */ 26 | constructor(input) { 27 | // Calculate clusters, using Packer 28 | const packer = new Packer(); 29 | const validCombinations = []; 30 | const space = { 31 | net: input.network, 32 | mask: input.netmask, 33 | }; 34 | let ok = true; 35 | let count = 1; 36 | while (ok && count < 1001) { 37 | const networks = []; 38 | const targetNumberLength = count.toString().length; 39 | for (let j = 1; j <= count; j++) { 40 | networks.push({ 41 | mask: input.nodeNetmask, 42 | name: 'node-' + this.pad(targetNumberLength, j), 43 | vpcName: 'vpc-' + this.pad(targetNumberLength, j), 44 | subnetRangeName: 'N/A', 45 | type: 'PRIMARY', 46 | description: 47 | 'Main VPC range for node-' + this.pad(targetNumberLength, j), 48 | }); 49 | 50 | networks.push({ 51 | mask: input.clusterNetmask, 52 | name: 'cluster-' + this.pad(targetNumberLength, j), 53 | vpcName: 'vpc-' + this.pad(targetNumberLength, j), 54 | subnetRangeName: 'cluster-' + this.pad(targetNumberLength, j), 55 | type: 'SECONDARY', 56 | description: 57 | 'Secondary range for VPC node-' + 58 | this.pad(targetNumberLength, j) + 59 | ' (for pods)', 60 | }); 61 | 62 | networks.push({ 63 | mask: input.serviceNetmask, 64 | name: 'service-' + this.pad(targetNumberLength, j), 65 | vpcName: 'vpc-' + this.pad(targetNumberLength, j), 66 | subnetRangeName: 'service-' + this.pad(targetNumberLength, j), 67 | type: 'SECONDARY', 68 | description: 69 | 'Secondary range for VPC node-' + 70 | this.pad(targetNumberLength, j) + 71 | ' (for services)', 72 | }); 73 | 74 | if (input.masterNetwork === 'UNIQUE') { 75 | networks.push({ 76 | mask: 28, 77 | name: 'master-' + this.pad(targetNumberLength, j), 78 | vpcName: 'N/A', 79 | subnetRangeName: 'N/A', 80 | type: 'MANAGED', 81 | description: 82 | 'IP range for managed VPC for master(s) for cluster node-' + 83 | this.pad(targetNumberLength, j), 84 | }); 85 | } 86 | } 87 | 88 | if (input.masterNetwork === 'SHARE') { 89 | networks.push({ 90 | mask: 28, 91 | name: 'master', 92 | vpcName: 'N/A', 93 | subnetRangeName: 'N/A', 94 | type: 'MANAGED', 95 | description: 96 | 'Shared IP range for managed VPC for master(s) for all clusters', 97 | }); 98 | } 99 | 100 | const packerResults = packer.pack(space, networks); 101 | if (packerResults.state === 'ok') { 102 | validCombinations.push({ 103 | networks: packerResults.packedNets, 104 | freeRanges: packerResults.freeRanges, 105 | }); 106 | } else { 107 | ok = false; 108 | } 109 | 110 | count++; 111 | } 112 | 113 | this.combinations = validCombinations; 114 | } 115 | 116 | /** 117 | * Pads the number to a target length using zeros. 118 | * 119 | * @param {number} targetLength number of digits to pad to. 120 | * @param {string} numberToPad number to pad. 121 | * @return {string} padded number. 122 | */ 123 | pad(targetLength, numberToPad) { 124 | return numberToPad.toString().padStart(targetLength, '0'); 125 | } 126 | 127 | /** 128 | * Returns the rules used for cluster netmask calculations. 129 | * 130 | * @return {*} the cluster rules. 131 | */ 132 | getClusterRules() { 133 | return this.clusterRules; 134 | } 135 | 136 | /** 137 | * Returns the rules used for node netmask calculations. 138 | * 139 | * @return {*} the node rules. 140 | */ 141 | getNodeRules() { 142 | return this.nodeRules; 143 | } 144 | 145 | /** 146 | * Returns the rules used for service netmask calculations. 147 | * 148 | * @return {*} the service rules. 149 | */ 150 | getServiceRules() { 151 | return this.serviceRules; 152 | } 153 | 154 | /** 155 | * Returns the viable combinations. 156 | * 157 | * @return {*} the viable combinations. 158 | */ 159 | getCombinations() { 160 | return this.combinations; 161 | } 162 | } 163 | 164 | export default Logic; 165 | -------------------------------------------------------------------------------- /src/rules/output/NodeOutputRules.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import IPUtils from '../../logic/IPUtils'; 16 | 17 | /** 18 | * Rules for the main subnet range based on the subnet size 19 | */ 20 | class NodeNetworkRules { 21 | /** 22 | * Constructor. 23 | * 24 | * @param {Input} input User input. 25 | */ 26 | constructor(input) { 27 | this.input = input; 28 | } 29 | 30 | /** 31 | * Returns the maximum number of usable IPs. 32 | * 33 | * @return {number} maximum number of usable IPs. 34 | */ 35 | maximum() { 36 | const netmask = this.input.nodeNetmask; 37 | // TODO allow reserving IPs on the Node Network 38 | return IPUtils.netmaskToUsableIps(netmask) - 4; // GCP reserves 4 ips. 39 | } 40 | 41 | /** 42 | * Returns the reference documentation URLs. 43 | * 44 | * @return {Array} an Array with a list of URLs. 45 | */ 46 | ref() { 47 | return [ 48 | 'https://cloud.google.com/vpc/docs/vpc#reserved_ip_addresses_in_every_subnet', 49 | ]; 50 | } 51 | } 52 | 53 | /** 54 | * Rules for the main subnet range based on the cluster subnet size size 55 | */ 56 | class ClusterNetworkRules { 57 | /** 58 | * Constructor. 59 | * 60 | * @param {Input} input User input. 61 | */ 62 | constructor(input) { 63 | this.input = input; 64 | } 65 | 66 | /** 67 | * Returns the maximum number of usable IPs. 68 | * 69 | * @return {number} maximum number of usable IPs. 70 | */ 71 | maximum() { 72 | const clusterNetmaskIps = IPUtils.netmaskToUsableIps( 73 | this.input.clusterNetmask, 74 | ); 75 | const podNetmaskIps = IPUtils.netmaskToUsableIps(this.input.nodePodNetmask); 76 | return clusterNetmaskIps / podNetmaskIps; 77 | } 78 | 79 | /** 80 | * Returns the reference documentation URLs. 81 | * 82 | * @return {Array} an Array with a list of URLs. 83 | */ 84 | ref() { 85 | return []; 86 | } 87 | } 88 | 89 | /** 90 | * Rules for the main subnet range based on location type (Regional, zonal, multi-zonal). 91 | */ 92 | class RegionalLocationRules { 93 | /** 94 | * Constructor. 95 | * 96 | * @param {Input} input User input. 97 | * @param {number} currentMax Current possible maximum based on upstream rules. 98 | */ 99 | constructor(input, currentMax) { 100 | this.input = input; 101 | this.currentMax = currentMax; 102 | } 103 | 104 | /** 105 | * Returns the maximum number of usable IPs. 106 | * 107 | * @return {number} maximum number of usable IPs. 108 | */ 109 | maximum() { 110 | if (this.input.locationType === 'REGIONAL') { 111 | return this.currentMax - (this.currentMax % 3); 112 | } else if (this.input.locationType === 'MULTI_ZONAL') { 113 | return ( 114 | this.currentMax - 115 | (this.currentMax % (Number.parseInt(this.input.extraZones) + 1)) 116 | ); 117 | } 118 | return this.currentMax; 119 | } 120 | 121 | /** 122 | * Returns the reference documentation URLs. 123 | * 124 | * @return {Array} an Array with a list of URLs. 125 | */ 126 | ref() { 127 | return []; 128 | } 129 | } 130 | 131 | /** 132 | * Rules for the main subnet range based on the aggregate of NodeNetworkRules and ClusterNetworkRules 133 | */ 134 | class NodeOutputRules { 135 | /** 136 | * Constructor. 137 | * 138 | * @param {Input} input User input. 139 | */ 140 | constructor(input) { 141 | this.input = input; 142 | this.nodeNetworkRules = new NodeNetworkRules(this.input); 143 | this.clusterNetworkRules = new ClusterNetworkRules(this.input); 144 | } 145 | 146 | /** 147 | * Returns the maximum number of usable IPs. 148 | * 149 | * @return {number} maximum number of usable IPs. 150 | */ 151 | maximum() { 152 | const v1 = this.nodeNetworkRules.maximum(); 153 | const v2 = this.clusterNetworkRules.maximum(); 154 | 155 | const regionalLocationRules = new RegionalLocationRules( 156 | this.input, 157 | v1 < v2 ? v1 : v2, 158 | ); 159 | 160 | return regionalLocationRules.maximum(); 161 | } 162 | 163 | /** 164 | * @return{Array} list of details that are affecting the maximum size. 165 | */ 166 | details() { 167 | const details = []; 168 | details.push({ 169 | id: 'subnet_limit', 170 | text: `The node subnet will limit each cluster to a maximum of ${this.nodeNetworkRules.maximum()} node(s).`, 171 | }); 172 | details.push({ 173 | id: 'cluster_limit', 174 | text: `The cluster subnet will limit each cluster to a maximum of ${this.clusterNetworkRules.maximum()} node(s).`, 175 | }); 176 | if (this.input.locationType === 'REGIONAL') { 177 | details.push({ 178 | id: 'regional_multiple_limit', 179 | text: `Nodes in a regional cluster have to be deployed in multiples of three`, 180 | }); 181 | } else if (this.input.locationType === 'MULTI_ZONAL') { 182 | details.push({ 183 | id: 'zonal_multiple_limit', 184 | text: `Nodes in a multi-zonal cluster (with ${ 185 | this.input.extraZones 186 | } extra zone) have to be deployed in multiples of ${Number.parseInt( 187 | this.input.extraZones, 188 | ) + 1}`, 189 | }); 190 | } 191 | 192 | return details; 193 | } 194 | } 195 | 196 | export default NodeOutputRules; 197 | 198 | export {NodeNetworkRules, ClusterNetworkRules}; 199 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | /* eslint no-console: ["off"], require-jsdoc: ["off"] */ 16 | 17 | // This optional code is used to register a service worker. 18 | // register() is not called by default. 19 | 20 | // This lets the app load faster on subsequent visits in production, and gives 21 | // it offline capabilities. However, it also means that developers (and users) 22 | // will only see deployed updates on subsequent visits to a page, after all the 23 | // existing tabs open on the page have been closed, since previously cached 24 | // resources are updated in the background. 25 | 26 | // To learn more about the benefits of this model and instructions on how to 27 | // opt-in, read http://bit.ly/CRA-PWA. 28 | 29 | const isLocalhost = Boolean( 30 | window.location.hostname === 'localhost' || 31 | // [::1] is the IPv6 localhost address. 32 | window.location.hostname === '[::1]' || 33 | // 127.0.0.1/8 is considered localhost for IPv4. 34 | window.location.hostname.match( 35 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/, 36 | ), 37 | ); 38 | 39 | export function register(config) { 40 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 41 | // The URL constructor is available in all browsers that support SW. 42 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location); 43 | if (publicUrl.origin !== window.location.origin) { 44 | // Our service worker won't work if PUBLIC_URL is on a different origin 45 | // from what our page is served on. This might happen if a CDN is used to 46 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 47 | return; 48 | } 49 | 50 | window.addEventListener('load', () => { 51 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 52 | 53 | if (isLocalhost) { 54 | // This is running on localhost. Let's check if a service worker still exists or not. 55 | checkValidServiceWorker(swUrl, config); 56 | 57 | // Add some additional logging to localhost, pointing developers to the 58 | // service worker/PWA documentation. 59 | navigator.serviceWorker.ready.then(() => { 60 | console.log( 61 | 'This web app is being served cache-first by a service ' + 62 | 'worker. To learn more, visit http://bit.ly/CRA-PWA', 63 | ); 64 | }); 65 | } else { 66 | // Is not localhost. Just register service worker 67 | registerValidSW(swUrl, config); 68 | } 69 | }); 70 | } 71 | } 72 | 73 | function registerValidSW(swUrl, config) { 74 | navigator.serviceWorker 75 | .register(swUrl) 76 | .then((registration) => { 77 | registration.onupdatefound = () => { 78 | const installingWorker = registration.installing; 79 | installingWorker.onstatechange = () => { 80 | if (installingWorker.state === 'installed') { 81 | if (navigator.serviceWorker.controller) { 82 | // At this point, the updated precached content has been fetched, 83 | // but the previous service worker will still serve the older 84 | // content until all client tabs are closed. 85 | console.log( 86 | 'New content is available and will be used when all ' + 87 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.', 88 | ); 89 | 90 | // Execute callback 91 | if (config && config.onUpdate) { 92 | config.onUpdate(registration); 93 | } 94 | } else { 95 | // At this point, everything has been precached. 96 | // It's the perfect time to display a 97 | // "Content is cached for offline use." message. 98 | console.log('Content is cached for offline use.'); 99 | 100 | // Execute callback 101 | if (config && config.onSuccess) { 102 | config.onSuccess(registration); 103 | } 104 | } 105 | } 106 | }; 107 | }; 108 | }) 109 | .catch((error) => { 110 | console.error('Error during service worker registration:', error); 111 | }); 112 | } 113 | 114 | function checkValidServiceWorker(swUrl, config) { 115 | // Check if the service worker can be found. If it can't reload the page. 116 | fetch(swUrl) 117 | .then((response) => { 118 | // Ensure service worker exists, and that we really are getting a JS file. 119 | if ( 120 | response.status === 404 || 121 | response.headers.get('content-type').indexOf('javascript') === -1 122 | ) { 123 | // No service worker found. Probably a different app. Reload the page. 124 | navigator.serviceWorker.ready.then((registration) => { 125 | registration.unregister().then(() => { 126 | window.location.reload(); 127 | }); 128 | }); 129 | } else { 130 | // Service worker found. Proceed as normal. 131 | registerValidSW(swUrl, config); 132 | } 133 | }) 134 | .catch(() => { 135 | console.log( 136 | 'No internet connection found. App is running in offline mode.', 137 | ); 138 | }); 139 | } 140 | 141 | export function unregister() { 142 | if ('serviceWorker' in navigator) { 143 | navigator.serviceWorker.ready.then((registration) => { 144 | registration.unregister(); 145 | }); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/ui/output/LogicOutput.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import '../../App.css'; 17 | import LogicOutputDropdown from './LogicOutputDropdown'; 18 | import LogicOutputNetworkRow from './LogicOutputNetworkRow'; 19 | import LogicOutputFreeNetworkRow from './LogicOutputFreeNetworkRow'; 20 | import {Table} from 'reactstrap'; 21 | import NodeOutputRules from '../../rules/output/NodeOutputRules'; 22 | import ServiceOutputRules from '../../rules/output/ServiceOutpuRules'; 23 | import PodOutputRules from '../../rules/output/PodOutputRules'; 24 | import Logic from '../../logic/Logic'; 25 | import PropTypes from 'prop-types'; 26 | 27 | /** 28 | * Container object for outputting the results of the calculation logic. 29 | */ 30 | class LogicOutput extends Component { 31 | /** 32 | * Constructor. 33 | * 34 | * @param {Object} props 35 | */ 36 | constructor(props) { 37 | super(props); 38 | const output = { 39 | combinationIndex: 0, 40 | }; 41 | 42 | this.state = { 43 | output: output, 44 | }; 45 | 46 | this.handleUpdateOutput = this.handleUpdateOutput.bind(this); 47 | this.handleChangeCombination = this.handleChangeCombination.bind(this); 48 | } 49 | /** 50 | * 51 | * Create the detail. 52 | * 53 | * @param {Object} nodeOutputRules rules that were used to compute the output. 54 | * @return {Object} the details for the rules. 55 | */ 56 | createDetail = (nodeOutputRules) => { 57 | const details = []; 58 | 59 | const nodeDetails = nodeOutputRules.details(); 60 | nodeDetails.forEach((detail) => { 61 | details.push(
{detail.text}
); 62 | }); 63 | 64 | return details; 65 | }; 66 | 67 | /** 68 | * 69 | * Create the rows. 70 | * 71 | * @return {Object} the details rows. 72 | */ 73 | createRows = () => { 74 | const logic = this.props.logic; 75 | const combination = logic.getCombinations()[ 76 | this.state.output.combinationIndex 77 | ]; 78 | const networks = combination.networks; 79 | const rows = []; 80 | 81 | networks.forEach((network) => { 82 | rows.push(); 83 | }); 84 | 85 | return rows; 86 | }; 87 | 88 | /** 89 | * 90 | * Create the rows representing the available or free CIDR ranges. 91 | * 92 | * @return {Object} the details rows. 93 | */ 94 | createFreeRows = () => { 95 | const logic = this.props.logic; 96 | const combination = logic.getCombinations()[ 97 | this.state.output.combinationIndex 98 | ]; 99 | const freeRanges = combination.freeRanges; 100 | const rows = []; 101 | 102 | freeRanges.forEach((freeRange) => { 103 | rows.push(); 104 | }); 105 | 106 | return rows; 107 | }; 108 | 109 | 110 | /** 111 | * 112 | * Create the detail tables. 113 | * 114 | * @return {Object} the detail tables. 115 | */ 116 | createTables = () => { 117 | const logic = this.props.logic; 118 | if (logic.getCombinations().length

Invalid Combination Selected

); 120 | } 121 | 122 | return (

VPCs and Subnets required

123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | {this.createRows()} 136 | 137 |
Network Start End Name VPC Name Subnet Range Name Subnet Range Type Description
{' '} 138 |

Free Subnets

139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | {this.createFreeRows()} 148 | 149 |
Network Start End Netmask
150 |
); 151 | } 152 | 153 | /** 154 | * Handle the selection of a different combination to be displayed. 155 | * 156 | * @param {number} combinationIndex The index of the combinations to be displayed. 157 | */ 158 | handleChangeCombination(combinationIndex) { 159 | this.handleUpdateOutput( 160 | Object.assign({}, this.state.output, { 161 | combinationIndex: combinationIndex, 162 | }), 163 | ); 164 | } 165 | 166 | /** 167 | * Update the output. 168 | * 169 | * @param {object} output The updated output. 170 | */ 171 | handleUpdateOutput(output) { 172 | this.setState({ 173 | output: output, 174 | }); 175 | } 176 | 177 | /** 178 | * Render the main output component. 179 | * 180 | * @return {Object} the main
for the output. 181 | */ 182 | render() { 183 | const nodeOutputRules = new NodeOutputRules(this.props.input); 184 | const serviceOutputRules = new ServiceOutputRules(this.props.input); 185 | const podOutputRules = new PodOutputRules(this.props.input); 186 | 187 | return ( 188 |
189 |
190 | Summary
191 | With the current configuration, up to{' '} 192 | {this.props.logic.getCombinations().length} isolated clusters can be 193 | created.
194 | Each cluster will suppport:
195 | Up to {nodeOutputRules.maximum()} node(s) per cluster.
196 | Up to {serviceOutputRules.maximum()} service(s) per cluster.
197 | Up to {podOutputRules.maximum()} pods per node.
198 |
199 |
200 | Details 201 |
202 | {this.createDetail(nodeOutputRules)} 203 |
204 | {' '} 208 | {this.createTables()} 209 |
210 | ); 211 | } 212 | } 213 | 214 | 215 | LogicOutput.propTypes = { 216 | logic: PropTypes.instanceOf(Logic), 217 | input: PropTypes.object, 218 | }; 219 | 220 | export default LogicOutput; 221 | -------------------------------------------------------------------------------- /src/ui/input/Parameters.js: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // https://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | import React, {Component} from 'react'; 16 | import {Form, FormGroup, Input, Label, Col} from 'reactstrap'; 17 | import TextDropdown from '../../components/TextDropdown'; 18 | import NetmaskInput from '../../components/NetmaskInput'; 19 | import TextDropdownInput from '../../components/TextDropdownInput'; 20 | import InputRules from '../../rules/input/InputRules'; 21 | import PropTypes from 'prop-types'; 22 | 23 | const locationTypeOptions = [ 24 | {value: 'ZONAL', text: 'Zonal'}, 25 | {value: 'MULTI_ZONAL', text: 'Multi - zonal'}, 26 | {value: 'REGIONAL', text: 'Regional'}, 27 | ]; 28 | 29 | const extraZoneOptions = [ 30 | {value: '1', text: '1'}, 31 | {value: '2', text: '2'}, 32 | {value: '3', text: '3'}, 33 | ]; 34 | 35 | const masterBlockOptions = [ 36 | {value: 'PUBLIC', text: 'Public Master'}, 37 | {value: 'SHARE', text: 'Share one Master CIDR across all clusters'}, 38 | {value: 'UNIQUE', text: 'Create a Unique Master CIDR for each cluster'}, 39 | {value: 'DEFAULT', text: 'Use default values for the Master CIDR block'}, 40 | ]; 41 | 42 | const nodePodNetmaskOptions = [ 43 | {value: '28', text: '8 pods (/28)', max: 8}, 44 | {value: '27', text: '9 to 16 pods (/27)', max: 16}, 45 | {value: '26', text: '17 to 32 pods (/26)', max: 32}, 46 | {value: '25', text: '33 to 64 pods (/25)', max: 64}, 47 | {value: '24', text: '65 to 110 pods (/24)', max: 110}, 48 | ]; 49 | 50 | /** 51 | * User inputted parameters 52 | */ 53 | class Parameters extends Component { 54 | /** 55 | * Constructor. 56 | * 57 | * @param {Object} props 58 | */ 59 | constructor(props) { 60 | super(props); 61 | 62 | this.handleChangeNetwork = this.handleChangeNetwork.bind(this); 63 | 64 | this.handleLocationTypeChange = this.handleLocationTypeChange.bind(this); 65 | this.handleExtraZonesChange = this.handleExtraZonesChange.bind(this); 66 | this.handlePropChange = this.handlePropChange.bind(this); 67 | } 68 | 69 | 70 | /** 71 | * Handle selecting a different location type. 72 | * 73 | * @param {Object} newValue the new value. 74 | */ 75 | handleLocationTypeChange(newValue) { 76 | this.props.handleUpdateInput( 77 | Object.assign({}, this.props.input, { 78 | locationType: newValue, 79 | }), 80 | ); 81 | } 82 | 83 | 84 | /** 85 | * Handle selecting a different number of extra zones (used for zonal clusters only). 86 | * 87 | * @param {Object} newValue the new value. 88 | */ 89 | handleExtraZonesChange(newValue) { 90 | this.props.handleUpdateInput( 91 | Object.assign({}, this.props.input, { 92 | extraZones: newValue, 93 | }), 94 | ); 95 | } 96 | 97 | 98 | /** 99 | * Handle changing the network. 100 | * 101 | * @param {Object} e the change event. 102 | */ 103 | handleChangeNetwork(e) { 104 | this.props.handleUpdateInput( 105 | Object.assign({}, this.props.input, { 106 | network: e.target.value, 107 | }), 108 | ); 109 | } 110 | 111 | /** 112 | * Handle changing an arbitrary property. 113 | * 114 | * @param {string} propName the name of the property to update. 115 | * @param {Object} newValue the new value. 116 | */ 117 | handlePropChange(propName, newValue) { 118 | this.props.handleUpdateInput( 119 | Object.assign({}, this.props.input, { 120 | [propName]: newValue, 121 | }), 122 | ); 123 | } 124 | 125 | /** 126 | * Render the widgets for the parameters. 127 | * 128 | * @return {Object} the Form element that contains the parameters. 129 | */ 130 | render() { 131 | let extraZonesLabel; 132 | let extraZonesInput; 133 | 134 | if (this.props.input.locationType === 'MULTI_ZONAL') { 135 | extraZonesLabel = ( 136 | 139 | ); 140 | extraZonesInput = ( 141 | 142 | 149 | 150 | ); 151 | } else { 152 | extraZonesLabel = ' '; 153 | extraZonesInput = ' '; 154 | } 155 | 156 | return ( 157 |
158 | 159 | 162 | 163 | 170 | 171 | {extraZonesLabel} 172 | {extraZonesInput} 173 | 174 | 175 | 176 | {' '} 180 | 181 | {' '} 189 | {' '} 190 | 191 | 192 | 200 | 201 | 209 | 210 | 218 | 219 | 227 | 228 | 236 | 237 | 245 | 246 | ); 247 | } 248 | } 249 | 250 | Parameters.propTypes = { 251 | input: PropTypes.object, 252 | inputRules: PropTypes.instanceOf(InputRules), 253 | handleUpdateInput: PropTypes.func, 254 | }; 255 | 256 | export default Parameters; 257 | 258 | export {nodePodNetmaskOptions}; 259 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 5 | 1. Definitions. 6 | "License" shall mean the terms and conditions for use, reproduction, 7 | and distribution as defined by Sections 1 through 9 of this document. 8 | "Licensor" shall mean the copyright owner or entity authorized by 9 | the copyright owner that is granting the License. 10 | "Legal Entity" shall mean the union of the acting entity and all 11 | other entities that control, are controlled by, or are under common 12 | control with that entity. For the purposes of this definition, 13 | "control" means (i) the power, direct or indirect, to cause the 14 | direction or management of such entity, whether by contract or 15 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 16 | outstanding shares, or (iii) beneficial ownership of such entity. 17 | "You" (or "Your") shall mean an individual or Legal Entity 18 | exercising permissions granted by this License. 19 | "Source" form shall mean the preferred form for making modifications, 20 | including but not limited to software source code, documentation 21 | source, and configuration files. 22 | "Object" form shall mean any form resulting from mechanical 23 | transformation or translation of a Source form, including but 24 | not limited to compiled object code, generated documentation, 25 | and conversions to other media types. 26 | "Work" shall mean the work of authorship, whether in Source or 27 | Object form, made available under the License, as indicated by a 28 | copyright notice that is included in or attached to the work 29 | (an example is provided in the Appendix below). 30 | "Derivative Works" shall mean any work, whether in Source or Object 31 | form, that is based on (or derived from) the Work and for which the 32 | editorial revisions, annotations, elaborations, or other modifications 33 | represent, as a whole, an original work of authorship. For the purposes 34 | of this License, Derivative Works shall not include works that remain 35 | separable from, or merely link (or bind by name) to the interfaces of, 36 | the Work and Derivative Works thereof. 37 | "Contribution" shall mean any work of authorship, including 38 | the original version of the Work and any modifications or additions 39 | to that Work or Derivative Works thereof, that is intentionally 40 | submitted to Licensor for inclusion in the Work by the copyright owner 41 | or by an individual or Legal Entity authorized to submit on behalf of 42 | the copyright owner. For the purposes of this definition, "submitted" 43 | means any form of electronic, verbal, or written communication sent 44 | to the Licensor or its representatives, including but not limited to 45 | communication on electronic mailing lists, source code control systems, 46 | and issue tracking systems that are managed by, or on behalf of, the 47 | Licensor for the purpose of discussing and improving the Work, but 48 | excluding communication that is conspicuously marked or otherwise 49 | designated in writing by the copyright owner as "Not a Contribution." 50 | "Contributor" shall mean Licensor and any individual or Legal Entity 51 | on behalf of whom a Contribution has been received by Licensor and 52 | subsequently incorporated within the Work. 53 | 2. Grant of Copyright License. Subject to the terms and conditions of 54 | this License, each Contributor hereby grants to You a perpetual, 55 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 56 | copyright license to reproduce, prepare Derivative Works of, 57 | publicly display, publicly perform, sublicense, and distribute the 58 | Work and such Derivative Works in Source or Object form. 59 | 3. Grant of Patent License. Subject to the terms and conditions of 60 | this License, each Contributor hereby grants to You a perpetual, 61 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 62 | (except as stated in this section) patent license to make, have made, 63 | use, offer to sell, sell, import, and otherwise transfer the Work, 64 | where such license applies only to those patent claims licensable 65 | by such Contributor that are necessarily infringed by their 66 | Contribution(s) alone or by combination of their Contribution(s) 67 | with the Work to which such Contribution(s) was submitted. If You 68 | institute patent litigation against any entity (including a 69 | cross-claim or counterclaim in a lawsuit) alleging that the Work 70 | or a Contribution incorporated within the Work constitutes direct 71 | or contributory patent infringement, then any patent licenses 72 | granted to You under this License for that Work shall terminate 73 | as of the date such litigation is filed. 74 | 4. Redistribution. You may reproduce and distribute copies of the 75 | Work or Derivative Works thereof in any medium, with or without 76 | modifications, and in Source or Object form, provided that You 77 | meet the following conditions: 78 | (a) You must give any other recipients of the Work or 79 | Derivative Works a copy of this License; and 80 | (b) You must cause any modified files to carry prominent notices 81 | stating that You changed the files; and 82 | (c) You must retain, in the Source form of any Derivative Works 83 | that You distribute, all copyright, patent, trademark, and 84 | attribution notices from the Source form of the Work, 85 | excluding those notices that do not pertain to any part of 86 | the Derivative Works; and 87 | (d) If the Work includes a "NOTICE" text file as part of its 88 | distribution, then any Derivative Works that You distribute must 89 | include a readable copy of the attribution notices contained 90 | within such NOTICE file, excluding those notices that do not 91 | pertain to any part of the Derivative Works, in at least one 92 | of the following places: within a NOTICE text file distributed 93 | as part of the Derivative Works; within the Source form or 94 | documentation, if provided along with the Derivative Works; or, 95 | within a display generated by the Derivative Works, if and 96 | wherever such third-party notices normally appear. The contents 97 | of the NOTICE file are for informational purposes only and 98 | do not modify the License. You may add Your own attribution 99 | notices within Derivative Works that You distribute, alongside 100 | or as an addendum to the NOTICE text from the Work, provided 101 | that such additional attribution notices cannot be construed 102 | as modifying the License. 103 | You may add Your own copyright statement to Your modifications and 104 | may provide additional or different license terms and conditions 105 | for use, reproduction, or distribution of Your modifications, or 106 | for any such Derivative Works as a whole, provided Your use, 107 | reproduction, and distribution of the Work otherwise complies with 108 | the conditions stated in this License. 109 | 5. Submission of Contributions. Unless You explicitly state otherwise, 110 | any Contribution intentionally submitted for inclusion in the Work 111 | by You to the Licensor shall be under the terms and conditions of 112 | this License, without any additional terms or conditions. 113 | Notwithstanding the above, nothing herein shall supersede or modify 114 | the terms of any separate license agreement you may have executed 115 | with Licensor regarding such Contributions. 116 | 6. Trademarks. This License does not grant permission to use the trade 117 | names, trademarks, service marks, or product names of the Licensor, 118 | except as required for reasonable and customary use in describing the 119 | origin of the Work and reproducing the content of the NOTICE file. 120 | 7. Disclaimer of Warranty. Unless required by applicable law or 121 | agreed to in writing, Licensor provides the Work (and each 122 | Contributor provides its Contributions) on an "AS IS" BASIS, 123 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 124 | implied, including, without limitation, any warranties or conditions 125 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 126 | PARTICULAR PURPOSE. You are solely responsible for determining the 127 | appropriateness of using or redistributing the Work and assume any 128 | risks associated with Your exercise of permissions under this License. 129 | 8. Limitation of Liability. In no event and under no legal theory, 130 | whether in tort (including negligence), contract, or otherwise, 131 | unless required by applicable law (such as deliberate and grossly 132 | negligent acts) or agreed to in writing, shall any Contributor be 133 | liable to You for damages, including any direct, indirect, special, 134 | incidental, or consequential damages of any character arising as a 135 | result of this License or out of the use or inability to use the 136 | Work (including but not limited to damages for loss of goodwill, 137 | work stoppage, computer failure or malfunction, or any and all 138 | other commercial damages or losses), even if such Contributor 139 | has been advised of the possibility of such damages. 140 | 9. Accepting Warranty or Additional Liability. While redistributing 141 | the Work or Derivative Works thereof, You may choose to offer, 142 | and charge a fee for, acceptance of support, warranty, indemnity, 143 | or other liability obligations and/or rights consistent with this 144 | License. However, in accepting such obligations, You may act only 145 | on Your own behalf and on Your sole responsibility, not on behalf 146 | of any other Contributor, and only if You agree to indemnify, 147 | defend, and hold each Contributor harmless for any liability 148 | incurred by, or claims asserted against, such Contributor by reason 149 | of your accepting any such warranty or additional liability. 150 | END OF TERMS AND CONDITIONS 151 | APPENDIX: How to apply the Apache License to your work. 152 | To apply the Apache License to your work, attach the following 153 | boilerplate notice, with the fields enclosed by brackets "{}" 154 | replaced with your own identifying information. (Don't include 155 | the brackets!) The text should be enclosed in the appropriate 156 | comment syntax for the file format. We also recommend that a 157 | file or class name and description of purpose be included on the 158 | same "printed page" as the copyright notice for easier 159 | identification within third-party archives. 160 | Copyright 2019 Google Inc. 161 | Licensed under the Apache License, Version 2.0 (the "License"); 162 | you may not use this file except in compliance with the License. 163 | You may obtain a copy of the License at 164 | http://www.apache.org/licenses/LICENSE-2.0 165 | Unless required by applicable law or agreed to in writing, software 166 | distributed under the License is distributed on an "AS IS" BASIS, 167 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 168 | See the License for the specific language governing permissions and 169 | limitations under the License. -------------------------------------------------------------------------------- /test/verify_boilerplate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3.7 2 | # Copyright 2019 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # https://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # Verifies that all source files contain the necessary copyright boilerplate 16 | # snippet. 17 | 18 | # This code is based on existing work 19 | # https://partner-code.googlesource.com/helmsman-cardinal/+/refs/heads/master/helmsman-template-project/test/verify_boilerplate.py 20 | 21 | 22 | """ 23 | A runnable module to test the presence of boilerplate 24 | text in files within a repo. 25 | """ 26 | 27 | from __future__ import print_function 28 | from subprocess import run, CalledProcessError 29 | import argparse 30 | import glob 31 | import os 32 | import re 33 | import sys 34 | 35 | 36 | # These directories will be omitted from header checks 37 | SKIPPED_PATHS = [ 38 | 'Godeps', 'third_party', '_gopath', '_output', 39 | '.git', 'vendor', '__init__.py', 'node_modules', 40 | 'bazel-out', 'external', '3rdparty' 41 | ] 42 | 43 | # A map of regular expressions used in boilerplate validation. 44 | # The date regex is used in validating the date referenced 45 | # is the boilerplate, by ensuring it is an acceptable year. 46 | REGEXES = { 47 | # beware the Y2100 problem 48 | "date": re.compile(r'(20\d\d)') 49 | } 50 | 51 | 52 | def get_args(): 53 | """Parses command line arguments. 54 | Configures and runs argparse.ArgumentParser to extract command line 55 | arguments. 56 | Returns: 57 | An argparse.Namespace containing the arguments parsed from the 58 | command line 59 | """ 60 | parser = argparse.ArgumentParser() 61 | 62 | parser.add_argument("filenames", 63 | help="""A list of files to check, all in repo are 64 | checked if this is unspecified.""", 65 | nargs='*') 66 | 67 | parser.add_argument("-f", "--force-extension", 68 | default="", 69 | help="""Force an extension to compare against. Useful 70 | for files without extensions, such as runnable shell 71 | scripts .""") 72 | 73 | parser.add_argument( 74 | "-r", "--rootdir", 75 | default=None, 76 | help="""Root directory of repository. If not specified, the script will 77 | attempt to draw this value from git.""") 78 | 79 | parser.add_argument("-b", "--boilerplate-dir", 80 | default=None, 81 | help="""Directory with boilerplate files. Defaults to 82 | [root]/test/boilerplate.""") 83 | 84 | args = parser.parse_args() 85 | 86 | if not args.rootdir: 87 | ask_git = run( 88 | ["git", "rev-parse", "--show-toplevel"], 89 | capture_output=True, text=True) 90 | try: 91 | ask_git.check_returncode() 92 | except CalledProcessError: 93 | print("""No root specfied and directory does not seem to be a git 94 | repository, or git is not installed.""", file=sys.stderr) 95 | sys.exit(1) 96 | args.rootdir = ask_git.stdout.strip() 97 | 98 | if not args.boilerplate_dir: 99 | args.boilerplate_dir = os.path.join(args.rootdir, "test/boilerplate") 100 | 101 | return args 102 | 103 | 104 | def get_references(args): 105 | """Reads each reference boilerplate file's contents into an array, then 106 | adds that array to a dictionary keyed by the file extension. 107 | 108 | Returns: 109 | A dictionary of boilerplate lines, keyed by file extension. 110 | For example, boilerplate.py.txt would result in the 111 | k,v pair {".py": py_lines} where py_lines is an array 112 | containing each line of the file. 113 | """ 114 | references = {} 115 | 116 | # Find all paths for boilerplate references 117 | boilerplate_paths = glob.glob( 118 | os.path.join(args.boilerplate_dir, "boilerplate.*.txt")) 119 | 120 | # Read all boilerplate references into dictionary 121 | for path in boilerplate_paths: 122 | with open(path, 'r') as ref_file: 123 | extension = os.path.basename(path).split(".")[1] 124 | ref = ref_file.read().splitlines() 125 | references[extension] = ref 126 | 127 | return references 128 | 129 | 130 | # Improvement: combine this function with `get_references` 131 | def get_preambles(args): 132 | """Reads each preamble boilerplate file's contents into an array, then 133 | adds that array to a dictionary keyed by the file extension. 134 | 135 | Returns: 136 | A dictionary of boilerplate lines, keyed by file extension. 137 | For example, boilerplate.py.preamble would result 138 | in the k,v pair {".py": py_lines} where py_lines is 139 | an array containing each line of the file 140 | (ex: "#!/usr/bin/env python3.7") 141 | """ 142 | preambles = {} 143 | 144 | # Find all paths for boilerplate preambles 145 | boilerplate_paths = glob.glob( 146 | os.path.join(args.boilerplate_dir, "boilerplate.*.preamble")) 147 | 148 | # Read all boilerplate preambles into dictionary 149 | for path in boilerplate_paths: 150 | with open(path, 'r') as ref_file: 151 | extension = os.path.basename(path).split(".")[1] 152 | ref = ref_file.read().splitlines() 153 | preambles[extension] = ref 154 | 155 | return preambles 156 | 157 | 158 | def has_valid_header(filename, references, preambles, regexs, args): 159 | """Test whether a file has the correct boilerplate header. 160 | Tests each file against the boilerplate stored in refs for that file type 161 | (based on extension), or by the entire filename (eg Dockerfile, Makefile). 162 | Some heuristics are applied to remove build tags and shebangs, but little 163 | variance in header formatting is tolerated. 164 | Args: 165 | filename: A string containing the name of the file to test 166 | references: A map of reference boilerplate text, 167 | keyed by file extension 168 | preambles: A map of preamble boilerplate text, keyed by file extension 169 | regexs: a map of compiled regex objects used in verifying boilerplate 170 | Returns: 171 | True if the file has the correct boilerplate header, otherwise returns 172 | False. 173 | """ 174 | # Read the entire file. 175 | with open(filename, 'r') as test_file: 176 | data = test_file.read() 177 | 178 | # Select the appropriate reference based on the extension, 179 | # or if none, the file name. 180 | basename, extension = get_file_parts(filename) 181 | if args.force_extension: 182 | extension = args.force_extension 183 | elif extension: 184 | extension = extension 185 | else: 186 | extension = basename 187 | ref = references[extension] 188 | print("Verifying boilerplate in file: %s as %s" % ( 189 | os.path.relpath(filename, args.rootdir), 190 | extension)) 191 | 192 | preamble = preambles.get(extension) 193 | if preamble: 194 | preamble = re.escape("\n".join(preamble)) 195 | regflags = re.MULTILINE | re.IGNORECASE 196 | regex = re.compile(r"^(%s.*\n)\n*" % preamble, regflags) 197 | (data, _) = regex.subn("", data, 1) 198 | 199 | data = data.splitlines() 200 | 201 | # if our test file is smaller than the reference it surely fails! 202 | if len(ref) > len(data): 203 | return False 204 | # truncate our file to the same number of lines as the reference file 205 | data = data[:len(ref)] 206 | 207 | # if we don't match the reference at this point, fail 208 | if ref != data: 209 | return False 210 | 211 | return True 212 | 213 | 214 | def get_file_parts(filename): 215 | """Extracts the basename and extension parts of a filename. 216 | Identifies the extension as everything after the last period in filename. 217 | Args: 218 | filename: string containing the filename 219 | Returns: 220 | A tuple of: 221 | A string containing the basename 222 | A string containing the extension in lowercase 223 | """ 224 | extension = os.path.splitext(filename)[1].split(".")[-1].lower() 225 | basename = os.path.basename(filename) 226 | return basename, extension 227 | 228 | 229 | def normalize_files(files, args): 230 | """Extracts the files that require boilerplate checking from the files 231 | argument. 232 | A new list will be built. Each path from the original files argument will 233 | be added unless it is within one of SKIPPED_DIRS. All relative paths will 234 | be converted to absolute paths by prepending the root_dir path parsed from 235 | the command line, or its default value. 236 | Args: 237 | files: a list of file path strings 238 | Returns: 239 | A modified copy of the files list where any any path in a skipped 240 | directory is removed, and all paths have been made absolute. 241 | """ 242 | newfiles = [f for f in files if not any(s in f for s in SKIPPED_PATHS)] 243 | 244 | for idx, pathname in enumerate(newfiles): 245 | if not os.path.isabs(pathname): 246 | newfiles[idx] = os.path.join(args.rootdir, pathname) 247 | return newfiles 248 | 249 | 250 | def get_files(extensions, args): 251 | """Generates a list of paths whose boilerplate should be verified. 252 | If a list of file names has been provided on the command line, it will be 253 | treated as the initial set to search. Otherwise, all paths within rootdir 254 | will be discovered and used as the initial set. 255 | Once the initial set of files is identified, it is normalized via 256 | normalize_files() and further stripped of any file name whose extension is 257 | not in extensions. 258 | Args: 259 | extensions: a list of file extensions indicating which file types 260 | should have their boilerplate verified 261 | Returns: 262 | A list of absolute file paths 263 | """ 264 | files = [] 265 | if args.filenames: 266 | files = args.filenames 267 | else: 268 | for root, dirs, walkfiles in os.walk(args.rootdir): 269 | # don't visit certain dirs. This is just a performance improvement 270 | # as we would prune these later in normalize_files(). But doing it 271 | # cuts down the amount of filesystem walking we do and cuts down 272 | # the size of the file list 273 | for dpath in SKIPPED_PATHS: 274 | if dpath in dirs: 275 | dirs.remove(dpath) 276 | for name in walkfiles: 277 | pathname = os.path.join(root, name) 278 | files.append(pathname) 279 | files = normalize_files(files, args) 280 | outfiles = [] 281 | for pathname in files: 282 | basename, extension = get_file_parts(pathname) 283 | extension_present = extension in extensions or basename in extensions 284 | if args.force_extension or extension_present: 285 | outfiles.append(pathname) 286 | return outfiles 287 | 288 | 289 | def main(args): 290 | """Identifies and verifies files that should have the desired boilerplate. 291 | Retrieves the lists of files to be validated and tests each one in turn. 292 | If all files contain correct boilerplate, this function terminates 293 | normally. Otherwise it prints the name of each non-conforming file and 294 | exists with a non-zero status code. 295 | """ 296 | refs = get_references(args) 297 | preambles = get_preambles(args) 298 | filenames = get_files(refs.keys(), args) 299 | nonconforming_files = [] 300 | for filename in filenames: 301 | if not has_valid_header(filename, refs, preambles, REGEXES, args): 302 | nonconforming_files.append(filename) 303 | if nonconforming_files: 304 | print('%d files have incorrect boilerplate headers:' % len( 305 | nonconforming_files)) 306 | for filename in sorted(nonconforming_files): 307 | print(os.path.relpath(filename, args.rootdir)) 308 | sys.exit(1) 309 | else: 310 | print('All files examined have correct boilerplate.') 311 | 312 | 313 | if __name__ == "__main__": 314 | ARGS = get_args() 315 | main(ARGS) 316 | --------------------------------------------------------------------------------