├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── dependabot.yml ├── release-drafter.yml └── workflows │ ├── codeql-analysis.yml │ ├── release-drafter.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .goreleaser.yml ├── .vscode └── launch.json ├── CHANGELOG.md ├── GNUmakefile ├── LICENSE ├── README.md ├── docker-compose.yaml ├── docs ├── data-sources │ ├── netmaker_access_key.md │ ├── netmaker_network.md │ ├── netmaker_networks.md │ └── netmaker_node.md ├── index.md └── resources │ ├── netmaker_access_key.md │ ├── netmaker_egress.md │ ├── netmaker_ingress.md │ ├── netmaker_network.md │ └── netmaker_user.md ├── examples ├── README.md ├── data-sources │ └── netmaker_networks │ │ └── data-source.tf ├── provider │ └── provider.tf └── resources │ └── netmaker_network │ └── resource.tf ├── go.mod ├── go.sum ├── helper ├── access_key.go ├── access_key_test.go ├── auth.go ├── auth_test.go ├── client.go ├── network.go ├── network_test.go ├── node.go ├── node_test.go ├── user.go └── user_test.go ├── internal └── provider │ ├── data_source_access_key.go │ ├── data_source_access_key_test.go │ ├── data_source_network.go │ ├── data_source_network_test.go │ ├── data_source_networks.go │ ├── data_source_networks_test.go │ ├── data_source_node.go │ ├── data_source_node_test.go │ ├── provider.go │ ├── provider_test.go │ ├── resource_access_key.go │ ├── resource_access_key_test.go │ ├── resource_egress.go │ ├── resource_egress_test.go │ ├── resource_ingress.go │ ├── resource_ingress_test.go │ ├── resource_network.go │ ├── resource_network_test.go │ ├── resource_user.go │ └── resource_user_test.go ├── main.go └── tools └── tools.go /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | HashiCorp Community Guidelines apply to you when interacting with the community here on GitHub and contributing code. 4 | 5 | Please read the full text at https://www.hashicorp.com/community-guidelines 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi there, 2 | 3 | Thank you for opening an issue. Please note that we try to keep the Terraform issue tracker reserved for bug reports and feature requests. For general usage questions, please see: https://www.terraform.io/community.html. 4 | 5 | ### Terraform Version 6 | Run `terraform -v` to show the version. If you are not running the latest version of Terraform, please upgrade because your issue may have already been fixed. 7 | 8 | ### Affected Resource(s) 9 | Please list the resources as a list, for example: 10 | - opc_instance 11 | - opc_storage_volume 12 | 13 | If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this. 14 | 15 | ### Terraform Configuration Files 16 | ```hcl 17 | # Copy-paste your Terraform configurations here - for large Terraform configs, 18 | # please use a service like Dropbox and share a link to the ZIP file. For 19 | # security, you can also encrypt the files using our GPG public key. 20 | ``` 21 | 22 | ### Debug Output 23 | Please provider a link to a GitHub Gist containing the complete debug output: https://www.terraform.io/docs/internals/debugging.html. Please do NOT paste the debug output in the issue; just paste a link to the Gist. 24 | 25 | ### Panic Output 26 | If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the `crash.log`. 27 | 28 | ### Expected Behavior 29 | What should have happened? 30 | 31 | ### Actual Behavior 32 | What actually happened? 33 | 34 | ### Steps to Reproduce 35 | Please list the steps required to reproduce the issue, for example: 36 | 1. `terraform apply` 37 | 38 | ### Important Factoids 39 | Are there anything atypical about your accounts that we should know? For example: Running in EC2 Classic? Custom version of OpenStack? Tight ACLs? 40 | 41 | ### References 42 | Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example: 43 | - GH-1234 44 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # See GitHub's docs for more information on this file: 2 | # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates 3 | version: 2 4 | updates: 5 | # Maintain dependencies for GitHub Actions 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every weekday 10 | interval: "daily" 11 | 12 | # Maintain dependencies for Go modules 13 | - package-ecosystem: "gomod" 14 | directory: "/" 15 | schedule: 16 | # Check for updates to Go modules every weekday 17 | interval: "daily" 18 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION 🌈' 2 | template: "## Changes\n $CHANGES" 3 | tag-template: 'v$RESOLVED_VERSION' 4 | categories: 5 | - title: '🚀 Features' 6 | labels: 7 | - 'feature' 8 | - 'enhancement' 9 | - title: '🐛 Bug Fixes' 10 | labels: 11 | - 'fix' 12 | - 'bugfix' 13 | - 'bug' 14 | - title: '🧰 Maintenance' 15 | label: 'chore' 16 | - title: '📗 Docs' 17 | label: 'docs' 18 | - title: '🥘 Dependencies Update' 19 | label: 'dependencies' 20 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 21 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 22 | version-resolver: 23 | major: 24 | labels: 25 | - 'major' 26 | minor: 27 | labels: 28 | - 'minor' 29 | - 'feature' 30 | patch: 31 | labels: 32 | - 'patch' 33 | default: patch 34 | autolabeler: 35 | - label: 'docs' 36 | files: 37 | - '*.md' 38 | branch: 39 | - '/docs\/.+/' 40 | - label: 'chore' 41 | branch: 42 | - '/chore\/.+/' 43 | - label: 'bug' 44 | branch: 45 | - '/fix\/.+/' 46 | title: 47 | - '/fix/i' 48 | - label: 'feature' 49 | branch: 50 | - '/feat\/.+/' 51 | 52 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '24 11 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v2 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v2 71 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | # branches to consider in the event; optional, defaults to all 6 | branches: 7 | - master 8 | # pull_request event is required only for autolabeler 9 | pull_request: 10 | # Only following types are handled by the action, but one can default to all as well 11 | types: [opened, reopened, synchronize] 12 | 13 | jobs: 14 | update_release_draft: 15 | runs-on: ubuntu-latest 16 | steps: 17 | # Drafts your next Release notes as Pull Requests are merged into "master" 18 | - uses: release-drafter/release-drafter@v5 19 | # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml 20 | # with: 21 | # config-name: my-config.yml 22 | # disable-autolabeler: true 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action can publish assets for release when a tag is created. 2 | # Currently its setup to run on any tag that matches the pattern "v*" (ie. v0.1.0). 3 | # 4 | # This uses an action (hashicorp/ghaction-import-gpg) that assumes you set your 5 | # private key in the `GPG_PRIVATE_KEY` secret and passphrase in the `PASSPHRASE` 6 | # secret. If you would rather own your own GPG handling, please fork this action 7 | # or use an alternative one for key handling. 8 | # 9 | # You will need to pass the `--batch` flag to `gpg` in your signing step 10 | # in `goreleaser` to indicate this is being used in a non-interactive mode. 11 | # 12 | name: release 13 | on: 14 | push: 15 | tags: 16 | - 'v*' 17 | jobs: 18 | goreleaser: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - 22 | name: Checkout 23 | uses: actions/checkout@v3 24 | - 25 | name: Unshallow 26 | run: git fetch --prune --unshallow 27 | - 28 | name: Set up Go 29 | uses: actions/setup-go@v2 30 | with: 31 | go-version: 1.14 32 | - 33 | name: Import GPG key 34 | id: import_gpg 35 | uses: hashicorp/ghaction-import-gpg@v2.1.0 36 | env: 37 | # These secrets will need to be configured for the repository: 38 | GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} 39 | PASSPHRASE: ${{ secrets.PASSPHRASE }} 40 | - 41 | name: Run GoReleaser 42 | uses: goreleaser/goreleaser-action@v2.8.0 43 | with: 44 | version: latest 45 | args: release --rm-dist 46 | env: 47 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 48 | # GitHub sets this automatically 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This GitHub action runs your tests for each commit push and/or PR. Optionally 2 | # you can turn it on using a cron schedule for regular testing. 3 | # 4 | name: Tests 5 | on: 6 | pull_request: 7 | paths-ignore: 8 | - 'README.md' 9 | push: 10 | paths-ignore: 11 | - 'README.md' 12 | # For systems with an upstream API that could drift unexpectedly (like most SaaS systems, etc.), 13 | # we recommend testing at a regular interval not necessarily tied to code changes. This will 14 | # ensure you are alerted to something breaking due to an API change, even if the code did not 15 | # change. 16 | # schedule: 17 | # - cron: '0 13 * * *' 18 | jobs: 19 | # ensure the code builds... 20 | build: 21 | name: Build 22 | runs-on: ubuntu-latest 23 | timeout-minutes: 5 24 | steps: 25 | 26 | - name: Set up Go 27 | uses: actions/setup-go@v2.1.3 28 | with: 29 | go-version: '1.15' 30 | id: go 31 | 32 | - name: Check out code into the Go module directory 33 | uses: actions/checkout@v3 34 | 35 | - name: Get dependencies 36 | run: | 37 | go mod download 38 | 39 | - name: Build 40 | run: | 41 | go build -v . 42 | 43 | # run acceptance tests in a matrix with Terraform core versions 44 | test: 45 | name: Matrix Test 46 | needs: build 47 | runs-on: ubuntu-latest 48 | timeout-minutes: 15 49 | strategy: 50 | fail-fast: false 51 | matrix: 52 | # list whatever Terraform versions here you would like to support 53 | terraform: 54 | - '0.12.29' 55 | - '0.13.4' 56 | - '0.14.0-beta2' 57 | steps: 58 | 59 | - name: Set up Go 60 | uses: actions/setup-go@v2.1.3 61 | with: 62 | go-version: '1.15' 63 | id: go 64 | 65 | - name: Check out code into the Go module directory 66 | uses: actions/checkout@v3 67 | 68 | - name: Get dependencies 69 | run: | 70 | go mod download 71 | 72 | - name: TF acceptance tests 73 | timeout-minutes: 10 74 | env: 75 | TF_ACC: "1" 76 | TF_ACC_TERRAFORM_VERSION: ${{ matrix.terraform }} 77 | 78 | # Set whatever additional acceptance test env vars here. You can 79 | # optionally use data from your repository secrets using the 80 | # following syntax: 81 | # SOME_VAR: ${{ secrets.SOME_VAR }} 82 | 83 | run: | 84 | go test -v -cover ./internal/provider/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dll 2 | *.exe 3 | .DS_Store 4 | example.tf 5 | terraform.tfplan 6 | terraform.tfstate 7 | bin/ 8 | dist/ 9 | modules-dev/ 10 | /pkg/ 11 | website/.vagrant 12 | website/.bundle 13 | website/build 14 | website/node_modules 15 | .vagrant/ 16 | *.backup 17 | ./*.tfstate 18 | .terraform/ 19 | *.log 20 | *.bak 21 | *~ 22 | .*.swp 23 | .idea 24 | *.iml 25 | *.test 26 | *.iml 27 | *.lock.hcl 28 | website/vendor 29 | vendor 30 | 31 | # Test exclusions 32 | !command/test-fixtures/**/*.tfstate 33 | !command/test-fixtures/**/.terraform/ 34 | 35 | # Keep windows files with windows line endings 36 | *.winfile eol=crlf 37 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | # Visit https://goreleaser.com for documentation on how to customize this 2 | # behavior. 3 | before: 4 | hooks: 5 | # this is just an example and not a requirement for provider building/publishing 6 | - go mod tidy 7 | builds: 8 | - env: 9 | # goreleaser does not work with CGO, it could also complicate 10 | # usage by users in CI/CD systems like Terraform Cloud where 11 | # they are unable to install libraries. 12 | - CGO_ENABLED=0 13 | mod_timestamp: '{{ .CommitTimestamp }}' 14 | flags: 15 | - -trimpath 16 | ldflags: 17 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 18 | goos: 19 | - freebsd 20 | - windows 21 | - linux 22 | - darwin 23 | goarch: 24 | - amd64 25 | - '386' 26 | - arm 27 | - arm64 28 | ignore: 29 | - goos: darwin 30 | goarch: '386' 31 | binary: '{{ .ProjectName }}_v{{ .Version }}' 32 | archives: 33 | - format: zip 34 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 35 | checksum: 36 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 37 | algorithm: sha256 38 | signs: 39 | - artifacts: checksum 40 | args: 41 | # if you are using this in a GitHub action or some other automated pipeline, you 42 | # need to pass the batch flag to indicate its not interactive. 43 | - "--batch" 44 | - "--local-user" 45 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 46 | - "--output" 47 | - "${signature}" 48 | - "--detach-sign" 49 | - "${artifact}" 50 | release: 51 | # If you want to manually examine the release before its live, uncomment this line: 52 | # draft: true 53 | changelog: 54 | skip: true 55 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Acceptance Tests", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "test", 12 | // this assumes your workspace is the root of the repo 13 | "program": "${fileDirname}", 14 | "env": { 15 | "TF_ACC": "1", 16 | }, 17 | "args": [], 18 | }, 19 | { 20 | "name": "Debug - Attach External CLI", 21 | "type": "go", 22 | "request": "launch", 23 | "mode": "debug", 24 | // this assumes your workspace is the root of the repo 25 | "program": "${workspaceFolder}", 26 | "env": {}, 27 | "args": [ 28 | // pass the debug flag for reattaching 29 | "-debug", 30 | ], 31 | } 32 | ] 33 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 (Unreleased) 2 | 3 | BACKWARDS INCOMPATIBILITIES / NOTES: 4 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | TEST?=$$(go list ./... | grep -v 'vendor') 2 | HOSTNAME=github.com 3 | NAMESPACE=madacluster 4 | NAME=netmaker 5 | BINARY=terraform-provider-${NAME} 6 | VERSION=0.2 7 | OS_ARCH=darwin_amd64 8 | 9 | default: install 10 | 11 | build: 12 | go build -o ${BINARY} 13 | 14 | release: 15 | GOOS=darwin GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_darwin_amd64 16 | GOOS=freebsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_freebsd_386 17 | GOOS=freebsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_freebsd_amd64 18 | GOOS=freebsd GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_freebsd_arm 19 | GOOS=linux GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_linux_386 20 | GOOS=linux GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_linux_amd64 21 | GOOS=linux GOARCH=arm go build -o ./bin/${BINARY}_${VERSION}_linux_arm 22 | GOOS=openbsd GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_openbsd_386 23 | GOOS=openbsd GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_openbsd_amd64 24 | GOOS=solaris GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_solaris_amd64 25 | GOOS=windows GOARCH=386 go build -o ./bin/${BINARY}_${VERSION}_windows_386 26 | GOOS=windows GOARCH=amd64 go build -o ./bin/${BINARY}_${VERSION}_windows_amd64 27 | 28 | install: build 29 | mkdir -p ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 30 | mv ${BINARY} ~/.terraform.d/plugins/${HOSTNAME}/${NAMESPACE}/${NAME}/${VERSION}/${OS_ARCH} 31 | 32 | test: 33 | go test -i $(TEST) || exit 1 34 | echo $(TEST) | xargs -t -n4 go test $(TESTARGS) -timeout=30s -parallel=4 35 | 36 | testacc: 37 | TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Terraform Provider Netmaker 2 | [![CodeFactor](https://www.codefactor.io/repository/github/madacluster/netmaker-terraform-provider/badge)](https://www.codefactor.io/repository/github/madacluster/netmaker-terraform-provider) 3 | 4 | This repository is a *template* for a [Terraform](https://www.terraform.io) provider. It is intended as a starting point for creating Terraform providers, containing: 5 | 6 | - A resource, and a data source (`internal/provider/`), 7 | - Examples (`examples/`) and generated documentation (`docs/`), 8 | - Miscellaneous meta files. 9 | 10 | These files contain boilerplate code that you will need to edit to create your own Terraform provider. A full guide to creating Terraform providers can be found at [Writing Custom Providers](https://www.terraform.io/docs/extend/writing-custom-providers.html). 11 | 12 | Please see the [GitHub template repository documentation](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template) for how to create a new repository from this template on GitHub. 13 | 14 | Once you've written your provider, you'll want to [publish it on the Terraform Registry](https://www.terraform.io/docs/registry/providers/publishing.html) so that others can use it. 15 | 16 | ## Features 17 | **Networks API** 18 | - [x] Get All Networks: /api/networks, GET 19 | 20 | - [x] Create Network: /api/network, POST 21 | 22 | - [x] Get Network: /api/networks/{network id}, GET 23 | 24 | - [x] Update Network: /api/networks/{network id}, PUT 25 | 26 | - [x] Delete Network: /api/networks/{network id}, DELETE 27 | 28 | **Access Keys API** 29 | 30 | - [ ] Get All Keys: /api/networks/{network id}/keys, GET 31 | 32 | - [ ] Create Key: /api/networks/{network id}/keys, GET 33 | 34 | - [ ] Delete Key: /api/networks/{network id}/keys/{keyname}, DELETE 35 | 36 | **Nodes API** 37 | 38 | - [ ] Create a Gateway: /api/nodes/{network id}/{macaddress}/creategateway, POST 39 | 40 | - [ ] Delete a Gateway: /api/nodes/{network id}/{macaddress}/deletegateway, DELETE 41 | 42 | - [ ] Get Network Nodes: /api/nodes/{network id}, GET 43 | 44 | - [ ] Get Node: /api/nodes/{network id}/{macaddress}, GET 45 | 46 | - [ ] Update Node: /api/nodes/{network id}/{macaddress}, PUT 47 | 48 | - [ ] Delete Node: /api/nodes/{network id}/{macaddress}, DELETE 49 | 50 | **Users API** 51 | Note: Only able to create Admin user at this time. The "user" is only used by the user interface to authenticate the single admin user. 52 | 53 | - [ ] Get User: /api/users/{username}, GET 54 | 55 | - [ ] Update User: /api/users/{username}, PUT 56 | 57 | - [ ] Delete User: /api/users/{username}, DELETE 58 | 59 | - [ ] Check for Admin User: /api/users/adm/hasadmin, GET 60 | 61 | - [ ] Create Admin User: /api/users/adm/createadmin, POST 62 | 63 | - [x] Authenticate: /api/users/adm/authenticate, POST 64 | 65 | 66 | ## Requirements 67 | 68 | - [Terraform](https://www.terraform.io/downloads.html) >= 0.13.x 69 | - [Go](https://golang.org/doc/install) >= 1.15 70 | 71 | ## Building The Provider 72 | 73 | 1. Clone the repository 74 | 1. Enter the repository directory 75 | 1. Build the provider using the Go `install` command: 76 | ```sh 77 | $ go install 78 | ``` 79 | 80 | ## Adding Dependencies 81 | 82 | This provider uses [Go modules](https://github.com/golang/go/wiki/Modules). 83 | Please see the Go documentation for the most up to date information about using Go modules. 84 | 85 | To add a new dependency `github.com/author/dependency` to your Terraform provider: 86 | 87 | ``` 88 | go get github.com/author/dependency 89 | go mod tidy 90 | ``` 91 | 92 | Then commit the changes to `go.mod` and `go.sum`. 93 | 94 | ## Using the provider 95 | 96 | Fill this in for each provider 97 | 98 | ## Developing the Provider 99 | 100 | If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (see [Requirements](#requirements) above). 101 | 102 | To compile the provider, run `go install`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. 103 | 104 | To generate or update documentation, run `go generate`. 105 | 106 | In order to run the full suite of Acceptance tests, run `make testacc`. 107 | 108 | *Note:* Acceptance tests create real resources, and often cost money to run. 109 | 110 | ```sh 111 | $ make testacc 112 | ``` 113 | 114 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.4" 2 | 3 | services: 4 | netmaker: 5 | container_name: netmaker 6 | image: gravitl/netmaker:0.9.0-dev 7 | restart: always 8 | environment: 9 | DNS_MODE: "off" 10 | API_PORT: "8081" 11 | GRPC_PORT: "50051" 12 | CLIENT_MODE: "off" 13 | DISPLAY_KEYS: "on" 14 | MASTER_KEY: "REPLACE_MASTER_KEY" 15 | SERVER_GRPC_WIREGUARD: "off" 16 | CORS_ALLOWED_ORIGIN: "*" 17 | DATABASE: "sqlite" 18 | ports: 19 | - "8081:8081" -------------------------------------------------------------------------------- /docs/data-sources/netmaker_access_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_access_key Data Source - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | AccessKey Data source in the Terraform provider Netmaker. 7 | --- 8 | 9 | # netmaker_access_key (Data Source) 10 | 11 | AccessKey Data source in the Terraform provider Netmaker. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **name** (String) Name of the access key 21 | - **netid** (String) Name of the access key 22 | - **uses** (Number) Uses of the access key 23 | 24 | ### Optional 25 | 26 | - **id** (String) The ID of this resource. 27 | 28 | ### Read-Only 29 | 30 | - **key** (String) Key of the access key 31 | 32 | 33 | -------------------------------------------------------------------------------- /docs/data-sources/netmaker_network.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_network Data Source - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | models.Network Data source in the Terraform provider Netmaker. 7 | --- 8 | 9 | # netmaker_network (Data Source) 10 | 11 | models.Network Data source in the Terraform provider Netmaker. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **addressrange** (String) 21 | - **netid** (String) 22 | 23 | ### Optional 24 | 25 | - **id** (String) The ID of this resource. 26 | 27 | ### Read-Only 28 | 29 | - **addressrange6** (String) 30 | - **allowmanualsignup** (String) 31 | - **checkininterval** (Number) 32 | - **defaultextclientdns** (String) 33 | - **defaultinterface** (String) 34 | - **defaultkeepalive** (Number) 35 | - **defaultlistenport** (Number) 36 | - **defaultmtu** (Number) 37 | - **defaultpostdown** (String) 38 | - **defaultpostup** (String) 39 | - **defaultsaveconfig** (String) 40 | - **defaultudpholepunch** (String) 41 | - **displayname** (String) 42 | - **isdualstack** (String) 43 | - **isgrpchub** (String) 44 | - **isipv4** (String) 45 | - **isipv6** (String) 46 | - **islocal** (String) 47 | - **localrange** (String) 48 | - **nodelimit** (Number) 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/data-sources/netmaker_networks.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_networks Data Source - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | models.Network Data source in the Terraform provider Netmaker. 7 | --- 8 | 9 | # netmaker_networks (Data Source) 10 | 11 | models.Network Data source in the Terraform provider Netmaker. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | terraform { 17 | required_providers { 18 | netmaker = { 19 | version = "0.2" 20 | source = "github.com/madacluster/netmaker" 21 | } 22 | } 23 | } 24 | 25 | provider "netmaker" { 26 | username = "admin" 27 | password = "mx4S6JsSg7JWcZ" 28 | host = "http://localhost:8081" 29 | } 30 | 31 | data "netmaker_networks" "example" { 32 | # sample_attribute = "foo" 33 | } 34 | 35 | output "networks" { 36 | value = data.netmaker_networks.example.networks 37 | } 38 | ``` 39 | 40 | 41 | ## Schema 42 | 43 | ### Optional 44 | 45 | - **id** (String) The ID of this resource. 46 | 47 | ### Read-Only 48 | 49 | - **networks** (List of Object) (see [below for nested schema](#nestedatt--networks)) 50 | 51 | 52 | ### Nested Schema for `networks` 53 | 54 | Read-Only: 55 | 56 | - **addressrange** (String) 57 | - **addressrange6** (String) 58 | - **allowmanualsignup** (String) 59 | - **checkininterval** (Number) 60 | - **defaultextclientdns** (String) 61 | - **defaultinterface** (String) 62 | - **defaultkeepalive** (Number) 63 | - **defaultlistenport** (Number) 64 | - **defaultmtu** (Number) 65 | - **defaultpostdown** (String) 66 | - **defaultpostup** (String) 67 | - **defaultsaveconfig** (String) 68 | - **defaultudpholepunch** (String) 69 | - **displayname** (String) 70 | - **isdualstack** (String) 71 | - **isgrpchub** (String) 72 | - **isipv4** (String) 73 | - **isipv6** (String) 74 | - **islocal** (String) 75 | - **localrange** (String) 76 | - **netid** (String) 77 | - **nodelimit** (Number) 78 | 79 | 80 | -------------------------------------------------------------------------------- /docs/data-sources/netmaker_node.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_node Data Source - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | Node Data source in the Terraform provider Netmaker. 7 | --- 8 | 9 | # netmaker_node (Data Source) 10 | 11 | Node Data source in the Terraform provider Netmaker. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **mac** (String) The MAC address of the node 21 | - **network_id** (String) The ID of the network the node belongs to 22 | 23 | ### Optional 24 | 25 | - **id** (String) The ID of this resource. 26 | - **is_egress_gateway** (String) Is the node an egress gateway 27 | - **is_ingress_gateway** (String) Is the node an ingress gateway 28 | - **name** (String) The name of the node 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker-terraform-provider Provider" 4 | subcategory: "" 5 | description: |- 6 | 7 | --- 8 | 9 | # netmaker-terraform-provider Provider 10 | 11 | 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | provider "netmaker" { 17 | # example configuration here 18 | username = "" 19 | password = "" 20 | host = "" 21 | } 22 | ``` 23 | 24 | 25 | ## Schema 26 | 27 | ### Required 28 | 29 | - **host** (String, Sensitive) 30 | - **password** (String, Sensitive) 31 | - **username** (String) 32 | -------------------------------------------------------------------------------- /docs/resources/netmaker_access_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_access_key Resource - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | Sample resource in the Terraform provider scaffolding. 7 | --- 8 | 9 | # netmaker_access_key (Resource) 10 | 11 | Sample resource in the Terraform provider scaffolding. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **name** (String) Name of the access key 21 | - **netid** (String) Name of the access key 22 | - **uses** (Number) Uses of the access key 23 | 24 | ### Optional 25 | 26 | - **id** (String) The ID of this resource. 27 | - **last_updated** (String) 28 | 29 | ### Read-Only 30 | 31 | - **key** (String) Key of the access key 32 | 33 | 34 | -------------------------------------------------------------------------------- /docs/resources/netmaker_egress.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_egress Resource - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | Sample resource in the Terraform provider scaffolding. 7 | --- 8 | 9 | # netmaker_egress (Resource) 10 | 11 | Sample resource in the Terraform provider scaffolding. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **mac** (String) The MAC address of the node 21 | - **netid** (String) The ID of the network the node belongs to 22 | 23 | ### Optional 24 | 25 | - **id** (String) The ID of this resource. 26 | - **interface** (String) The interface the node is connected to 27 | - **last_updated** (String) 28 | - **ranges** (Set of String) The ranges the node is allowed to access 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/resources/netmaker_ingress.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_ingress Resource - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | Sample resource in the Terraform provider scaffolding. 7 | --- 8 | 9 | # netmaker_ingress (Resource) 10 | 11 | Sample resource in the Terraform provider scaffolding. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **mac** (String) The MAC address of the node 21 | - **netid** (String) The ID of the network the node belongs to 22 | 23 | ### Optional 24 | 25 | - **id** (String) The ID of this resource. 26 | - **last_updated** (String) 27 | 28 | 29 | -------------------------------------------------------------------------------- /docs/resources/netmaker_network.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_network Resource - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | Sample resource in the Terraform provider scaffolding. 7 | --- 8 | 9 | # netmaker_network (Resource) 10 | 11 | Sample resource in the Terraform provider scaffolding. 12 | 13 | ## Example Usage 14 | 15 | ```terraform 16 | resource "netmaker_network" "example" { 17 | sample_attribute = "foo" 18 | } 19 | ``` 20 | 21 | 22 | ## Schema 23 | 24 | ### Required 25 | 26 | - **addressrange** (String) 27 | - **netid** (String) 28 | 29 | ### Optional 30 | 31 | - **id** (String) The ID of this resource. 32 | - **last_updated** (String) 33 | 34 | ### Read-Only 35 | 36 | - **addressrange6** (String) 37 | - **allowmanualsignup** (String) 38 | - **checkininterval** (Number) 39 | - **defaultextclientdns** (String) 40 | - **defaultinterface** (String) 41 | - **defaultkeepalive** (Number) 42 | - **defaultlistenport** (Number) 43 | - **defaultmtu** (Number) 44 | - **defaultpostdown** (String) 45 | - **defaultpostup** (String) 46 | - **defaultsaveconfig** (String) 47 | - **defaultudpholepunch** (String) 48 | - **displayname** (String) 49 | - **isdualstack** (String) 50 | - **isgrpchub** (String) 51 | - **isipv4** (String) 52 | - **isipv6** (String) 53 | - **islocal** (String) 54 | - **localrange** (String) 55 | - **nodelimit** (Number) 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/resources/netmaker_user.md: -------------------------------------------------------------------------------- 1 | --- 2 | # generated by https://github.com/hashicorp/terraform-plugin-docs 3 | page_title: "netmaker_user Resource - netmaker-terraform-provider" 4 | subcategory: "" 5 | description: |- 6 | Sample resource in the Terraform provider scaffolding. 7 | --- 8 | 9 | # netmaker_user (Resource) 10 | 11 | Sample resource in the Terraform provider scaffolding. 12 | 13 | 14 | 15 | 16 | ## Schema 17 | 18 | ### Required 19 | 20 | - **username** (String) 21 | 22 | ### Optional 23 | 24 | - **id** (String) The ID of this resource. 25 | - **last_updated** (String) 26 | - **networks** (List of String) 27 | - **password** (String, Sensitive) 28 | 29 | 30 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | This directory contains examples that are mostly used for documentation, but can also be run/tested manually via the Terraform CLI. 4 | 5 | The document generation tool looks for files in the following locations by default. All other *.tf files besides the ones mentioned below are ignored by the documentation tool. This is useful for creating examples that can run and/or ar testable even if some parts are not relevant for the documentation. 6 | 7 | * **provider/provider.tf** example file for the provider index page 8 | * **data-sources//data-source.tf** example file for the named data source page 9 | * **resources//resource.tf** example file for the named data source page 10 | -------------------------------------------------------------------------------- /examples/data-sources/netmaker_networks/data-source.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_providers { 3 | netmaker = { 4 | version = "0.2" 5 | source = "github.com/madacluster/netmaker" 6 | } 7 | } 8 | } 9 | 10 | provider "netmaker" { 11 | username = "admin" 12 | password = "mx4S6JsSg7JWcZ" 13 | host = "http://localhost:8081" 14 | } 15 | 16 | data "netmaker_networks" "example" { 17 | # sample_attribute = "foo" 18 | } 19 | 20 | output "networks" { 21 | value = data.netmaker_networks.example.networks 22 | } -------------------------------------------------------------------------------- /examples/provider/provider.tf: -------------------------------------------------------------------------------- 1 | provider "netmaker" { 2 | # example configuration here 3 | username = "" 4 | password = "" 5 | host = "" 6 | } -------------------------------------------------------------------------------- /examples/resources/netmaker_network/resource.tf: -------------------------------------------------------------------------------- 1 | resource "netmaker_network" "example" { 2 | sample_attribute = "foo" 3 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/madacluster/netmaker-terraform-provider 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/gravitl/netmaker v0.9.0 7 | github.com/hashicorp/terraform-plugin-docs v0.5.1 8 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0 9 | github.com/kyoh86/richgo v0.3.10 // indirect 10 | golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect 11 | golang.org/x/sys v0.0.0-20211117180635-dee7805ff2e1 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /helper/access_key.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gravitl/netmaker/models" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | func (c *Client) CreateAccessKeyFromSchema(d *schema.ResourceData, netID string) (*models.AccessKey, error) { 14 | key := CreateAccessKeyFromSchema(d) 15 | return c.CreateKey(netID, *key) 16 | } 17 | 18 | func (c *Client) CreateKey(networkID string, key models.AccessKey) (*models.AccessKey, error) { 19 | rb, err := json.Marshal(key) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/networks/%s/keys", c.HostURL, networkID), strings.NewReader(string(rb))) 25 | if err != nil { 26 | return nil, err 27 | } 28 | body, err := c.doRequest(req) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | err = json.Unmarshal(body, &key) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | return &key, nil 39 | } 40 | func CreateAccessKeyFromSchema(d *schema.ResourceData) *models.AccessKey { 41 | return &models.AccessKey{ 42 | Name: d.Get("name").(string), 43 | AccessString: d.Get("key").(string), 44 | Uses: d.Get("uses").(int), 45 | } 46 | } 47 | func (c *Client) GetKeys(networkID string) ([]models.AccessKey, error) { 48 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/networks/%s/keys", c.HostURL, networkID), nil) 49 | if err != nil { 50 | return nil, err 51 | } 52 | body, err := c.doRequest(req) 53 | if err != nil { 54 | return nil, err 55 | } 56 | 57 | var key []models.AccessKey 58 | err = json.Unmarshal(body, &key) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | return key, nil 64 | } 65 | 66 | func (c *Client) GetKey(networkID string, keyID string) (*models.AccessKey, error) { 67 | keys, err := c.GetKeys(networkID) 68 | if err != nil { 69 | return nil, err 70 | } 71 | for _, key := range keys { 72 | if key.Name == keyID { 73 | return &key, nil 74 | } 75 | } 76 | return nil, nil 77 | } 78 | 79 | func (c *Client) DeleteKey(networkID string, keyID string) error { 80 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/networks/%s/keys/%s", c.HostURL, networkID, keyID), nil) 81 | if err != nil { 82 | return err 83 | } 84 | _, err = c.doRequest(req) 85 | if err != nil { 86 | return err 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func CreateAccessKeySchema() map[string]*schema.Schema { 93 | return map[string]*schema.Schema{ 94 | "name": { 95 | Type: schema.TypeString, 96 | Required: true, 97 | Description: "Name of the access key", 98 | }, 99 | "netid": { 100 | Type: schema.TypeString, 101 | Required: true, 102 | Description: "Name of the access key", 103 | ForceNew: true, 104 | }, 105 | "key": { 106 | Type: schema.TypeString, 107 | Computed: true, 108 | Description: "Key of the access key", 109 | }, 110 | "uses": { 111 | Type: schema.TypeInt, 112 | // Computed: true, 113 | Required: true, 114 | Description: "Uses of the access key", 115 | }, 116 | } 117 | } 118 | 119 | func SetAccessKeySchemaData(d *schema.ResourceData, key *models.AccessKey, netID string) error { 120 | ID := fmt.Sprintf("%s-%s", netID, key.Name) 121 | d.SetId(ID) 122 | d.Set("name", key.Name) 123 | d.Set("key", key.AccessString) 124 | d.Set("uses", key.Uses) 125 | return nil 126 | } 127 | 128 | func (c *Client) UpdateKey(networkID string, key models.AccessKey) error { 129 | rb, err := json.Marshal(key) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | req, err := http.NewRequest("PUT", fmt.Sprintf("%s/api/networks/%s/keyupdate", c.HostURL, networkID), strings.NewReader(string(rb))) 135 | if err != nil { 136 | return err 137 | } 138 | _, err = c.doRequest(req) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | return nil 144 | } 145 | 146 | func (c *Client) UpdateKeyFromSchema(d *schema.ResourceData, netID string) error { 147 | key := CreateAccessKeyFromSchema(d) 148 | return c.UpdateKey(netID, *key) 149 | } 150 | -------------------------------------------------------------------------------- /helper/access_key_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/gravitl/netmaker/models" 9 | ) 10 | 11 | func TestClient_CreateKey(t *testing.T) { 12 | type fields struct { 13 | HostURL string 14 | HTTPClient *http.Client 15 | Token string 16 | Auth AuthStruct 17 | } 18 | type args struct { 19 | networkID string 20 | key models.AccessKey 21 | } 22 | tests := []struct { 23 | name string 24 | fields fields 25 | args args 26 | want *models.AccessKey 27 | wantErr bool 28 | }{ 29 | // TODO: Add test cases. 30 | { 31 | name: "Create admin user", 32 | fields: fields{ 33 | HostURL: host, 34 | HTTPClient: &http.Client{}, 35 | Auth: AuthStruct{ 36 | Username: user, 37 | Password: pass, 38 | }, 39 | }, 40 | args: args{ 41 | networkID: "netmakertest", 42 | key: models.AccessKey{ 43 | Name: "test", 44 | Uses: 10, 45 | }, 46 | }, 47 | want: &models.AccessKey{ 48 | Name: "test", 49 | Uses: 10, 50 | }, 51 | }, 52 | } 53 | for _, tt := range tests { 54 | t.Run(tt.name, func(t *testing.T) { 55 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 56 | got, err := c.CreateKey(tt.args.networkID, tt.args.key) 57 | if (err != nil) != tt.wantErr { 58 | t.Errorf("Client.CreateKey() error = %v, wantErr %v", err, tt.wantErr) 59 | return 60 | } 61 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 62 | t.Errorf("Client.CreateKey() = %v, want %v", got, tt.want) 63 | } 64 | }) 65 | } 66 | } 67 | 68 | func TestClient_GetKey(t *testing.T) { 69 | type fields struct { 70 | HostURL string 71 | HTTPClient *http.Client 72 | Token string 73 | Auth AuthStruct 74 | } 75 | type args struct { 76 | networkID string 77 | accesKeyID string 78 | } 79 | tests := []struct { 80 | name string 81 | fields fields 82 | args args 83 | want *models.AccessKey 84 | wantErr bool 85 | }{ 86 | // TODO: Add test cases. 87 | { 88 | name: "Get admin user", 89 | fields: fields{ 90 | HostURL: host, 91 | HTTPClient: &http.Client{}, 92 | Auth: AuthStruct{ 93 | Username: user, 94 | Password: pass, 95 | }, 96 | }, 97 | args: args{ 98 | networkID: "netmakertest", 99 | accesKeyID: "test", 100 | }, 101 | want: &models.AccessKey{ 102 | Name: "test", 103 | Uses: 10, 104 | }, 105 | }, 106 | } 107 | for _, tt := range tests { 108 | t.Run(tt.name, func(t *testing.T) { 109 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 110 | got, err := c.GetKey(tt.args.networkID, tt.args.accesKeyID) 111 | if (err != nil) != tt.wantErr { 112 | t.Errorf("Client.GetKey() error = %v, wantErr %v", err, tt.wantErr) 113 | return 114 | } 115 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 116 | t.Errorf("Client.GetKey() = %v, want %v", got, tt.want) 117 | } 118 | }) 119 | } 120 | } 121 | 122 | func TestClient_GetKeys(t *testing.T) { 123 | type fields struct { 124 | HostURL string 125 | HTTPClient *http.Client 126 | Token string 127 | Auth AuthStruct 128 | } 129 | type args struct { 130 | networkID string 131 | } 132 | tests := []struct { 133 | name string 134 | fields fields 135 | args args 136 | want []models.AccessKey 137 | wantErr bool 138 | }{ 139 | // TODO: Add test cases. 140 | } 141 | for _, tt := range tests { 142 | t.Run(tt.name, func(t *testing.T) { 143 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 144 | 145 | got, err := c.GetKeys(tt.args.networkID) 146 | if (err != nil) != tt.wantErr { 147 | t.Errorf("Client.GetKeys() error = %v, wantErr %v", err, tt.wantErr) 148 | return 149 | } 150 | if !reflect.DeepEqual(got[0].Name, tt.want[0].Name) { 151 | t.Errorf("Client.GetKeys() = %v, want %v", got, tt.want) 152 | } 153 | }) 154 | } 155 | } 156 | 157 | func TestClient_DeleteKey(t *testing.T) { 158 | type fields struct { 159 | HostURL string 160 | HTTPClient *http.Client 161 | Token string 162 | Auth AuthStruct 163 | } 164 | type args struct { 165 | networkID string 166 | keyID string 167 | } 168 | tests := []struct { 169 | name string 170 | fields fields 171 | args args 172 | wantErr bool 173 | }{ 174 | // TODO: Add test cases. 175 | 176 | { 177 | name: "Create admin user", 178 | fields: fields{ 179 | HostURL: host, 180 | HTTPClient: &http.Client{}, 181 | Auth: AuthStruct{ 182 | Username: user, 183 | Password: pass, 184 | }, 185 | }, 186 | args: args{ 187 | networkID: "netmakertest", 188 | keyID: "test", 189 | }, 190 | }, 191 | } 192 | for _, tt := range tests { 193 | t.Run(tt.name, func(t *testing.T) { 194 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 195 | 196 | if err := c.DeleteKey(tt.args.networkID, tt.args.keyID); (err != nil) != tt.wantErr { 197 | t.Errorf("Client.DeleteKey() error = %v, wantErr %v", err, tt.wantErr) 198 | } 199 | }) 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /helper/auth.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | ) 9 | 10 | // SignIn - 11 | func (c *Client) SignIn() (*AuthResponse, error) { 12 | if c.Auth.Username == "" || c.Auth.Password == "" { 13 | return nil, fmt.Errorf("define username and password") 14 | } 15 | rb, err := json.Marshal(c.Auth) 16 | if err != nil { 17 | return nil, err 18 | } 19 | 20 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/users/adm/authenticate", c.HostURL), strings.NewReader(string(rb))) 21 | if err != nil { 22 | return nil, err 23 | } 24 | 25 | body, err := c.doRequest(req) 26 | if err != nil { 27 | return nil, err 28 | } 29 | 30 | ar := AuthResponse{} 31 | err = json.Unmarshal(body, &ar) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | return &ar, nil 37 | } 38 | -------------------------------------------------------------------------------- /helper/auth_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestClient_SignIn(t *testing.T) { 11 | type fields struct { 12 | HostURL string 13 | HTTPClient *http.Client 14 | Token string 15 | Auth AuthStruct 16 | } 17 | 18 | tests := []struct { 19 | name string 20 | fields fields 21 | want *AuthResponse 22 | wantErr bool 23 | }{ 24 | // TODO: Add test cases. 25 | {"", 26 | fields{ 27 | "http://localhost:8081", 28 | &http.Client{Timeout: 10 * time.Second}, 29 | "", 30 | AuthStruct{ 31 | "admin", 32 | "mx4S6JsSg7JWcZ", 33 | }, 34 | }, 35 | &AuthResponse{ 36 | Code: 200, 37 | }, 38 | false, 39 | }, 40 | } 41 | for _, tt := range tests { 42 | t.Run(tt.name, func(t *testing.T) { 43 | c := &Client{ 44 | HostURL: tt.fields.HostURL, 45 | HTTPClient: tt.fields.HTTPClient, 46 | Token: tt.fields.Token, 47 | Auth: tt.fields.Auth, 48 | } 49 | got, err := c.SignIn() 50 | if (err != nil) != tt.wantErr { 51 | t.Errorf("Client.SignIn() error = %v, wantErr %v", err, tt.wantErr) 52 | return 53 | } 54 | if !reflect.DeepEqual(got.Code, tt.want.Code) { 55 | t.Errorf("Client.SignIn() = %v, want %v", got, tt.want) 56 | } 57 | }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /helper/client.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/gravitl/netmaker/models" 10 | ) 11 | 12 | // AuthStruct - 13 | type AuthStruct struct { 14 | Username string `json:"username"` 15 | Password string `json:"password"` 16 | } 17 | 18 | // AuthResponse - 19 | type AuthResponse struct { 20 | Code int `json:"Code"` 21 | Message string `json:"Message"` 22 | Response struct { 23 | UserName string `json:"UserName"` 24 | AuthToken string `json:"AuthToken"` 25 | } `json:"Response"` 26 | } 27 | 28 | // Client - 29 | type Client struct { 30 | HostURL string 31 | HTTPClient *http.Client 32 | Token string 33 | Auth AuthStruct 34 | } 35 | 36 | const HostURL string = "http://localhost:19090" 37 | 38 | func NewClient(host, username, password *string) (*Client, error) { 39 | 40 | c := Client{ 41 | HTTPClient: &http.Client{Timeout: 10 * time.Second}, 42 | // Default Hashicups URL 43 | HostURL: HostURL, 44 | Auth: AuthStruct{ 45 | Username: *username, 46 | Password: *password, 47 | }, 48 | } 49 | 50 | if host != nil { 51 | c.HostURL = *host 52 | } 53 | admin, err := c.CheckAdmin() 54 | if err != nil { 55 | return nil, err 56 | } 57 | if !admin { 58 | user := models.User{ 59 | UserName: *username, 60 | Password: *password, 61 | } 62 | c.CreateAdmin(user) 63 | } 64 | ar, err := c.SignIn() 65 | if err != nil { 66 | return nil, err 67 | } 68 | 69 | c.Token = ar.Response.AuthToken 70 | 71 | return &c, nil 72 | 73 | } 74 | 75 | func (c *Client) doRequest(req *http.Request) ([]byte, error) { 76 | req.Header.Set("Authorization", "Bearer "+c.Token) 77 | 78 | res, err := c.HTTPClient.Do(req) 79 | if err != nil { 80 | return nil, err 81 | } 82 | defer res.Body.Close() 83 | 84 | body, err := ioutil.ReadAll(res.Body) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | if res.StatusCode != http.StatusOK { 90 | return nil, fmt.Errorf("status: %d, body: %s", res.StatusCode, body) 91 | } 92 | 93 | return body, err 94 | } 95 | -------------------------------------------------------------------------------- /helper/network.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/gravitl/netmaker/models" 11 | 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 13 | ) 14 | 15 | // GetNetworks - Returns list of coffees (no auth required) 16 | func (c *Client) GetNetworks() ([]models.Network, error) { 17 | 18 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/networks", c.HostURL), nil) 19 | if err != nil { 20 | return nil, err 21 | } 22 | 23 | body, err := c.doRequest(req) 24 | if err != nil { 25 | return nil, err 26 | } 27 | networks := []models.Network{} 28 | err = json.Unmarshal(body, &networks) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return networks, nil 34 | } 35 | 36 | // GetNetworks - Returns a network by ID 37 | func (c *Client) GetNetwork(networkID string) (*models.Network, error) { 38 | 39 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/networks/%s", c.HostURL, networkID), nil) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | body, err := c.doRequest(req) 45 | if err != nil { 46 | return nil, err 47 | } 48 | network := models.Network{} 49 | err = json.Unmarshal(body, &network) 50 | if err != nil { 51 | return nil, err 52 | } 53 | 54 | return &network, nil 55 | } 56 | 57 | func (c *Client) CreateNetworkFromSchema(d *schema.ResourceData) (*models.Network, error) { 58 | network := CreateNetworkFromSchemaData(d) 59 | return c.CreateNetwork(*network) 60 | } 61 | 62 | // GetNetworks - Create a new network 63 | func (c *Client) CreateNetwork(network models.Network) (*models.Network, error) { 64 | 65 | rb, err := json.Marshal(network) 66 | if err != nil { 67 | return nil, err 68 | } 69 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/networks", c.HostURL), strings.NewReader(string(rb))) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | _, err = c.doRequest(req) 75 | if err != nil { 76 | return nil, err 77 | } 78 | 79 | return &network, nil 80 | } 81 | 82 | // UpdateNetwork - Updates a network 83 | func (c *Client) UpdateNetwork(network models.Network) (*models.Network, error) { 84 | 85 | rb, err := json.Marshal(network) 86 | if err != nil { 87 | return nil, err 88 | } 89 | req, err := http.NewRequest("PUT", fmt.Sprintf("%s/api/networks/%s", c.HostURL, network.NetID), strings.NewReader(string(rb))) 90 | if err != nil { 91 | return nil, err 92 | } 93 | 94 | body, err := c.doRequest(req) 95 | if err != nil { 96 | return nil, err 97 | } 98 | network = models.Network{} 99 | err = json.Unmarshal(body, &network) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return &network, nil 105 | } 106 | 107 | func (c *Client) UpdateNetworkFromSchema(d *schema.ResourceData) (*models.Network, error) { 108 | network := CreateNetworkFromSchemaData(d) 109 | return c.UpdateNetwork(*network) 110 | } 111 | 112 | func (c *Client) UpdateNetworkMap(data map[string]string) (*models.Network, error) { 113 | network := models.Network{} 114 | mapFiels(data, &network) 115 | return c.UpdateNetwork(network) 116 | } 117 | 118 | func (c *Client) DeleteNetwork(networkID string) error { 119 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/networks/%s", c.HostURL, networkID), nil) 120 | if err != nil { 121 | return err 122 | } 123 | 124 | _, err = c.doRequest(req) 125 | if err != nil { 126 | return err 127 | } 128 | 129 | return nil 130 | } 131 | 132 | func mapFiels(data map[string]string, network *models.Network) { 133 | for k, v := range data { 134 | switch k { 135 | case "displayname": 136 | network.DisplayName = v 137 | case "addressrange": 138 | network.AddressRange = v 139 | case "netid": 140 | network.NetID = v 141 | case "islocal": 142 | network.IsLocal = v 143 | case "isDualStack": 144 | network.IsDualStack = v 145 | case "isIPv4": 146 | network.IsIPv4 = v 147 | case "isIPv6": 148 | network.IsIPv6 = v 149 | case "isGRPCHub": 150 | network.IsGRPCHub = v 151 | case "localrange": 152 | network.LocalRange = v 153 | case "checkininterval": 154 | i, _ := strconv.Atoi(v) 155 | network.DefaultCheckInInterval = int32(i) 156 | case "defaultudpholepunch": 157 | network.DefaultUDPHolePunch = v 158 | case "defaultextclientdns": 159 | network.DefaultExtClientDNS = v 160 | case "defaultmtu": 161 | i, _ := strconv.Atoi(v) 162 | network.DefaultMTU = int32(i) 163 | case "defaultkeepalive": 164 | i, _ := strconv.Atoi(v) 165 | network.DefaultKeepalive = int32(i) 166 | case "allowmanualsignup": 167 | network.AllowManualSignUp = v 168 | case "nodeslastmodified": 169 | network.NodesLastModified, _ = strconv.ParseInt(v, 10, 64) 170 | case "defaultinterface": 171 | network.DefaultInterface = v 172 | case "defaultlistenport": 173 | i, _ := strconv.ParseInt(v, 10, 32) 174 | network.DefaultListenPort = int32(i) 175 | case "defaultsaveconfig": 176 | network.DefaultSaveConfig = v 177 | case "nodelimit": 178 | i, _ := strconv.ParseInt(v, 10, 32) 179 | network.NodeLimit = int32(i) 180 | case "defaultpostup": 181 | network.DefaultPostUp = v 182 | case "defaultpostdown": 183 | network.DefaultPostDown = v 184 | } 185 | } 186 | } 187 | 188 | func mapFielsRevert(network *models.Network) map[string]string { 189 | data := make(map[string]string) 190 | data["displayname"] = network.DisplayName 191 | data["addressrange"] = network.AddressRange 192 | data["netid"] = network.NetID 193 | data["defaultinterface"] = network.DefaultInterface 194 | data["defaultlistenport"] = strconv.FormatInt(int64(network.DefaultListenPort), 10) 195 | data["nodelimit"] = strconv.FormatInt(int64(network.NodeLimit), 10) 196 | data["defaultpostup"] = network.DefaultPostUp 197 | data["defaultpostdown"] = network.DefaultPostDown 198 | data["defaultsaveconfig"] = network.DefaultSaveConfig 199 | data["defaultmtu"] = strconv.FormatInt(int64(network.DefaultMTU), 10) 200 | data["defaultkeepalive"] = strconv.FormatInt(int64(network.DefaultKeepalive), 10) 201 | data["allowmanualsignup"] = network.AllowManualSignUp 202 | data["defaultudpholepunch"] = network.DefaultUDPHolePunch 203 | data["defaultextclientdns"] = network.DefaultExtClientDNS 204 | data["islocal"] = network.IsLocal 205 | data["isDualStack"] = network.IsDualStack 206 | data["isIPv4"] = network.IsIPv4 207 | data["isIPv6"] = network.IsIPv6 208 | data["isGRPCHub"] = network.IsGRPCHub 209 | data["localrange"] = network.LocalRange 210 | data["checkininterval"] = strconv.FormatInt(int64(network.DefaultCheckInInterval), 10) 211 | return data 212 | } 213 | 214 | func CreateNetworkSchema() map[string]*schema.Schema { 215 | return map[string]*schema.Schema{ 216 | "netid": { 217 | Type: schema.TypeString, 218 | Required: true, 219 | }, 220 | "addressrange": { 221 | Type: schema.TypeString, 222 | Required: true, 223 | }, 224 | "addressrange6": { 225 | Type: schema.TypeString, 226 | Computed: true, 227 | }, 228 | "displayname": { 229 | Type: schema.TypeString, 230 | Computed: true, 231 | }, 232 | "islocal": { 233 | Type: schema.TypeString, 234 | Computed: true, 235 | Default: nil, 236 | }, 237 | "isdualstack": { 238 | Type: schema.TypeString, 239 | Computed: true, 240 | Default: nil, 241 | }, 242 | "isipv4": { 243 | Type: schema.TypeString, 244 | Computed: true, 245 | }, 246 | "isipv6": { 247 | Type: schema.TypeString, 248 | Computed: true, 249 | }, 250 | "isgrpchub": { 251 | Type: schema.TypeString, 252 | Computed: true, 253 | }, 254 | "localrange": { 255 | Type: schema.TypeString, 256 | Computed: true, 257 | }, 258 | "checkininterval": { 259 | Type: schema.TypeInt, 260 | Computed: true, 261 | }, 262 | 263 | "defaultudpholepunch": { 264 | Type: schema.TypeString, 265 | Computed: true, 266 | Default: nil, 267 | }, 268 | "defaultextclientdns": { 269 | Type: schema.TypeString, 270 | Computed: true, 271 | }, 272 | "defaultmtu": { 273 | Type: schema.TypeInt, 274 | Computed: true, 275 | }, 276 | "defaultkeepalive": { 277 | Type: schema.TypeInt, 278 | Computed: true, 279 | }, 280 | "allowmanualsignup": { 281 | Type: schema.TypeString, 282 | Computed: true, 283 | }, 284 | "defaultinterface": { 285 | Type: schema.TypeString, 286 | Computed: true, 287 | }, 288 | "defaultlistenport": { 289 | Type: schema.TypeInt, 290 | Computed: true, 291 | }, 292 | "defaultsaveconfig": { 293 | Type: schema.TypeString, 294 | Computed: true, 295 | }, 296 | "nodelimit": { 297 | Type: schema.TypeInt, 298 | Computed: true, 299 | }, 300 | "defaultpostup": { 301 | Type: schema.TypeString, 302 | Computed: true, 303 | }, 304 | "defaultpostdown": { 305 | Type: schema.TypeString, 306 | Computed: true, 307 | }, 308 | } 309 | 310 | } 311 | 312 | func CreateNetworkFromSchemaData(d *schema.ResourceData) *models.Network { 313 | network := &models.Network{} 314 | network.NetID = d.Get("netid").(string) 315 | network.AddressRange = d.Get("addressrange").(string) 316 | network.AddressRange6 = d.Get("addressrange6").(string) 317 | network.DisplayName = d.Get("displayname").(string) 318 | network.IsLocal = d.Get("islocal").(string) 319 | network.IsDualStack = d.Get("isdualstack").(string) 320 | network.IsIPv4 = d.Get("isipv4").(string) 321 | network.IsIPv6 = d.Get("isipv6").(string) 322 | network.IsGRPCHub = d.Get("isgrpchub").(string) 323 | network.LocalRange = d.Get("localrange").(string) 324 | network.DefaultCheckInInterval = int32(d.Get("checkininterval").(int)) 325 | network.DefaultUDPHolePunch = d.Get("defaultudpholepunch").(string) 326 | network.DefaultExtClientDNS = d.Get("defaultextclientdns").(string) 327 | network.DefaultMTU = int32(d.Get("defaultmtu").(int)) 328 | network.AllowManualSignUp = d.Get("allowmanualsignup").(string) 329 | network.DefaultInterface = d.Get("defaultinterface").(string) 330 | network.DefaultListenPort = int32(d.Get("defaultlistenport").(int)) 331 | network.DefaultSaveConfig = d.Get("defaultsaveconfig").(string) 332 | network.NodeLimit = int32(d.Get("nodelimit").(int)) 333 | network.DefaultPostUp = d.Get("defaultpostup").(string) 334 | network.DefaultPostDown = d.Get("defaultpostdown").(string) 335 | return network 336 | } 337 | 338 | func SetNetworkSchemaData(d *schema.ResourceData, network *models.Network) error { 339 | if err := d.Set("netid", network.NetID); err != nil { 340 | return err 341 | } 342 | if err := d.Set("addressrange", network.AddressRange); err != nil { 343 | return err 344 | } 345 | if err := d.Set("addressrange6", network.AddressRange6); err != nil { 346 | return err 347 | } 348 | if err := d.Set("displayname", network.DisplayName); err != nil { 349 | return err 350 | } 351 | if err := d.Set("islocal", network.IsLocal); err != nil { 352 | return err 353 | } 354 | if err := d.Set("isdualstack", network.IsDualStack); err != nil { 355 | return err 356 | } 357 | if err := d.Set("isipv4", network.IsIPv4); err != nil { 358 | return err 359 | } 360 | if err := d.Set("isipv6", network.IsIPv6); err != nil { 361 | return err 362 | } 363 | if err := d.Set("isgrpchub", network.IsGRPCHub); err != nil { 364 | return err 365 | } 366 | if err := d.Set("localrange", network.LocalRange); err != nil { 367 | return err 368 | } 369 | if err := d.Set("checkininterval", network.DefaultCheckInInterval); err != nil { 370 | return err 371 | } 372 | 373 | if err := d.Set("defaultudpholepunch", network.DefaultUDPHolePunch); err != nil { 374 | return err 375 | } 376 | if err := d.Set("defaultextclientdns", network.DefaultExtClientDNS); err != nil { 377 | return err 378 | } 379 | if err := d.Set("defaultmtu", network.DefaultMTU); err != nil { 380 | return err 381 | } 382 | if err := d.Set("defaultkeepalive", network.DefaultKeepalive); err != nil { 383 | return err 384 | } 385 | if err := d.Set("allowmanualsignup", network.AllowManualSignUp); err != nil { 386 | return err 387 | } 388 | return nil 389 | } 390 | 391 | func FlattenNetworkData(network *models.Network) map[string]interface{} { 392 | oi := make(map[string]interface{}) 393 | oi["netid"] = network.NetID 394 | oi["addressrange"] = network.AddressRange 395 | oi["addressrange6"] = network.AddressRange6 396 | oi["displayname"] = network.DisplayName 397 | oi["islocal"] = network.IsLocal 398 | oi["isdualstack"] = network.IsDualStack 399 | oi["isipv4"] = network.IsIPv4 400 | oi["isipv6"] = network.IsIPv6 401 | oi["isgrpchub"] = network.IsGRPCHub 402 | oi["localrange"] = network.LocalRange 403 | oi["checkininterval"] = network.DefaultCheckInInterval 404 | oi["defaultinterface"] = network.DefaultInterface 405 | oi["defaultlistenport"] = network.DefaultListenPort 406 | oi["nodelimit"] = network.NodeLimit 407 | oi["defaultpostup"] = network.DefaultPostUp 408 | oi["defaultpostdown"] = network.DefaultPostDown 409 | oi["defaultsaveconfig"] = network.DefaultSaveConfig 410 | oi["defaultmtu"] = network.DefaultMTU 411 | oi["defaultkeepalive"] = network.DefaultKeepalive 412 | oi["allowmanualsignup"] = network.AllowManualSignUp 413 | oi["defaultudpholepunch"] = network.DefaultUDPHolePunch 414 | oi["defaultextclientdns"] = network.DefaultExtClientDNS 415 | oi["islocal"] = network.IsLocal 416 | oi["isdualstack"] = network.IsDualStack 417 | oi["isipv4"] = network.IsIPv4 418 | oi["isipv6"] = network.IsIPv6 419 | oi["isgrpchub"] = network.IsGRPCHub 420 | oi["localrange"] = network.LocalRange 421 | oi["checkininterval"] = network.DefaultCheckInInterval 422 | 423 | return oi 424 | } 425 | 426 | func FlattenNetworksData(networks *[]models.Network) []interface{} { 427 | if networks != nil { 428 | ois := make([]interface{}, len(*networks)) 429 | for i, network := range *networks { 430 | ois[i] = FlattenNetworkData(&network) 431 | } 432 | 433 | return ois 434 | } 435 | 436 | return make([]interface{}, 0) 437 | } 438 | -------------------------------------------------------------------------------- /helper/network_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "testing" 7 | "time" 8 | 9 | "github.com/gravitl/netmaker/models" 10 | ) 11 | 12 | var host = "http://localhost:8081" 13 | var pass = "mx4S6JsSg7JWcZ" 14 | var user = "admin" 15 | 16 | const ipRange = "10.101.0.0/24" 17 | 18 | func TestClient_CreateNetwork(t *testing.T) { 19 | type fields struct { 20 | HostURL string 21 | HTTPClient *http.Client 22 | Token string 23 | Auth AuthStruct 24 | } 25 | type args struct { 26 | networkID string 27 | addressrange string 28 | localrange string 29 | islocal string 30 | isdualstack string 31 | addressrange6 string 32 | defaultudpholepunch string 33 | } 34 | tests := []struct { 35 | name string 36 | fields fields 37 | args args 38 | want *models.Network 39 | wantErr bool 40 | }{ 41 | // TODO: Add test cases. 42 | {"", 43 | fields{ 44 | "http://localhost:8081", 45 | &http.Client{Timeout: 10 * time.Second}, 46 | "", 47 | AuthStruct{ 48 | user, 49 | pass, 50 | }, 51 | }, 52 | args{ 53 | networkID: "netmakertest", 54 | addressrange: "10.101.10.0/24", 55 | localrange: "", 56 | islocal: "no", 57 | isdualstack: "no", 58 | addressrange6: "", 59 | defaultudpholepunch: "yes", 60 | }, 61 | &models.Network{ 62 | AddressRange: "10.101.10.0/24", 63 | NetID: "netmakertest", 64 | }, 65 | 66 | false, 67 | }, 68 | } 69 | for _, tt := range tests { 70 | t.Run(tt.name, func(t *testing.T) { 71 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 72 | network := &models.Network{ 73 | AddressRange: tt.args.addressrange, 74 | LocalRange: tt.args.localrange, 75 | IsLocal: tt.args.islocal, 76 | IsDualStack: tt.args.isdualstack, 77 | AddressRange6: tt.args.addressrange6, 78 | DefaultUDPHolePunch: tt.args.defaultudpholepunch, 79 | NetID: tt.args.networkID, 80 | } 81 | got, err := c.CreateNetwork(*network) 82 | if (err != nil) != tt.wantErr { 83 | t.Errorf("Client.CreateNetwork() error = %v, wantErr %v", err, tt.wantErr) 84 | return 85 | } 86 | if !reflect.DeepEqual(got.NetID, tt.want.NetID) { 87 | t.Errorf("Client.CreateNetwork() = %v, want %v", got, tt.want) 88 | } 89 | }) 90 | } 91 | } 92 | 93 | func TestClient_GetNetworks(t *testing.T) { 94 | type fields struct { 95 | HostURL string 96 | HTTPClient *http.Client 97 | Token string 98 | Auth AuthStruct 99 | } 100 | tests := []struct { 101 | name string 102 | fields fields 103 | want []models.Network 104 | wantErr bool 105 | }{ 106 | // TODO: Add test cases. 107 | {"", 108 | fields{ 109 | "http://localhost:8081", 110 | &http.Client{Timeout: 10 * time.Second}, 111 | "", 112 | AuthStruct{ 113 | "admin", 114 | "mx4S6JsSg7JWcZ", 115 | }, 116 | }, 117 | []models.Network{ 118 | { 119 | AddressRange: ipRange, 120 | NetID: "netmakertest", 121 | }, 122 | }, 123 | false, 124 | }, 125 | } 126 | for _, tt := range tests { 127 | t.Run(tt.name, func(t *testing.T) { 128 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 129 | got, err := c.GetNetworks() 130 | if (err != nil) != tt.wantErr { 131 | t.Errorf("Client.GetNetworks() error = %v, wantErr %v", err, tt.wantErr) 132 | return 133 | } 134 | if !reflect.DeepEqual(got[0].NetID, tt.want[0].NetID) { 135 | t.Errorf("Client.GetNetworks() = %v, want %v", got, tt.want) 136 | } 137 | }) 138 | } 139 | } 140 | 141 | func TestClient_GetNetwork(t *testing.T) { 142 | type fields struct { 143 | HostURL string 144 | HTTPClient *http.Client 145 | Token string 146 | Auth AuthStruct 147 | } 148 | type args struct { 149 | networkID string 150 | } 151 | tests := []struct { 152 | name string 153 | fields fields 154 | args args 155 | want *models.Network 156 | wantErr bool 157 | }{ 158 | // TODO: Add test cases. 159 | {"", 160 | fields{ 161 | "http://localhost:8081", 162 | &http.Client{Timeout: 10 * time.Second}, 163 | "", 164 | AuthStruct{ 165 | "admin", 166 | "mx4S6JsSg7JWcZ", 167 | }, 168 | }, 169 | args{networkID: "netmakertest"}, 170 | &models.Network{ 171 | AddressRange: "10.101.10.0/24", 172 | NetID: "netmakertest", 173 | DefaultUDPHolePunch: "yes", 174 | IsDualStack: "no", 175 | IsLocal: "no", 176 | }, 177 | 178 | false, 179 | }, 180 | } 181 | for _, tt := range tests { 182 | t.Run(tt.name, func(t *testing.T) { 183 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 184 | 185 | got, err := c.GetNetwork(tt.args.networkID) 186 | if (err != nil) != tt.wantErr { 187 | t.Errorf("Client.GetNetwork() error = %v, wantErr %v", err, tt.wantErr) 188 | return 189 | } 190 | if !reflect.DeepEqual(got.AddressRange, tt.want.AddressRange) { 191 | t.Errorf("Client.GetNetwork() = %v, want %v", got, tt.want) 192 | } 193 | }) 194 | } 195 | } 196 | 197 | func TestClient_UpdateNetwork(t *testing.T) { 198 | type fields struct { 199 | HostURL string 200 | HTTPClient *http.Client 201 | Token string 202 | Auth AuthStruct 203 | } 204 | type args struct { 205 | data map[string]string 206 | } 207 | c, err := NewClient(&host, &user, &pass) 208 | if err != nil { 209 | t.Errorf("Client.UpdateNetwork() error = %v", err) 210 | } 211 | 212 | tests := []struct { 213 | name string 214 | fields fields 215 | args args 216 | want *models.Network 217 | wantErr bool 218 | }{ 219 | // TODO: Add test cases. 220 | {"", 221 | fields{ 222 | "http://localhost:8081", 223 | &http.Client{Timeout: 10 * time.Second}, 224 | "", 225 | AuthStruct{ 226 | "admin", 227 | "mx4S6JsSg7JWcZ", 228 | }, 229 | }, 230 | args{ 231 | data: map[string]string{ 232 | "netid": "netmakertest", 233 | "addressrange": "10.102.0.0/24", 234 | }, 235 | }, 236 | &models.Network{ 237 | AddressRange: "10.102.0.0/24", 238 | NetID: "netmakertest", 239 | DisplayName: "netmakertest", 240 | }, 241 | 242 | false, 243 | }, 244 | } 245 | for _, tt := range tests { 246 | t.Run(tt.name, func(t *testing.T) { 247 | network, err := c.GetNetwork(tt.args.data["netid"]) 248 | if err != nil { 249 | t.Errorf("Client.UpdateNetwork() error = %v", err) 250 | } 251 | 252 | networkFieldMap := mapFielsRevert(network) 253 | got, err := c.UpdateNetworkMap(networkFieldMap) 254 | if (err != nil) != tt.wantErr { 255 | t.Errorf("Client.UpdateNetwork() error = %v, wantErr %v", err, tt.wantErr) 256 | return 257 | } 258 | if !reflect.DeepEqual(got.DisplayName, tt.want.DisplayName) { 259 | t.Errorf("Client.UpdateNetwork() = %v, want %v", got, tt.want) 260 | } 261 | }) 262 | } 263 | } 264 | 265 | func TestClient_DeleteNetwork(t *testing.T) { 266 | type fields struct { 267 | HostURL string 268 | HTTPClient *http.Client 269 | Token string 270 | Auth AuthStruct 271 | } 272 | type args struct { 273 | networkID string 274 | } 275 | tests := []struct { 276 | name string 277 | fields fields 278 | args args 279 | wantErr bool 280 | }{ 281 | // TODO: Add test cases. 282 | {"", 283 | fields{ 284 | "http://localhost:8081", 285 | &http.Client{Timeout: 10 * time.Second}, 286 | "", 287 | AuthStruct{ 288 | "admin", 289 | "mx4S6JsSg7JWcZ", 290 | }, 291 | }, 292 | args{networkID: "netmakertest"}, 293 | false, 294 | }, 295 | } 296 | for _, tt := range tests { 297 | t.Run(tt.name, func(t *testing.T) { 298 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 299 | 300 | if err := c.DeleteNetwork(tt.args.networkID); (err != nil) != tt.wantErr { 301 | t.Errorf("Client.DeleteNetwork() error = %v, wantErr %v", err, tt.wantErr) 302 | } 303 | }) 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /helper/node.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gravitl/netmaker/models" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | func (c *Client) GetNodes() ([]models.Node, error) { 14 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/nodes", c.HostURL), nil) 15 | if err != nil { 16 | return nil, err 17 | } 18 | body, err := c.doRequest(req) 19 | if err != nil { 20 | return nil, err 21 | } 22 | nodes := []models.Node{} 23 | err = json.Unmarshal(body, &nodes) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return nodes, nil 28 | } 29 | 30 | func (c *Client) GetNetworkNodes(networkID string) ([]models.Node, error) { 31 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/nodes/%s", c.HostURL, networkID), nil) 32 | if err != nil { 33 | return nil, err 34 | } 35 | body, err := c.doRequest(req) 36 | if err != nil { 37 | return nil, err 38 | } 39 | nodes := []models.Node{} 40 | err = json.Unmarshal(body, &nodes) 41 | if err != nil { 42 | return nil, err 43 | } 44 | return nodes, nil 45 | } 46 | 47 | func (c *Client) CreateNetworkNode(networkID string, node models.Node) (*models.Node, error) { 48 | rb, err := json.Marshal(node) 49 | if err != nil { 50 | return nil, err 51 | } 52 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/nodes/%s", c.HostURL, networkID), strings.NewReader(string(rb))) 53 | if err != nil { 54 | return nil, err 55 | } 56 | body, err := c.doRequest(req) 57 | if err != nil { 58 | return nil, err 59 | } 60 | node = models.Node{} 61 | err = json.Unmarshal(body, &node) 62 | if err != nil { 63 | return nil, err 64 | } 65 | return &node, nil 66 | } 67 | 68 | func (c *Client) DeleteNetworkNode(networkID, mac string) error { 69 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/nodes/%s/%s", c.HostURL, networkID, mac), nil) 70 | if err != nil { 71 | return err 72 | } 73 | _, err = c.doRequest(req) 74 | if err != nil { 75 | return err 76 | } 77 | return nil 78 | } 79 | 80 | func (c *Client) GetNode(networkID, mac string) (*models.Node, error) { 81 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/nodes/%s/%s", c.HostURL, networkID, mac), nil) 82 | if err != nil { 83 | return nil, err 84 | } 85 | body, err := c.doRequest(req) 86 | if err != nil { 87 | return nil, err 88 | } 89 | node := &models.Node{} 90 | err = json.Unmarshal(body, node) 91 | if err != nil { 92 | return nil, err 93 | } 94 | return node, nil 95 | } 96 | 97 | func (c *Client) GetNetworkIngress(networkID string) ([]models.Node, error) { 98 | nodes, err := c.GetNetworkNodes(networkID) 99 | if err != nil { 100 | return nil, err 101 | } 102 | filter := []models.Node{} 103 | for _, node := range nodes { 104 | if node.IsIngressGateway == "yes" { 105 | filter = append(filter, node) 106 | } 107 | } 108 | return filter, nil 109 | } 110 | 111 | func (c *Client) CreateIngress(networkID, mac string) (*models.Node, error) { 112 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/nodes/%s/%s/createingress", c.HostURL, networkID, mac), nil) 113 | if err != nil { 114 | return nil, err 115 | } 116 | body, err := c.doRequest(req) 117 | if err != nil { 118 | return nil, err 119 | } 120 | node := &models.Node{} 121 | 122 | err = json.Unmarshal(body, node) 123 | if err != nil { 124 | return nil, err 125 | } 126 | return node, nil 127 | } 128 | 129 | func (c *Client) DeleteIngress(networkID, mac string) (*models.Node, error) { 130 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/nodes/%s/%s/deleteingress", c.HostURL, networkID, mac), nil) 131 | if err != nil { 132 | return nil, err 133 | } 134 | body, err := c.doRequest(req) 135 | if err != nil { 136 | return nil, err 137 | } 138 | node := &models.Node{} 139 | 140 | err = json.Unmarshal(body, node) 141 | if err != nil { 142 | return nil, err 143 | } 144 | return node, nil 145 | } 146 | 147 | func (c *Client) GetNetworkEgress(networkID string) ([]models.Node, error) { 148 | nodes, err := c.GetNetworkNodes(networkID) 149 | if err != nil { 150 | return nil, err 151 | } 152 | filter := []models.Node{} 153 | for _, node := range nodes { 154 | if node.IsEgressGateway == "yes" { 155 | filter = append(filter, node) 156 | } 157 | } 158 | return filter, nil 159 | } 160 | 161 | func (c *Client) CreateEgress(networkID, mac string, gateway *models.EgressGatewayRequest) (*models.Node, error) { 162 | rb, err := json.Marshal(gateway) 163 | if err != nil { 164 | return nil, err 165 | } 166 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/nodes/%s/%s/creategateway", c.HostURL, networkID, mac), strings.NewReader(string(rb))) 167 | if err != nil { 168 | return nil, err 169 | } 170 | body, err := c.doRequest(req) 171 | if err != nil { 172 | return nil, err 173 | } 174 | node := &models.Node{} 175 | 176 | err = json.Unmarshal(body, node) 177 | if err != nil { 178 | return nil, err 179 | } 180 | return node, nil 181 | } 182 | 183 | func (c *Client) DeleteEgress(networkID, mac string) (*models.Node, error) { 184 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/nodes/%s/%s/deletegateway", c.HostURL, networkID, mac), nil) 185 | if err != nil { 186 | return nil, err 187 | } 188 | body, err := c.doRequest(req) 189 | if err != nil { 190 | return nil, err 191 | } 192 | node := &models.Node{} 193 | 194 | err = json.Unmarshal(body, node) 195 | if err != nil { 196 | return nil, err 197 | } 198 | return node, nil 199 | } 200 | 201 | func CreateNodeDataSchema() map[string]*schema.Schema { 202 | return map[string]*schema.Schema{ 203 | "name": { 204 | Type: schema.TypeString, 205 | Optional: true, 206 | Description: "The name of the node", 207 | }, 208 | "mac": { 209 | Type: schema.TypeString, 210 | Required: true, 211 | Description: "The MAC address of the node", 212 | }, 213 | "network_id": { 214 | Type: schema.TypeString, 215 | Required: true, 216 | Description: "The ID of the network the node belongs to", 217 | }, 218 | "is_ingress_gateway": { 219 | Type: schema.TypeString, 220 | Optional: true, 221 | Description: "Is the node an ingress gateway", 222 | }, 223 | "is_egress_gateway": { 224 | Type: schema.TypeString, 225 | Optional: true, 226 | Description: "Is the node an egress gateway", 227 | }, 228 | } 229 | } 230 | 231 | func SetNodeSchemaData(d *schema.ResourceData, node *models.Node, networkID string) { 232 | d.SetId(node.ID) 233 | d.Set("name", node.Name) 234 | d.Set("mac", node.MacAddress) 235 | d.Set("network_id", node.Network) 236 | d.Set("is_ingress_gateway", node.IsIngressGateway) 237 | d.Set("is_egress_gateway", node.IsEgressGateway) 238 | } 239 | 240 | func CreateEgressSchema() map[string]*schema.Schema { 241 | return map[string]*schema.Schema{ 242 | "mac": { 243 | Type: schema.TypeString, 244 | Required: true, 245 | Description: "The MAC address of the node", 246 | }, 247 | "netid": { 248 | Type: schema.TypeString, 249 | Required: true, 250 | Description: "The ID of the network the node belongs to", 251 | }, 252 | "interface": { 253 | Type: schema.TypeString, 254 | Optional: true, 255 | ForceNew: true, 256 | Description: "The interface the node is connected to", 257 | }, 258 | "ranges": { 259 | Type: schema.TypeSet, 260 | Optional: true, 261 | Description: "The ranges the node is allowed to access", 262 | ForceNew: true, 263 | Elem: &schema.Schema{Type: schema.TypeString}, 264 | }, 265 | } 266 | } 267 | 268 | func CreateIngressSchema() map[string]*schema.Schema { 269 | return map[string]*schema.Schema{ 270 | "mac": { 271 | Type: schema.TypeString, 272 | Required: true, 273 | Description: "The MAC address of the node", 274 | }, 275 | "netid": { 276 | Type: schema.TypeString, 277 | Required: true, 278 | Description: "The ID of the network the node belongs to", 279 | }, 280 | } 281 | } 282 | 283 | func CreateEgressFromSchema(d *schema.ResourceData) *models.EgressGatewayRequest { 284 | 285 | tfRanges := d.Get("ranges").(*schema.Set).List() 286 | ranges := make([]string, len(tfRanges)) 287 | for i, tfTag := range tfRanges { 288 | ranges[i] = tfTag.(string) 289 | } 290 | return &models.EgressGatewayRequest{ 291 | Interface: d.Get("interface").(string), 292 | Ranges: ranges, 293 | } 294 | } 295 | 296 | func (c *Client) CreateEgressFromSchema(d *schema.ResourceData, netID, mac string) (*models.Node, error) { 297 | egress := CreateEgressFromSchema(d) 298 | return c.CreateEgress(netID, mac, egress) 299 | } 300 | 301 | func SetEgressSchemaData(d *schema.ResourceData, node *models.Node, networkID, mac string) { 302 | d.SetId(node.ID) 303 | d.Set("mac", mac) 304 | d.Set("netid", node.Network) 305 | d.Set("interface", node.Interface) 306 | } 307 | 308 | func SetIngressSchemaData(d *schema.ResourceData, node *models.Node, networkID, mac string) { 309 | d.SetId(node.ID) 310 | d.Set("mac", mac) 311 | d.Set("netid", node.Network) 312 | } 313 | -------------------------------------------------------------------------------- /helper/node_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/gravitl/netmaker/models" 9 | ) 10 | 11 | var token string 12 | 13 | const node_id = "testnode" 14 | const net_id = "test" 15 | const key_name = "test" 16 | const node_mac = "01:02:03:04:05:06" 17 | 18 | func CreateTestData(t *testing.T, createNode bool) { 19 | c, err := NewClient(&host, &user, &pass) 20 | if err != nil { 21 | t.Fatal(err) 22 | } 23 | 24 | network := &models.Network{ 25 | AddressRange: "10.102.0.0/24", 26 | LocalRange: "", 27 | IsLocal: "no", 28 | IsDualStack: "", 29 | AddressRange6: "", 30 | DefaultUDPHolePunch: "yes", 31 | NetID: net_id, 32 | } 33 | got, err := c.CreateNetwork(*network) 34 | if err != nil { 35 | t.Fatal(err) 36 | } 37 | key := &models.AccessKey{ 38 | Name: key_name, 39 | Uses: 10, 40 | } 41 | accessKey, err := c.CreateKey(got.NetID, *key) 42 | token = accessKey.Value 43 | // token = accessKey. 44 | if err != nil { 45 | t.Fatal(err) 46 | } 47 | if createNode { 48 | node := models.Node{ 49 | AccessKey: token, 50 | PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: node_id, Endpoint: "10.0.0.1", MacAddress: node_mac, Password: "password", Network: net_id, 51 | } 52 | _, err := c.CreateNetworkNode(network.NetID, node) 53 | if err != nil { 54 | t.Fatal(err) 55 | } 56 | } 57 | t.Cleanup(func() { 58 | err := CleanTestData() 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | }) 63 | } 64 | 65 | func CleanTestData() error { 66 | c, err := NewClient(&host, &user, &pass) 67 | if err != nil { 68 | return err 69 | } 70 | err = c.DeleteNetworkNode(net_id, node_mac) 71 | if err != nil { 72 | return err 73 | } 74 | err = c.DeleteKey(net_id, key_name) 75 | if err != nil { 76 | return err 77 | } 78 | return c.DeleteNetwork(net_id) 79 | } 80 | func TestClient_CreateNetworkNode(t *testing.T) { 81 | CreateTestData(t, false) 82 | // if err != nil { 83 | // t.Errorf("Client.CreateNetworkNode() error = %v", err) 84 | 85 | node := models.Node{ 86 | AccessKey: token, 87 | PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: node_id, Endpoint: "10.0.0.1", MacAddress: node_mac, Password: "password", Network: net_id, 88 | } 89 | type fields struct { 90 | HostURL string 91 | HTTPClient *http.Client 92 | Token string 93 | Auth AuthStruct 94 | } 95 | type args struct { 96 | networkID string 97 | node models.Node 98 | } 99 | tests := []struct { 100 | name string 101 | fields fields 102 | args args 103 | want *models.Node 104 | wantErr bool 105 | }{ 106 | { 107 | 108 | name: "Create Network Node", 109 | fields: fields{ 110 | HostURL: host, 111 | HTTPClient: &http.Client{}, 112 | Auth: AuthStruct{ 113 | Username: user, 114 | Password: pass, 115 | }, 116 | }, 117 | args: args{ 118 | networkID: net_id, 119 | node: node, 120 | }, 121 | want: &node, 122 | wantErr: false, 123 | }, 124 | } 125 | for _, tt := range tests { 126 | t.Run(tt.name, func(t *testing.T) { 127 | c, err := NewClient(&host, &user, &pass) 128 | if err != nil { 129 | t.Errorf("Client.CreateNetworkNode() error = %v", err) 130 | } 131 | got, err := c.CreateNetworkNode(tt.args.networkID, tt.args.node) 132 | if (err != nil) != tt.wantErr { 133 | t.Errorf("Client.CreateNetworkNode() error = %v, wantErr %v", err, tt.wantErr) 134 | return 135 | } 136 | if !reflect.DeepEqual(got.Network, net_id) { 137 | t.Errorf("Client.CreateNetworkNode() = %v, want %v", got.Network, net_id) 138 | } 139 | }) 140 | } 141 | } 142 | 143 | func TestClient_GetNodes(t *testing.T) { 144 | CreateTestData(t, true) 145 | type fields struct { 146 | HostURL string 147 | HTTPClient *http.Client 148 | Token string 149 | Auth AuthStruct 150 | } 151 | tests := []struct { 152 | name string 153 | fields fields 154 | want []models.Node 155 | wantErr bool 156 | }{ 157 | { 158 | name: "Get Nodes", 159 | fields: fields{ 160 | HostURL: host, 161 | HTTPClient: &http.Client{}, 162 | Auth: AuthStruct{ 163 | Username: user, 164 | Password: pass, 165 | }, 166 | }, 167 | want: []models.Node{}, 168 | wantErr: false, 169 | }, 170 | } 171 | for _, tt := range tests { 172 | t.Run(tt.name, func(t *testing.T) { 173 | c, err := NewClient(&host, &user, &pass) 174 | if err != nil { 175 | t.Errorf("Client.NetClient() error = %v", err) 176 | } 177 | got, err := c.GetNodes() 178 | if (err != nil) != tt.wantErr { 179 | t.Errorf("Client.GetNodes() error = %v, wantErr %v", err, tt.wantErr) 180 | return 181 | } 182 | if !reflect.DeepEqual(got[0].Name, "testnode") { 183 | t.Errorf("Client.GetNodes() = %v, want %v", got[0].Name, "testnode") 184 | } 185 | }) 186 | } 187 | } 188 | 189 | func TestClient_GetNetworkNodes(t *testing.T) { 190 | CreateTestData(t, true) 191 | 192 | type fields struct { 193 | HostURL string 194 | HTTPClient *http.Client 195 | Token string 196 | Auth AuthStruct 197 | } 198 | type args struct { 199 | networkID string 200 | } 201 | tests := []struct { 202 | name string 203 | fields fields 204 | args args 205 | want []models.Node 206 | wantErr bool 207 | }{ 208 | { 209 | name: "Get Nodes Network", 210 | fields: fields{ 211 | HostURL: host, 212 | HTTPClient: &http.Client{}, 213 | Auth: AuthStruct{ 214 | Username: user, 215 | Password: pass, 216 | }, 217 | }, 218 | args: args{ 219 | networkID: net_id, 220 | }, 221 | want: []models.Node{}, 222 | wantErr: false, 223 | }, 224 | } 225 | for _, tt := range tests { 226 | t.Run(tt.name, func(t *testing.T) { 227 | c, err := NewClient(&host, &user, &pass) 228 | if err != nil { 229 | t.Errorf("Client.NetClient() error = %v", err) 230 | } 231 | got, err := c.GetNetworkNodes(tt.args.networkID) 232 | if (err != nil) != tt.wantErr { 233 | t.Errorf("Client.GetNodes() error = %v, wantErr %v", err, tt.wantErr) 234 | return 235 | } 236 | if !reflect.DeepEqual(got[0].Name, "testnode") { 237 | t.Errorf("Client.GetNodes() = %v, want %v", got[0].Name, "testnode") 238 | } 239 | }) 240 | } 241 | } 242 | 243 | func TestClient_GetNode(t *testing.T) { 244 | CreateTestData(t, true) 245 | type fields struct { 246 | HostURL string 247 | HTTPClient *http.Client 248 | Token string 249 | Auth AuthStruct 250 | } 251 | type args struct { 252 | networkID string 253 | mac string 254 | } 255 | tests := []struct { 256 | name string 257 | fields fields 258 | args args 259 | want models.Node 260 | wantErr bool 261 | }{ 262 | { 263 | name: "Get Node", 264 | fields: fields{}, 265 | args: args{ 266 | networkID: net_id, 267 | mac: node_mac, 268 | }, 269 | want: models.Node{ 270 | Name: node_id, 271 | }, 272 | wantErr: false, 273 | }, 274 | } 275 | for _, tt := range tests { 276 | t.Run(tt.name, func(t *testing.T) { 277 | c, err := NewClient(&host, &user, &pass) 278 | if err != nil { 279 | t.Errorf("Client.NetClient() error = %v", err) 280 | } 281 | got, err := c.GetNode(tt.args.networkID, tt.args.mac) 282 | if (err != nil) != tt.wantErr { 283 | t.Errorf("Client.GetNode() error = %v, wantErr %v", err, tt.wantErr) 284 | return 285 | } 286 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 287 | t.Errorf("Client.GetNode() = %v, want %v", got, tt.want) 288 | } 289 | }) 290 | } 291 | } 292 | 293 | func TestClient_GetNetworkIngress(t *testing.T) { 294 | CreateTestData(t, true) 295 | type fields struct { 296 | HostURL string 297 | HTTPClient *http.Client 298 | Token string 299 | Auth AuthStruct 300 | } 301 | type args struct { 302 | networkID string 303 | } 304 | tests := []struct { 305 | name string 306 | fields fields 307 | args args 308 | want []models.Node 309 | wantErr bool 310 | }{ 311 | { 312 | name: "Get Network Ingress", 313 | fields: fields{}, 314 | args: args{ 315 | networkID: net_id, 316 | }, 317 | want: []models.Node{}, 318 | wantErr: false, 319 | }, 320 | } 321 | for _, tt := range tests { 322 | t.Run(tt.name, func(t *testing.T) { 323 | c, err := NewClient(&host, &user, &pass) 324 | if err != nil { 325 | t.Errorf("Client.NetClient() error = %v", err) 326 | return 327 | } 328 | got, err := c.GetNetworkIngress(tt.args.networkID) 329 | if (err != nil) != tt.wantErr { 330 | t.Errorf("Client.GetNetworkIngress() error = %v, wantErr %v", err, tt.wantErr) 331 | return 332 | } 333 | if !reflect.DeepEqual(got, tt.want) { 334 | t.Errorf("Client.GetNetworkIngress() = %v, want %v", got, tt.want) 335 | } 336 | }) 337 | } 338 | } 339 | 340 | func TestClient_CreateIngress(t *testing.T) { 341 | CreateTestData(t, true) 342 | type fields struct { 343 | HostURL string 344 | HTTPClient *http.Client 345 | Token string 346 | Auth AuthStruct 347 | } 348 | type args struct { 349 | networkID string 350 | mac string 351 | } 352 | tests := []struct { 353 | name string 354 | fields fields 355 | args args 356 | want *models.Node 357 | wantErr bool 358 | }{ 359 | { 360 | name: "Create Ingress", 361 | fields: fields{}, 362 | args: args{ 363 | networkID: net_id, 364 | mac: node_mac, 365 | }, 366 | want: &models.Node{ 367 | Name: node_id, 368 | }, 369 | wantErr: false, 370 | }, 371 | } 372 | for _, tt := range tests { 373 | t.Run(tt.name, func(t *testing.T) { 374 | c, err := NewClient(&host, &user, &pass) 375 | if err != nil { 376 | t.Errorf("Client.NetClient() error = %v", err) 377 | } 378 | got, err := c.CreateIngress(tt.args.networkID, tt.args.mac) 379 | if (err != nil) != tt.wantErr { 380 | t.Errorf("Client.CreateIngress() error = %v, wantErr %v", err, tt.wantErr) 381 | return 382 | } 383 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 384 | t.Errorf("Client.CreateIngress() = %v, want %v", got, tt.want.Name) 385 | } 386 | }) 387 | } 388 | } 389 | 390 | func TestClient_DeleteIngress(t *testing.T) { 391 | CreateTestData(t, true) 392 | type fields struct { 393 | HostURL string 394 | HTTPClient *http.Client 395 | Token string 396 | Auth AuthStruct 397 | } 398 | type args struct { 399 | networkID string 400 | mac string 401 | } 402 | tests := []struct { 403 | name string 404 | fields fields 405 | args args 406 | want *models.Node 407 | wantErr bool 408 | }{ 409 | { 410 | name: "Delete Ingress", 411 | fields: fields{}, 412 | args: args{ 413 | networkID: net_id, 414 | mac: node_mac, 415 | }, 416 | want: &models.Node{ 417 | Name: node_id, 418 | }, 419 | wantErr: false, 420 | }, 421 | } 422 | for _, tt := range tests { 423 | t.Run(tt.name, func(t *testing.T) { 424 | c, err := NewClient(&host, &user, &pass) 425 | if err != nil { 426 | t.Errorf("Client.NetClient() error = %v", err) 427 | } 428 | _, err = c.CreateIngress(tt.args.networkID, tt.args.mac) 429 | if err != nil { 430 | t.Errorf("Create Ingress() error = %v", err) 431 | } 432 | got, err := c.DeleteIngress(tt.args.networkID, tt.args.mac) 433 | if (err != nil) != tt.wantErr { 434 | t.Errorf("Client.DeleteIngress() error = %v, wantErr %v", err, tt.wantErr) 435 | return 436 | } 437 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 438 | t.Errorf("Client.DeleteIngress() = %v, want %v", got.Name, tt.want.Name) 439 | } 440 | }) 441 | } 442 | } 443 | 444 | func TestClient_CreateEgress(t *testing.T) { 445 | CreateTestData(t, true) 446 | type fields struct { 447 | HostURL string 448 | HTTPClient *http.Client 449 | Token string 450 | Auth AuthStruct 451 | } 452 | type args struct { 453 | networkID string 454 | mac string 455 | egress *models.EgressGatewayRequest 456 | } 457 | tests := []struct { 458 | name string 459 | fields fields 460 | args args 461 | want *models.Node 462 | wantErr bool 463 | }{ 464 | { 465 | name: "Get Network Egress", 466 | fields: fields{}, 467 | args: args{ 468 | networkID: net_id, 469 | mac: node_mac, 470 | egress: &models.EgressGatewayRequest{ 471 | Interface: "eth0", 472 | Ranges: []string{"0.0.0.0./0"}, 473 | }, 474 | }, 475 | want: &models.Node{ 476 | Name: node_id, 477 | }, 478 | wantErr: false, 479 | }, 480 | } 481 | for _, tt := range tests { 482 | t.Run(tt.name, func(t *testing.T) { 483 | c, err := NewClient(&host, &user, &pass) 484 | if err != nil { 485 | t.Errorf("Client.NetClient() error = %v", err) 486 | } 487 | got, err := c.CreateEgress(tt.args.networkID, tt.args.mac, tt.args.egress) 488 | if (err != nil) != tt.wantErr { 489 | t.Errorf("Client.CreateEgress() error = %v, wantErr %v", err, tt.wantErr) 490 | return 491 | } 492 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 493 | t.Errorf("Client.CreateEgress() = %v, want %v", got, tt.want) 494 | } 495 | }) 496 | } 497 | } 498 | 499 | func TestClient_GetNetworkEgress(t *testing.T) { 500 | CreateTestData(t, true) 501 | type fields struct { 502 | HostURL string 503 | HTTPClient *http.Client 504 | Token string 505 | Auth AuthStruct 506 | } 507 | type args struct { 508 | networkID string 509 | } 510 | tests := []struct { 511 | name string 512 | fields fields 513 | args args 514 | want string 515 | wantErr bool 516 | }{ 517 | { 518 | name: "Get Network Egress", 519 | fields: fields{}, 520 | args: args{ 521 | networkID: net_id, 522 | }, 523 | want: "yes", 524 | wantErr: false, 525 | }, 526 | } 527 | egress := &models.EgressGatewayRequest{ 528 | Interface: "eth0", 529 | Ranges: []string{"0.0.0.0./0"}, 530 | } 531 | for _, tt := range tests { 532 | t.Run(tt.name, func(t *testing.T) { 533 | c, err := NewClient(&host, &user, &pass) 534 | if err != nil { 535 | t.Errorf("Client.NetClient() error = %v", err) 536 | } 537 | _, err = c.CreateEgress(tt.args.networkID, node_mac, egress) 538 | if err != nil { 539 | t.Errorf("Create Egress error = %v", err) 540 | } 541 | got, err := c.GetNetworkEgress(tt.args.networkID) 542 | if (err != nil) != tt.wantErr { 543 | t.Errorf("Client.GetNetworkEgress() error = %v, wantErr %v", err, tt.wantErr) 544 | return 545 | } 546 | if !reflect.DeepEqual(got[0].IsEgressGateway, tt.want) { 547 | t.Errorf("Client.GetNetworkEgress() = %v, want %v", got, tt.want) 548 | } 549 | }) 550 | } 551 | } 552 | 553 | func TestClient_DeleteEgress(t *testing.T) { 554 | CreateTestData(t, true) 555 | type fields struct { 556 | HostURL string 557 | HTTPClient *http.Client 558 | Token string 559 | Auth AuthStruct 560 | } 561 | type args struct { 562 | networkID string 563 | mac string 564 | } 565 | tests := []struct { 566 | name string 567 | fields fields 568 | args args 569 | want *models.Node 570 | wantErr bool 571 | }{ 572 | { 573 | name: "Delete Network Egress", 574 | fields: fields{}, 575 | args: args{ 576 | networkID: net_id, 577 | mac: node_mac, 578 | }, 579 | want: &models.Node{ 580 | Name: node_id, 581 | }, 582 | wantErr: false, 583 | }, 584 | } 585 | for _, tt := range tests { 586 | t.Run(tt.name, func(t *testing.T) { 587 | c, err := NewClient(&host, &user, &pass) 588 | if err != nil { 589 | t.Errorf("Client.NetClient() error = %v", err) 590 | } 591 | egress := &models.EgressGatewayRequest{ 592 | Interface: "eth0", 593 | Ranges: []string{"0.0.0.0./0"}, 594 | } 595 | _, err = c.CreateEgress(tt.args.networkID, tt.args.mac, egress) 596 | if err != nil { 597 | t.Errorf("Client.NetClient() error = %v", err) 598 | } 599 | got, err := c.DeleteEgress(tt.args.networkID, tt.args.mac) 600 | if (err != nil) != tt.wantErr { 601 | t.Errorf("Client.DeleteEgress() error = %v, wantErr %v", err, tt.wantErr) 602 | return 603 | } 604 | if !reflect.DeepEqual(got.Name, tt.want.Name) { 605 | t.Errorf("Client.DeleteEgress() = %v, want %v", got, tt.want) 606 | } 607 | }) 608 | } 609 | } 610 | 611 | func TestClient_DeleteNetworkNode(t *testing.T) { 612 | CreateTestData(t, true) 613 | type fields struct { 614 | HostURL string 615 | HTTPClient *http.Client 616 | Token string 617 | Auth AuthStruct 618 | } 619 | type args struct { 620 | networkID string 621 | mac string 622 | } 623 | tests := []struct { 624 | name string 625 | fields fields 626 | args args 627 | wantErr bool 628 | }{ 629 | { 630 | name: "Delete Network Node", 631 | fields: fields{}, 632 | args: args{ 633 | networkID: net_id, 634 | mac: node_mac, 635 | }, 636 | wantErr: false, 637 | }, 638 | } 639 | for _, tt := range tests { 640 | t.Run(tt.name, func(t *testing.T) { 641 | c, err := NewClient(&host, &user, &pass) 642 | if err != nil { 643 | t.Errorf("Client.NetClient() error = %v", err) 644 | } 645 | if err := c.DeleteNetworkNode(tt.args.networkID, tt.args.mac); (err != nil) != tt.wantErr { 646 | t.Errorf("Client.DeleteNetworkNode() error = %v, wantErr %v", err, tt.wantErr) 647 | } 648 | }) 649 | } 650 | } 651 | -------------------------------------------------------------------------------- /helper/user.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gravitl/netmaker/models" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | func (c *Client) CreateAdmin(user models.User) error { 14 | 15 | rb, err := json.Marshal(user) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/users/adm/createadmin", c.HostURL), strings.NewReader(string(rb))) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | _, err = c.doRequest(req) 26 | if err != nil { 27 | return err 28 | } 29 | return nil 30 | 31 | } 32 | 33 | func (c *Client) CheckAdmin() (bool, error) { 34 | admin := false 35 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/users/adm/hasadmin", c.HostURL), nil) 36 | if err != nil { 37 | return admin, err 38 | } 39 | 40 | body, err := c.doRequest(req) 41 | if err != nil { 42 | return admin, err 43 | } 44 | 45 | err = json.Unmarshal(body, &admin) 46 | if err != nil { 47 | return admin, err 48 | } 49 | 50 | return admin, nil 51 | } 52 | 53 | func (c *Client) CreateUser(user models.User) error { 54 | 55 | rb, err := json.Marshal(user) 56 | if err != nil { 57 | return err 58 | } 59 | 60 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/api/users/%s", c.HostURL, user.UserName), strings.NewReader(string(rb))) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | _, err = c.doRequest(req) 66 | if err != nil { 67 | return err 68 | } 69 | return nil 70 | } 71 | 72 | func (c *Client) DeleteUser(username string) error { 73 | 74 | req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/api/users/%s", c.HostURL, username), nil) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | _, err = c.doRequest(req) 80 | if err != nil { 81 | return err 82 | } 83 | return nil 84 | 85 | } 86 | 87 | func (c *Client) UpdateUser(user models.User) error { 88 | rb, err := json.Marshal(user) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | req, err := http.NewRequest("PUT", fmt.Sprintf("%s/api/users/%s", c.HostURL, user.UserName), strings.NewReader(string(rb))) 94 | if err != nil { 95 | return err 96 | } 97 | 98 | _, err = c.doRequest(req) 99 | if err != nil { 100 | return err 101 | } 102 | return nil 103 | } 104 | 105 | func CreateUserSchema() map[string]*schema.Schema { 106 | return map[string]*schema.Schema{ 107 | "username": { 108 | Type: schema.TypeString, 109 | Required: true, 110 | }, 111 | "password": { 112 | Type: schema.TypeString, 113 | Sensitive: true, 114 | Optional: true, 115 | }, 116 | "networks": { 117 | Type: schema.TypeList, 118 | Computed: true, 119 | Optional: true, 120 | Elem: &schema.Schema{ 121 | Type: schema.TypeString, 122 | }, 123 | }, 124 | } 125 | } 126 | 127 | func (c *Client) GetUser(username string) (*models.User, error) { 128 | 129 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/api/users/%s", c.HostURL, username), nil) 130 | if err != nil { 131 | return nil, err 132 | } 133 | 134 | body, err := c.doRequest(req) 135 | if err != nil { 136 | return nil, err 137 | } 138 | 139 | user := models.User{} 140 | err = json.Unmarshal(body, &user) 141 | if err != nil { 142 | return nil, err 143 | } 144 | 145 | return &user, nil 146 | } 147 | 148 | func CreateUserFromSchemaData(d *schema.ResourceData) *models.User { 149 | user := &models.User{} 150 | user.UserName = d.Get("username").(string) 151 | user.Password = d.Get("password").(string) 152 | networks := d.Get("network") 153 | if networks != nil { 154 | user.Networks = d.Get("network").([]string) 155 | } else { 156 | user.Networks = []string{} 157 | } 158 | 159 | return user 160 | } 161 | 162 | func (c *Client) CreateUserFromSchema(d *schema.ResourceData) (*models.User, error) { 163 | user := CreateUserFromSchemaData(d) 164 | return user, c.CreateUser(*user) 165 | } 166 | 167 | func (c *Client) CreateAdminUserFromSchema(d *schema.ResourceData) (*models.User, error) { 168 | user := CreateUserFromSchemaData(d) 169 | return user, c.CreateAdmin(*user) 170 | } 171 | 172 | func SetUserSchemaData(d *schema.ResourceData, user *models.User) error { 173 | d.Set("username", user.UserName) 174 | d.Set("network", user.Networks) 175 | return nil 176 | } 177 | 178 | func (c *Client) UpdateUserFromSchema(d *schema.ResourceData) (*models.User, error) { 179 | user := CreateUserFromSchemaData(d) 180 | return user, c.UpdateUser(*user) 181 | } 182 | -------------------------------------------------------------------------------- /helper/user_test.go: -------------------------------------------------------------------------------- 1 | package helper 2 | 3 | import ( 4 | "net/http" 5 | "testing" 6 | 7 | "github.com/gravitl/netmaker/models" 8 | ) 9 | 10 | func TestClient_CreateAdmin(t *testing.T) { 11 | type fields struct { 12 | HostURL string 13 | HTTPClient *http.Client 14 | Token string 15 | Auth AuthStruct 16 | } 17 | type args struct { 18 | user models.User 19 | } 20 | tests := []struct { 21 | name string 22 | fields fields 23 | args args 24 | wantErr bool 25 | }{ 26 | // TODO: Add test cases. 27 | { 28 | name: "Create admin user", 29 | fields: fields{ 30 | HostURL: host, 31 | HTTPClient: &http.Client{}, 32 | Token: "", 33 | Auth: AuthStruct{ 34 | Username: user, 35 | Password: pass, 36 | }, 37 | }, 38 | args: args{ 39 | user: models.User{ 40 | UserName: "admin_test", 41 | Password: pass, 42 | }, 43 | }, 44 | wantErr: false, 45 | }, 46 | } 47 | for _, tt := range tests { 48 | t.Run(tt.name, func(t *testing.T) { 49 | c := &Client{ 50 | HostURL: "http://localhost:8081", 51 | HTTPClient: tt.fields.HTTPClient, 52 | Token: tt.fields.Token, 53 | Auth: tt.fields.Auth, 54 | } 55 | if err := c.CreateAdmin(tt.args.user); (err != nil) != tt.wantErr { 56 | t.Errorf("Client.CreateAdmin() error = %v, wantErr %v", err, tt.wantErr) 57 | } 58 | }) 59 | } 60 | } 61 | 62 | func TestClient_CheckAdmin(t *testing.T) { 63 | type fields struct { 64 | HostURL string 65 | HTTPClient *http.Client 66 | Token string 67 | Auth AuthStruct 68 | } 69 | tests := []struct { 70 | name string 71 | fields fields 72 | want bool 73 | wantErr bool 74 | }{ 75 | // TODO: Add test cases. 76 | { 77 | name: "Check admin user", 78 | fields: fields{ 79 | HostURL: host, 80 | HTTPClient: &http.Client{}, 81 | Token: "", 82 | Auth: AuthStruct{ 83 | Username: user, 84 | Password: pass, 85 | }, 86 | }, 87 | want: true, 88 | wantErr: false, 89 | }, 90 | } 91 | for _, tt := range tests { 92 | t.Run(tt.name, func(t *testing.T) { 93 | c := &Client{ 94 | HostURL: tt.fields.HostURL, 95 | HTTPClient: tt.fields.HTTPClient, 96 | Token: tt.fields.Token, 97 | Auth: tt.fields.Auth, 98 | } 99 | got, err := c.CheckAdmin() 100 | if (err != nil) != tt.wantErr { 101 | t.Errorf("Client.CheckAdmin() error = %v, wantErr %v", err, tt.wantErr) 102 | return 103 | } 104 | if got != tt.want { 105 | t.Errorf("Client.CheckAdmin() = %v, want %v", got, tt.want) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | func TestClient_CreateUser(t *testing.T) { 112 | type fields struct { 113 | HostURL string 114 | HTTPClient *http.Client 115 | Token string 116 | Auth AuthStruct 117 | } 118 | type args struct { 119 | user models.User 120 | } 121 | tests := []struct { 122 | name string 123 | fields fields 124 | args args 125 | wantErr bool 126 | }{ 127 | // TODO: Add test cases. 128 | { 129 | name: "Create user", 130 | fields: fields{ 131 | HostURL: host, 132 | HTTPClient: &http.Client{}, 133 | Token: "", 134 | Auth: AuthStruct{ 135 | Username: user, 136 | Password: pass, 137 | }, 138 | }, 139 | args: args{ 140 | user: models.User{ 141 | UserName: "user_test", 142 | Password: pass, 143 | }, 144 | }, 145 | wantErr: false, 146 | }, 147 | } 148 | for _, tt := range tests { 149 | t.Run(tt.name, func(t *testing.T) { 150 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 151 | 152 | if err := c.CreateUser(tt.args.user); (err != nil) != tt.wantErr { 153 | t.Errorf("Client.CreateUser() error = %v, wantErr %v", err, tt.wantErr) 154 | } 155 | }) 156 | } 157 | } 158 | 159 | func TestClient_DeleteUser(t *testing.T) { 160 | type fields struct { 161 | HostURL string 162 | HTTPClient *http.Client 163 | Token string 164 | Auth AuthStruct 165 | } 166 | type args struct { 167 | user models.User 168 | } 169 | tests := []struct { 170 | name string 171 | fields fields 172 | args args 173 | wantErr bool 174 | }{ 175 | // TODO: Add test cases. 176 | { 177 | name: "Delete user", 178 | fields: fields{ 179 | HostURL: host, 180 | HTTPClient: &http.Client{}, 181 | Token: "", 182 | Auth: AuthStruct{ 183 | Username: user, 184 | Password: pass, 185 | }, 186 | }, 187 | args: args{ 188 | user: models.User{ 189 | UserName: "user_test", 190 | Password: pass, 191 | }, 192 | }, 193 | wantErr: false, 194 | }, 195 | } 196 | for _, tt := range tests { 197 | t.Run(tt.name, func(t *testing.T) { 198 | c, _ := NewClient(&tt.fields.HostURL, &tt.fields.Auth.Username, &tt.fields.Auth.Password) 199 | 200 | if err := c.DeleteUser(tt.args.user.UserName); (err != nil) != tt.wantErr { 201 | t.Errorf("Client.DeleteUser() error = %v, wantErr %v", err, tt.wantErr) 202 | } 203 | }) 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /internal/provider/data_source_access_key.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/madacluster/netmaker-terraform-provider/helper" 11 | ) 12 | 13 | func AddIdAccessKeySchema() map[string]*schema.Schema { 14 | result := helper.CreateAccessKeySchema() 15 | result["id"] = &schema.Schema{ 16 | Type: schema.TypeString, 17 | Required: true, 18 | } 19 | return result 20 | } 21 | 22 | func dataSourceAccessKey() *schema.Resource { 23 | return &schema.Resource{ 24 | // This description is used by the documentation generator and the language server. 25 | Description: "AccessKey Data source in the Terraform provider Netmaker.", 26 | 27 | ReadContext: dataSourceAccessKeyRead, 28 | 29 | Schema: helper.CreateAccessKeySchema(), 30 | } 31 | } 32 | 33 | func dataSourceAccessKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 34 | // use the meta value to retrieve your client from the provider configure method 35 | client := meta.(*helper.Client) 36 | accessKeyID := d.Get("name").(string) 37 | netID := d.Get("netid").(string) 38 | 39 | // Warning or errors can be collected in a slice type 40 | var diags diag.Diagnostics 41 | 42 | key, err := client.GetKey(accessKeyID, netID) 43 | if err != nil { 44 | return diag.FromErr(err) 45 | } 46 | err = helper.SetAccessKeySchemaData(d, key, netID) 47 | 48 | if err != nil { 49 | return diag.FromErr(err) 50 | } 51 | // always run 52 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 53 | 54 | return diags 55 | } 56 | -------------------------------------------------------------------------------- /internal/provider/data_source_access_key_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccDataSourceAccessKey(t *testing.T) { 11 | // t.Skip("data source not yet implemented, remove this once you add your own code") 12 | 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccDataSourceAccessKey, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "data.netmaker_access_key.foo", "netid", regexp.MustCompile("^netmakertest")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccDataSourceAccessKey = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | data "netmaker_access_key" "foo" { 35 | name = "netmakertest" 36 | netid = "netmakertest" 37 | } 38 | ` 39 | -------------------------------------------------------------------------------- /internal/provider/data_source_network.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/madacluster/netmaker-terraform-provider/helper" 11 | ) 12 | 13 | func dataSourceNetwork() *schema.Resource { 14 | return &schema.Resource{ 15 | // This description is used by the documentation generator and the language server. 16 | Description: "models.Network Data source in the Terraform provider Netmaker.", 17 | 18 | ReadContext: dataSourceNetworkRead, 19 | 20 | Schema: helper.CreateNetworkSchema(), 21 | } 22 | } 23 | 24 | func dataSourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 25 | // use the meta value to retrieve your client from the provider configure method 26 | client := meta.(*helper.Client) 27 | networkID := d.Get("netid").(string) 28 | // idFromAPI := "my-id" 29 | // d.SetId(idFromAPI) 30 | 31 | // Warning or errors can be collected in a slice type 32 | var diags diag.Diagnostics 33 | 34 | network, err := client.GetNetwork(networkID) 35 | if err != nil { 36 | return diag.FromErr(err) 37 | } 38 | err = helper.SetNetworkSchemaData(d, network) 39 | 40 | if err != nil { 41 | return diag.FromErr(err) 42 | } 43 | // always run 44 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 45 | 46 | return diags 47 | } 48 | -------------------------------------------------------------------------------- /internal/provider/data_source_network_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccDataSourceNetwork(t *testing.T) { 11 | // t.Skip("data source not yet implemented, remove this once you add your own code") 12 | 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccDataSourceNetwork, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "data.netmaker_network.foo", "netid", regexp.MustCompile("^netmakertest")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccDataSourceNetwork = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | data "netmaker_network" "foo" { 35 | netid = "netmakertest" 36 | addressrange = "10.100.10.0/24" 37 | } 38 | ` 39 | -------------------------------------------------------------------------------- /internal/provider/data_source_networks.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/madacluster/netmaker-terraform-provider/helper" 11 | ) 12 | 13 | func dataSourceNetworks() *schema.Resource { 14 | return &schema.Resource{ 15 | // This description is used by the documentation generator and the language server. 16 | Description: "models.Network Data source in the Terraform provider Netmaker.", 17 | 18 | ReadContext: dataSourceNetworksRead, 19 | 20 | Schema: map[string]*schema.Schema{ 21 | "networks": { 22 | Type: schema.TypeList, 23 | Computed: true, 24 | Elem: &schema.Resource{ 25 | Schema: helper.CreateNetworkSchema(), 26 | }, 27 | }, 28 | }, 29 | } 30 | } 31 | 32 | func dataSourceNetworksRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 33 | // use the meta value to retrieve your client from the provider configure method 34 | client := meta.(*helper.Client) 35 | 36 | // idFromAPI := "my-id" 37 | // d.SetId(idFromAPI) 38 | 39 | // Warning or errors can be collected in a slice type 40 | var diags diag.Diagnostics 41 | 42 | networks, err := client.GetNetworks() 43 | networksFlatten := helper.FlattenNetworksData(&networks) 44 | 45 | if err != nil { 46 | return diag.FromErr(err) 47 | } 48 | 49 | if err := d.Set("networks", networksFlatten); err != nil { 50 | return diag.FromErr(err) 51 | } 52 | 53 | // always run 54 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 55 | 56 | return diags 57 | } 58 | -------------------------------------------------------------------------------- /internal/provider/data_source_networks_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccDataSourceNetworks(t *testing.T) { 11 | // t.Skip("data source not yet implemented, remove this once you add your own code") 12 | 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccDataSourceNetworks, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "data.netmaker_networks.foo", "networks.0.netid", regexp.MustCompile("^netmakertest")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccDataSourceNetworks = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | data "netmaker_networks" "foo" { 35 | } 36 | ` 37 | -------------------------------------------------------------------------------- /internal/provider/data_source_node.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/madacluster/netmaker-terraform-provider/helper" 11 | ) 12 | 13 | func dataSourceNode() *schema.Resource { 14 | return &schema.Resource{ 15 | // This description is used by the documentation generator and the language server. 16 | Description: "Node Data source in the Terraform provider Netmaker.", 17 | 18 | ReadContext: dataSourceNodeRead, 19 | 20 | Schema: helper.CreateNodeDataSchema(), 21 | } 22 | } 23 | 24 | func dataSourceNodeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 25 | // use the meta value to retrieve your client from the provider configure method 26 | client := meta.(*helper.Client) 27 | mac := d.Get("mac").(string) 28 | netID := d.Get("network_id").(string) 29 | 30 | // Warning or errors can be collected in a slice type 31 | var diags diag.Diagnostics 32 | 33 | node, err := client.GetNode(netID, mac) 34 | if err != nil { 35 | return diag.FromErr(err) 36 | } 37 | helper.SetNodeSchemaData(d, node, netID) 38 | 39 | // always run 40 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 41 | 42 | return diags 43 | } 44 | -------------------------------------------------------------------------------- /internal/provider/data_source_node_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/gravitl/netmaker/models" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 9 | "github.com/madacluster/netmaker-terraform-provider/helper" 10 | ) 11 | 12 | var token string 13 | 14 | const node_id = "testnode" 15 | const net_id = "test" 16 | const key_name = "test" 17 | const node_mac = "01:02:03:04:05:06" 18 | 19 | var host = "http://localhost:8081" 20 | var pass = "mx4S6JsSg7JWcZ" 21 | var user = "admin" 22 | 23 | // const ipRange = "10.101.0.0/24" 24 | 25 | func CreateTestData(t *testing.T, createNode bool) { 26 | c, err := helper.NewClient(&host, &user, &pass) 27 | if err != nil { 28 | t.Fatal(err) 29 | } 30 | 31 | network := &models.Network{ 32 | AddressRange: "10.102.0.0/24", 33 | LocalRange: "", 34 | IsLocal: "no", 35 | IsDualStack: "", 36 | AddressRange6: "", 37 | DefaultUDPHolePunch: "yes", 38 | NetID: net_id, 39 | } 40 | got, err := c.CreateNetwork(*network) 41 | if err != nil { 42 | t.Fatal(err) 43 | } 44 | key := &models.AccessKey{ 45 | Name: key_name, 46 | Uses: 10, 47 | } 48 | accessKey, err := c.CreateKey(got.NetID, *key) 49 | // token = accessKey. 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | token = accessKey.Value 54 | if createNode { 55 | node := models.Node{ 56 | AccessKey: token, 57 | PublicKey: "DM5qhLAE20PG9BbfBCger+Ac9D2NDOwCtY1rbYDLf34=", Name: node_id, Endpoint: "10.0.0.1", MacAddress: node_mac, Password: "password", Network: net_id, 58 | } 59 | _, err := c.CreateNetworkNode(network.NetID, node) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | } 64 | t.Cleanup(func() { 65 | err := CleanTestData() 66 | if err != nil { 67 | t.Fatal(err) 68 | } 69 | }) 70 | } 71 | 72 | func CleanTestData() error { 73 | c, err := helper.NewClient(&host, &user, &pass) 74 | if err != nil { 75 | return err 76 | } 77 | err = c.DeleteNetworkNode(net_id, node_mac) 78 | if err != nil { 79 | return err 80 | } 81 | err = c.DeleteKey(net_id, key_name) 82 | if err != nil { 83 | return err 84 | } 85 | return c.DeleteNetwork(net_id) 86 | } 87 | func TestAccDataSourceNode(t *testing.T) { 88 | // t.Skip("data source not yet implemented, remove this once you add your own code") 89 | CreateTestData(t, true) 90 | resource.UnitTest(t, resource.TestCase{ 91 | PreCheck: func() { testAccPreCheck(t) }, 92 | ProviderFactories: providerFactories, 93 | Steps: []resource.TestStep{ 94 | { 95 | Config: testAccDataSourceNode, 96 | Check: resource.ComposeTestCheckFunc( 97 | resource.TestMatchResourceAttr( 98 | "data.netmaker_node.foo", "name", regexp.MustCompile("^testnode")), 99 | ), 100 | }, 101 | }, 102 | }) 103 | } 104 | 105 | const testAccDataSourceNode = ` 106 | provider "netmaker" { 107 | username = "admin" 108 | password = "mx4S6JsSg7JWcZ" 109 | host = "http://localhost:8081" 110 | } 111 | 112 | data "netmaker_node" "foo" { 113 | network_id = "test" 114 | mac = "01:02:03:04:05:06" 115 | } 116 | ` 117 | -------------------------------------------------------------------------------- /internal/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | client "github.com/madacluster/netmaker-terraform-provider/helper" 9 | ) 10 | 11 | func init() { 12 | // Set descriptions to support markdown syntax, this will be used in document generation 13 | // and the language server. 14 | schema.DescriptionKind = schema.StringMarkdown 15 | 16 | // Customize the content of descriptions when output. For example you can add defaults on 17 | // to the exported descriptions if present. 18 | // schema.SchemaDescriptionBuilder = func(s *schema.Schema) string { 19 | // desc := s.Description 20 | // if s.Default != nil { 21 | // desc += fmt.Sprintf(" Defaults to `%v`.", s.Default) 22 | // } 23 | // return strings.TrimSpace(desc) 24 | // } 25 | } 26 | 27 | func New(version string) func() *schema.Provider { 28 | return func() *schema.Provider { 29 | p := &schema.Provider{ 30 | DataSourcesMap: map[string]*schema.Resource{ 31 | "netmaker_networks": dataSourceNetworks(), 32 | "netmaker_network": dataSourceNetwork(), 33 | "netmaker_access_key": dataSourceAccessKey(), 34 | "netmaker_node": dataSourceNode(), 35 | }, 36 | ResourcesMap: map[string]*schema.Resource{ 37 | "netmaker_network": resourceNetwork(), 38 | "netmaker_user": resourceUser(), 39 | "netmaker_access_key": resourceAccessKey(), 40 | "netmaker_egress": resourceEgress(), 41 | "netmaker_ingress": resourceIngress(), 42 | }, 43 | Schema: map[string]*schema.Schema{ 44 | "username": { 45 | Type: schema.TypeString, 46 | Required: true, 47 | DefaultFunc: schema.EnvDefaultFunc("NETMAKER_USERNAME", nil), 48 | }, 49 | "password": { 50 | Type: schema.TypeString, 51 | Required: true, 52 | Sensitive: true, 53 | DefaultFunc: schema.EnvDefaultFunc("NETMAKER_PASSWORD", nil), 54 | }, 55 | "host": { 56 | Type: schema.TypeString, 57 | Required: true, 58 | Sensitive: true, 59 | DefaultFunc: schema.EnvDefaultFunc("NETMAKER_HOST", nil), 60 | }, 61 | }, 62 | } 63 | 64 | p.ConfigureContextFunc = configure(version, p) 65 | 66 | return p 67 | } 68 | } 69 | 70 | func configure(version string, p *schema.Provider) func(context.Context, *schema.ResourceData) (interface{}, diag.Diagnostics) { 71 | return func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 72 | // Setup a models.User-Agent for your API client (replace the provider name for yours): 73 | // userAgent := p.UserAgent("terraform-provider-scaffolding", version) 74 | // TODO: myClient.UserAgent = userAgent 75 | username := d.Get("username").(string) 76 | password := d.Get("password").(string) 77 | host := d.Get("host").(string) 78 | 79 | // Warning or errors can be collected in a slice type 80 | var diags diag.Diagnostics 81 | if (username != "") && (password != "") && (host != "") { 82 | c, err := client.NewClient(&host, &username, &password) 83 | if err != nil { 84 | diags = append(diags, diag.Diagnostic{ 85 | Severity: diag.Error, 86 | Summary: "Unable to create HashiCups client", 87 | Detail: "Unable to auth user for authenticated HashiCups client", 88 | }) 89 | } 90 | 91 | return c, diags 92 | } 93 | 94 | c, err := client.NewClient(nil, nil, nil) 95 | if err != nil { 96 | diags = append(diags, diag.Diagnostic{ 97 | Severity: diag.Error, 98 | Summary: "Unable to create HashiCups client", 99 | Detail: "Unable to auth user for authenticated HashiCups client", 100 | }) 101 | } 102 | 103 | return c, diags 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /internal/provider/provider_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | // providerFactories are used to instantiate a provider during acceptance testing. 10 | // The factory function will be invoked for every Terraform CLI command executed 11 | // to create a provider server to which the CLI can reattach. 12 | var providerFactories = map[string]func() (*schema.Provider, error){ 13 | "netmaker": func() (*schema.Provider, error) { 14 | return New("dev")(), nil 15 | }, 16 | } 17 | 18 | func TestProvider(t *testing.T) { 19 | if err := New("dev")().InternalValidate(); err != nil { 20 | t.Fatalf("err: %s", err) 21 | } 22 | } 23 | 24 | func testAccPreCheck(t *testing.T) { 25 | // You can add code here to run prior to any test case execution, for example assertions 26 | // about the appropriate environment variables being set are common to see in a pre-check 27 | // function. 28 | } 29 | -------------------------------------------------------------------------------- /internal/provider/resource_access_key.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | "github.com/madacluster/netmaker-terraform-provider/helper" 11 | ) 12 | 13 | func resourceAccessKey() *schema.Resource { 14 | return &schema.Resource{ 15 | // This description is used by the documentation generator and the language server. 16 | Description: "Sample resource in the Terraform provider scaffolding.", 17 | 18 | CreateContext: resourceAccessKeyCreate, 19 | ReadContext: resourceAccessKeyRead, 20 | UpdateContext: resourceAccessKeyUpdate, 21 | DeleteContext: resourceAccessKeyDelete, 22 | 23 | Schema: addLastupdated(helper.CreateAccessKeySchema()), 24 | } 25 | } 26 | 27 | func resourceAccessKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 28 | // use the meta value to retrieve your client from the provider configure method 29 | // client := meta.(*apiClient) 30 | var diags diag.Diagnostics 31 | 32 | client := meta.(*helper.Client) 33 | netID := d.Get("netid").(string) 34 | key, err := client.CreateAccessKeyFromSchema(d, netID) 35 | 36 | if err != nil { 37 | return diag.Errorf("failed to create models.AccessKey: %s", err) 38 | } 39 | 40 | ID := fmt.Sprintf("%s-%s", netID, key.Name) 41 | d.SetId(ID) 42 | return diags 43 | } 44 | 45 | func resourceAccessKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 46 | // use the meta value to retrieve your client from the provider configure method 47 | client := meta.(*helper.Client) 48 | accessKeyID := d.Get("name").(string) 49 | netid := d.Get("netid").(string) 50 | // d.SetId(UserID) 51 | 52 | // Warning or errors can be collected in a slice type 53 | var diags diag.Diagnostics 54 | 55 | accessKey, err := client.GetKey(netid, accessKeyID) 56 | if err != nil { 57 | return diag.FromErr(err) 58 | } 59 | 60 | if helper.SetAccessKeySchemaData(d, accessKey, netid) != nil { 61 | return diag.FromErr(err) 62 | } 63 | return diags 64 | } 65 | 66 | func resourceAccessKeyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 67 | // use the meta value to retrieve your client from the provider configure method 68 | client := meta.(*helper.Client) 69 | if d.HasChanges("name") { 70 | networkID := d.Get("netid").(string) 71 | err := client.UpdateKeyFromSchema(d, networkID) 72 | if err != nil { 73 | return diag.FromErr(err) 74 | } 75 | d.Set("last_updated", time.Now().Format(time.RFC850)) 76 | } 77 | 78 | return resourceAccessKeyRead(ctx, d, meta) 79 | } 80 | 81 | func resourceAccessKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 82 | // use the meta value to retrieve your client from the provider configure method 83 | // client := meta.(*apiClient) 84 | client := meta.(*helper.Client) 85 | var diags diag.Diagnostics 86 | 87 | networkID := d.Get("netid").(string) 88 | name := d.Get("name").(string) 89 | err := client.DeleteKey(networkID, name) 90 | if err != nil { 91 | return diag.FromErr(err) 92 | } 93 | 94 | // d.SetId("") is automatically called assuming delete returns no errors, but 95 | // it is added here for explicitness. 96 | d.SetId("") 97 | 98 | return diags 99 | } 100 | -------------------------------------------------------------------------------- /internal/provider/resource_access_key_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccResourceAccessKey(t *testing.T) { 11 | // t.Skip("resource not yet implemented, remove this once you add your own code") 12 | 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccResourceAccessKey, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "netmaker_access_key.foo", "netid", regexp.MustCompile("^test")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccResourceAccessKey = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | resource "netmaker_network" "foo" { 35 | netid = "test" 36 | addressrange = "10.100.10.0/24" 37 | } 38 | resource "netmaker_access_key" "foo" { 39 | depends_on = ["netmaker_network.foo"] 40 | netid = "test" 41 | uses = 10 42 | name = "test" 43 | } 44 | ` 45 | -------------------------------------------------------------------------------- /internal/provider/resource_egress.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/madacluster/netmaker-terraform-provider/helper" 10 | ) 11 | 12 | func resourceEgress() *schema.Resource { 13 | return &schema.Resource{ 14 | // This description is used by the documentation generator and the language server. 15 | Description: "Sample resource in the Terraform provider scaffolding.", 16 | 17 | CreateContext: resourceEgressCreate, 18 | ReadContext: resourceEgressRead, 19 | UpdateContext: resourceEgressUpdate, 20 | DeleteContext: resourceEgressDelete, 21 | 22 | Schema: addLastupdated(helper.CreateEgressSchema()), 23 | } 24 | } 25 | 26 | func resourceEgressCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 27 | // use the meta value to retrieve your client from the provider configure method 28 | // client := meta.(*apiClient) 29 | var diags diag.Diagnostics 30 | 31 | client := meta.(*helper.Client) 32 | netID := d.Get("netid").(string) 33 | mac := d.Get("mac").(string) 34 | node, err := client.CreateEgressFromSchema(d, netID, mac) 35 | 36 | if err != nil { 37 | return diag.Errorf("failed to create models.Egress: %s", err) 38 | } 39 | 40 | ID := fmt.Sprintf("%s-%s", netID, node.MacAddress) 41 | d.SetId(ID) 42 | return diags 43 | } 44 | 45 | func resourceEgressRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 46 | // use the meta value to retrieve your client from the provider configure method 47 | client := meta.(*helper.Client) 48 | mac := d.Get("mac").(string) 49 | netid := d.Get("netid").(string) 50 | // d.SetId(UserID) 51 | 52 | // Warning or errors can be collected in a slice type 53 | var diags diag.Diagnostics 54 | 55 | node, err := client.GetNode(netid, mac) 56 | if err != nil { 57 | return diag.FromErr(err) 58 | } 59 | helper.SetEgressSchemaData(d, node, netid, mac) 60 | 61 | return diags 62 | } 63 | 64 | func resourceEgressUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 65 | // use the meta value to retrieve your client from the provider configure method 66 | resourceEgressDelete(ctx, d, meta) 67 | 68 | return resourceEgressCreate(ctx, d, meta) 69 | } 70 | 71 | func resourceEgressDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 72 | // use the meta value to retrieve your client from the provider configure method 73 | // client := meta.(*apiClient) 74 | client := meta.(*helper.Client) 75 | var diags diag.Diagnostics 76 | 77 | networkID := d.Get("netid").(string) 78 | mac := d.Get("mac").(string) 79 | _, err := client.DeleteEgress(networkID, mac) 80 | if err != nil { 81 | return diag.FromErr(err) 82 | } 83 | 84 | // d.SetId("") is automatically called assuming delete returns no errors, but 85 | // it is added here for explicitness. 86 | d.SetId("") 87 | 88 | return diags 89 | } 90 | -------------------------------------------------------------------------------- /internal/provider/resource_egress_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccResourceEgress(t *testing.T) { 11 | // t.Skip("resource not yet implemented, remove this once you add your own code") 12 | CreateTestData(t, true) 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccResourceEgress, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "netmaker_egress.foo", "netid", regexp.MustCompile("^test")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccResourceEgress = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | 35 | resource "netmaker_egress" "foo" { 36 | netid = "test" 37 | mac = "01:02:03:04:05:06" 38 | interface = "nm-test" 39 | ranges = ["0.0.0.0/0"] 40 | } 41 | ` 42 | -------------------------------------------------------------------------------- /internal/provider/resource_ingress.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/madacluster/netmaker-terraform-provider/helper" 10 | ) 11 | 12 | func resourceIngress() *schema.Resource { 13 | return &schema.Resource{ 14 | // This description is used by the documentation generator and the language server. 15 | Description: "Sample resource in the Terraform provider scaffolding.", 16 | 17 | CreateContext: resourceIngressCreate, 18 | ReadContext: resourceIngressRead, 19 | UpdateContext: resourceIngressUpdate, 20 | DeleteContext: resourceIngressDelete, 21 | 22 | Schema: addLastupdated(helper.CreateIngressSchema()), 23 | } 24 | } 25 | 26 | func resourceIngressCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 27 | // use the meta value to retrieve your client from the provider configure method 28 | // client := meta.(*apiClient) 29 | var diags diag.Diagnostics 30 | 31 | client := meta.(*helper.Client) 32 | netID := d.Get("netid").(string) 33 | mac := d.Get("mac").(string) 34 | node, err := client.CreateIngress(netID, mac) 35 | 36 | if err != nil { 37 | return diag.Errorf("failed to create models.Ingress: %s", err) 38 | } 39 | 40 | ID := fmt.Sprintf("%s-%s", netID, node.MacAddress) 41 | d.SetId(ID) 42 | return diags 43 | } 44 | 45 | func resourceIngressRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 46 | // use the meta value to retrieve your client from the provider configure method 47 | client := meta.(*helper.Client) 48 | mac := d.Get("mac").(string) 49 | netid := d.Get("netid").(string) 50 | // d.SetId(UserID) 51 | 52 | // Warning or errors can be collected in a slice type 53 | var diags diag.Diagnostics 54 | 55 | node, err := client.GetNode(netid, mac) 56 | if err != nil { 57 | return diag.FromErr(err) 58 | } 59 | helper.SetIngressSchemaData(d, node, netid, mac) 60 | 61 | return diags 62 | } 63 | 64 | func resourceIngressUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 65 | // use the meta value to retrieve your client from the provider configure method 66 | resourceIngressDelete(ctx, d, meta) 67 | 68 | return resourceIngressCreate(ctx, d, meta) 69 | } 70 | 71 | func resourceIngressDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 72 | // use the meta value to retrieve your client from the provider configure method 73 | // client := meta.(*apiClient) 74 | client := meta.(*helper.Client) 75 | var diags diag.Diagnostics 76 | 77 | networkID := d.Get("netid").(string) 78 | mac := d.Get("mac").(string) 79 | _, err := client.DeleteIngress(networkID, mac) 80 | if err != nil { 81 | return diag.FromErr(err) 82 | } 83 | 84 | // d.SetId("") is automatically called assuming delete returns no errors, but 85 | // it is added here for explicitness. 86 | d.SetId("") 87 | 88 | return diags 89 | } 90 | -------------------------------------------------------------------------------- /internal/provider/resource_ingress_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccResourceIngress(t *testing.T) { 11 | // t.Skip("resource not yet implemented, remove this once you add your own code") 12 | CreateTestData(t, true) 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccResourceIngress, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "netmaker_ingress.foo", "netid", regexp.MustCompile("^test")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccResourceIngress = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | 35 | resource "netmaker_ingress" "foo" { 36 | netid = "test" 37 | mac = "01:02:03:04:05:06" 38 | } 39 | ` 40 | -------------------------------------------------------------------------------- /internal/provider/resource_network.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/madacluster/netmaker-terraform-provider/helper" 10 | ) 11 | 12 | func addLastupdated(s map[string]*schema.Schema) map[string]*schema.Schema { 13 | s["last_updated"] = &schema.Schema{ 14 | Type: schema.TypeString, 15 | Computed: true, 16 | Optional: true, 17 | } 18 | return s 19 | } 20 | 21 | func resourceNetwork() *schema.Resource { 22 | return &schema.Resource{ 23 | // This description is used by the documentation generator and the language server. 24 | Description: "Sample resource in the Terraform provider scaffolding.", 25 | 26 | CreateContext: resourceNetworkCreate, 27 | ReadContext: resourceNetworkRead, 28 | UpdateContext: resourceNetworkUpdate, 29 | DeleteContext: resourceNetworkDelete, 30 | 31 | Schema: addLastupdated(helper.CreateNetworkSchema()), 32 | } 33 | } 34 | 35 | func resourceNetworkCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 36 | // use the meta value to retrieve your client from the provider configure method 37 | var diags diag.Diagnostics 38 | 39 | client := meta.(*helper.Client) 40 | network, err := client.CreateNetworkFromSchema(d) 41 | 42 | if err != nil { 43 | return diag.Errorf("failed to create network: %s", err) 44 | } 45 | d.SetId(network.NetID) 46 | return diags 47 | 48 | } 49 | 50 | func resourceNetworkRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 51 | // use the meta value to retrieve your client from the provider configure method 52 | client := meta.(*helper.Client) 53 | networkID := d.Id() 54 | 55 | // Warning or errors can be collected in a slice type 56 | var diags diag.Diagnostics 57 | 58 | network, err := client.GetNetwork(networkID) 59 | if err != nil { 60 | return diag.FromErr(err) 61 | } 62 | 63 | if helper.SetNetworkSchemaData(d, network) != nil { 64 | return diag.FromErr(err) 65 | } 66 | return diags 67 | } 68 | 69 | func resourceNetworkUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 70 | // use the meta value to retrieve your client from the provider configure method 71 | client := meta.(*helper.Client) 72 | if d.HasChangesExcept("last_updated") { 73 | _, err := client.UpdateNetworkFromSchema(d) 74 | if err != nil { 75 | return diag.FromErr(err) 76 | } 77 | d.Set("last_updated", time.Now().Format(time.RFC850)) 78 | 79 | } 80 | 81 | return resourceNetworkRead(ctx, d, meta) 82 | } 83 | 84 | func resourceNetworkDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 85 | // use the meta value to retrieve your client from the provider configure method 86 | // client := meta.(*apiClient) 87 | client := meta.(*helper.Client) 88 | var diags diag.Diagnostics 89 | 90 | networkID := d.Id() 91 | err := client.DeleteNetwork(networkID) 92 | if err != nil { 93 | return diag.FromErr(err) 94 | } 95 | 96 | // d.SetId("") is automatically called assuming delete returns no errors, but 97 | // it is added here for explicitness. 98 | d.SetId("") 99 | 100 | return diags 101 | } 102 | -------------------------------------------------------------------------------- /internal/provider/resource_network_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccResourceNetwork(t *testing.T) { 11 | // t.Skip("resource not yet implemented, remove this once you add your own code") 12 | 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccResourceNetwork, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "netmaker_network.foo", "netid", regexp.MustCompile("^netmakertes")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccResourceNetwork = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | resource "netmaker_network" "foo" { 35 | netid = "netmakertes" 36 | addressrange = "10.100.10.0/24" 37 | } 38 | ` 39 | -------------------------------------------------------------------------------- /internal/provider/resource_user.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "context" 5 | "time" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/madacluster/netmaker-terraform-provider/helper" 10 | ) 11 | 12 | func resourceUser() *schema.Resource { 13 | return &schema.Resource{ 14 | // This description is used by the documentation generator and the language server. 15 | Description: "Sample resource in the Terraform provider scaffolding.", 16 | 17 | CreateContext: resourceUserCreate, 18 | ReadContext: resourceUserRead, 19 | UpdateContext: resourceUserUpdate, 20 | DeleteContext: resourceUserDelete, 21 | 22 | Schema: addLastupdated(helper.CreateUserSchema()), 23 | } 24 | } 25 | 26 | func resourceUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 27 | // use the meta value to retrieve your client from the provider configure method 28 | // client := meta.(*apiClient) 29 | var diags diag.Diagnostics 30 | 31 | client := meta.(*helper.Client) 32 | user, err := client.CreateUserFromSchema(d) 33 | 34 | if err != nil { 35 | return diag.Errorf("failed to create models.User: %s", err) 36 | } 37 | d.SetId(user.UserName) 38 | return diags 39 | 40 | } 41 | 42 | func resourceUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 43 | // use the meta value to retrieve your client from the provider configure method 44 | client := meta.(*helper.Client) 45 | UserID := d.Id() 46 | 47 | // d.SetId(UserID) 48 | 49 | // Warning or errors can be collected in a slice type 50 | var diags diag.Diagnostics 51 | 52 | user, err := client.GetUser(UserID) 53 | if err != nil { 54 | return diag.FromErr(err) 55 | } 56 | 57 | if helper.SetUserSchemaData(d, user) != nil { 58 | return diag.FromErr(err) 59 | } 60 | return diags 61 | } 62 | 63 | func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 64 | // use the meta value to retrieve your client from the provider configure method 65 | client := meta.(*helper.Client) 66 | // UserID := d.Id() 67 | if d.HasChangesExcept("password") { 68 | _, err := client.UpdateUserFromSchema(d) 69 | if err != nil { 70 | return diag.FromErr(err) 71 | } 72 | d.Set("last_updated", time.Now().Format(time.RFC850)) 73 | 74 | } 75 | 76 | return resourceUserRead(ctx, d, meta) 77 | } 78 | 79 | func resourceUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 80 | // use the meta value to retrieve your client from the provider configure method 81 | // client := meta.(*apiClient) 82 | client := meta.(*helper.Client) 83 | var diags diag.Diagnostics 84 | 85 | UserID := d.Id() 86 | err := client.DeleteUser(UserID) 87 | if err != nil { 88 | return diag.FromErr(err) 89 | } 90 | 91 | // d.SetId("") is automatically called assuming delete returns no errors, but 92 | // it is added here for explicitness. 93 | d.SetId("") 94 | 95 | return diags 96 | } 97 | -------------------------------------------------------------------------------- /internal/provider/resource_user_test.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | func TestAccResourceUser(t *testing.T) { 11 | // t.Skip("resource not yet implemented, remove this once you add your own code") 12 | 13 | resource.UnitTest(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: providerFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccResourceAdminUser, 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestMatchResourceAttr( 21 | "netmaker_user.foo", "username", regexp.MustCompile("^netmakertes")), 22 | ), 23 | }, 24 | }, 25 | }) 26 | } 27 | 28 | const testAccResourceAdminUser = ` 29 | provider "netmaker" { 30 | username = "admin" 31 | password = "mx4S6JsSg7JWcZ" 32 | host = "http://localhost:8081" 33 | } 34 | resource "netmaker_network" "foo" { 35 | netid = "netmakertes" 36 | addressrange = "10.100.10.0/24" 37 | } 38 | resource "netmaker_user" "foo" { 39 | username = "netmakertes" 40 | password = "netmaker" 41 | networks = ["netmakertes"] 42 | } 43 | ` 44 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 9 | "github.com/madacluster/netmaker-terraform-provider/internal/provider" 10 | ) 11 | 12 | // Run "go generate" to format example terraform files and generate the docs for the registry/website 13 | 14 | // If you do not have terraform installed, you can remove the formatting command, but its suggested to 15 | // ensure the documentation is formatted properly. 16 | //go:generate terraform fmt -recursive ./examples/ 17 | 18 | // Run the docs generation tool, check its repository for more information on how it works and how docs 19 | // can be customized. 20 | //go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs 21 | 22 | var ( 23 | // these will be set by the goreleaser configuration 24 | // to appropriate values for the compiled binary 25 | version string = "dev" 26 | 27 | // goreleaser can also pass the specific commit if you want 28 | // commit string = "" 29 | ) 30 | 31 | func main() { 32 | var debugMode bool 33 | 34 | flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") 35 | flag.Parse() 36 | 37 | opts := &plugin.ServeOpts{ProviderFunc: provider.New(version)} 38 | 39 | if debugMode { 40 | // TODO: update this string with the full name of your provider as used in your configs 41 | err := plugin.Debug(context.Background(), "registry.terraform.io/hashicorp/netmaker", opts) 42 | if err != nil { 43 | log.Fatal(err.Error()) 44 | } 45 | return 46 | } 47 | 48 | plugin.Serve(opts) 49 | } 50 | -------------------------------------------------------------------------------- /tools/tools.go: -------------------------------------------------------------------------------- 1 | //go:build tools 2 | // +build tools 3 | 4 | package tools 5 | 6 | // document generation 7 | 8 | import ( 9 | _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" 10 | ) 11 | --------------------------------------------------------------------------------