├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ └── general_issue.md ├── dependabot.yml └── workflows │ ├── codeql-analysis.yml │ ├── documentation.yml │ ├── golangci-lint.yml │ ├── release.yml │ ├── reviewdog.yml │ └── tests.yml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .markdownlinkcheck.json ├── .markdownlint.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── GNUmakefile ├── LICENSE ├── README.md ├── TESTING.md ├── api ├── api-accessors.go ├── api.go ├── api_keys.go ├── attributes.go ├── config.go ├── config_option.go ├── environments.go ├── gen-accessors.go ├── groups.go ├── segments.go ├── split.go ├── split_definition.go ├── tags.go ├── traffic_types.go ├── users.go └── workspaces.go ├── docs ├── data-sources │ ├── environment.md │ ├── traffic_type.md │ └── workspace.md ├── index.md └── resources │ ├── api_key.md │ ├── environment.md │ ├── environment_segment_keys.md │ ├── group.md │ ├── segment.md │ ├── segment_environment_association.md │ ├── split_definition.md │ ├── split_split.md │ ├── traffic_type.md │ ├── traffic_type_attribute.md │ ├── user.md │ └── workspace.md ├── go.mod ├── go.sum ├── helper └── test │ └── config.go ├── main.go ├── scripts ├── build-release └── gofmtcheck.sh ├── split ├── config.go ├── data_source_split_environment.go ├── data_source_split_environment_test.go ├── data_source_split_traffic_type.go ├── data_source_split_traffic_type_test.go ├── data_source_split_workspace.go ├── data_source_split_workspace_test.go ├── helper.go ├── import_split_api_key_test.go ├── import_split_environment_segment_keys_test.go ├── import_split_environment_test.go ├── import_split_group_test.go ├── import_split_segment_environment_association_test.go ├── import_split_segment_test.go ├── import_split_split_definition_test.go ├── import_split_split_test.go ├── import_split_traffic_type_attribute_test.go ├── import_split_traffic_type_test.go ├── import_split_user_test.go ├── import_split_workspace_test.go ├── provider.go ├── provider_test.go ├── resource_split_api_key.go ├── resource_split_api_key_test.go ├── resource_split_environment.go ├── resource_split_environment_segment_keys.go ├── resource_split_environment_segment_keys_test.go ├── resource_split_environment_test.go ├── resource_split_group.go ├── resource_split_group_test.go ├── resource_split_segment.go ├── resource_split_segment_environment_association.go ├── resource_split_segment_environment_association_test.go ├── resource_split_segment_test.go ├── resource_split_split.go ├── resource_split_split_definition.go ├── resource_split_split_definition_test.go ├── resource_split_split_test.go ├── resource_split_traffic_type.go ├── resource_split_traffic_type_attribute.go ├── resource_split_traffic_type_attribute_test.go ├── resource_split_traffic_type_test.go ├── resource_split_user.go ├── resource_split_user_test.go ├── resource_split_workspace.go ├── resource_split_workspace_test.go └── version.go └── version └── version.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/general_issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue Template 3 | about: Report a bug, feature, or enhancement 4 | title: '' 5 | labels: needs_triaging 6 | assignees: davidji99 7 | 8 | --- 9 | 10 | Hi there, 11 | 12 | ### Terraform Version 13 | 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. 14 | 15 | ### HerokuX Provider Version 16 | Run `terraform -v` to show core and any provider versions. A sample output could be: 17 | 18 | ``` 19 | Terraform v0.14.8 20 | + provider.split v0.1.0 21 | ``` 22 | 23 | ### Affected Resource(s) 24 | Please list the resources as a list, for example: 25 | - opc_instance 26 | - opc_storage_volume 27 | 28 | If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this. 29 | 30 | ### Terraform Configuration Files 31 | ```hcl 32 | # Copy-paste your Terraform configurations here - for large Terraform configs, 33 | # please use a service like Dropbox and share a link to the ZIP file. For 34 | # security, you can also encrypt the files using our GPG public key. 35 | ``` 36 | 37 | ### Debug Output 38 | 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. Please MAKE SURE to mask any sensitive values. 39 | 40 | ### Panic Output 41 | If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the `crash.log`. 42 | 43 | ### Expected Behavior 44 | What should have happened? 45 | 46 | ### Actual Behavior 47 | What actually happened? 48 | 49 | ### Steps to Reproduce 50 | Please list the steps required to reproduce the issue, for example: 51 | 1. `terraform apply` 52 | 53 | ### Important Factoids 54 | Are there anything atypical about your accounts that we should know? For example: Running in EC2 Classic? Custom version of OpenStack? Tight ACLs? 55 | 56 | ### References 57 | Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example: 58 | - GH-1234 59 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | # Check for updates to GitHub Actions every weekday 9 | interval: "daily" 10 | 11 | # Maintain dependencies for Go modules 12 | - package-ecosystem: "gomod" 13 | directory: "/" 14 | schedule: 15 | # Check for updates to Go modules every weekday 16 | interval: "daily" -------------------------------------------------------------------------------- /.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 | name: "CodeQL" 7 | 8 | on: 9 | push: 10 | branches: [master] 11 | pull_request: 12 | # The branches below must be a subset of the branches above 13 | branches: [master] 14 | schedule: 15 | - cron: '0 7 * * 6' 16 | 17 | jobs: 18 | analyze: 19 | name: Analyze 20 | runs-on: ubuntu-latest 21 | 22 | strategy: 23 | fail-fast: false 24 | matrix: 25 | # Override automatic language detection by changing the below list 26 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] 27 | language: ['go'] 28 | # Learn more... 29 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection 30 | 31 | steps: 32 | - name: Checkout repository 33 | uses: actions/checkout@v4.1.1 34 | with: 35 | # We must fetch at least the immediate parents so that if this is 36 | # a pull request then we can checkout the head. 37 | fetch-depth: 2 38 | 39 | # If this run was triggered by a pull request event, then checkout 40 | # the head of the pull request instead of the merge commit. 41 | - run: git checkout HEAD^2 42 | if: ${{ github.event_name == 'pull_request' }} 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v3 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v3 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v3 72 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | name: Documentation Checks 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | paths: 8 | - .markdownlinkcheck.json 9 | - .markdownlint.yml 10 | - .github/workflows/documentation.yml 11 | - docs/** 12 | 13 | env: 14 | GO_VERSION: "1.21" 15 | GO111MODULE: on 16 | 17 | jobs: 18 | markdown-link-check: 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4.1.1 22 | - uses: gaurav-nelson/github-action-markdown-link-check@1.0.15 23 | with: 24 | check-modified-files-only: 'yes' 25 | use-quiet-mode: 'yes' 26 | use-verbose-mode: 'yes' 27 | config-file: '.markdownlinkcheck.json' 28 | folder-path: 'docs' 29 | file-extension: '.md' 30 | markdown-lint: 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v4.1.1 34 | - uses: avto-dev/markdown-lint@v1.5.0 35 | with: 36 | config: '.markdownlint.yml' 37 | args: 'docs' 38 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: [pull_request] 3 | jobs: 4 | golangci: 5 | name: golangci-lint 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/setup-go@v5 9 | with: 10 | go-version: 1.23 11 | - uses: actions/checkout@v4 12 | - name: golangci-lint 13 | uses: golangci/golangci-lint-action@v6 14 | with: 15 | # Required: the version of golangci-lint is required and must be specified without patch version: 16 | # we always use the latest patch version. 17 | version: v1.64.8 18 | 19 | # Optional: working directory, useful for monorepos 20 | # working-directory: somedir 21 | 22 | # Optional: golangci-lint command line arguments. 23 | # args: --issues-exit-code=0 24 | 25 | # Optional: show only new issues if it's a pull request. The default value is `false`. 26 | # only-new-issues: true -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | goreleaser: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@v4.1.1 14 | with: 15 | fetch-depth: 0 16 | - 17 | name: Set up Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version: 1.23 21 | - 22 | name: Import GPG key 23 | id: import_gpg 24 | uses: crazy-max/ghaction-import-gpg@v6.1.0 25 | with: 26 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 27 | - 28 | name: Run GoReleaser 29 | uses: goreleaser/goreleaser-action@v5.0.0 30 | with: 31 | version: v1.23 32 | args: release --rm-dist 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 35 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} -------------------------------------------------------------------------------- /.github/workflows/reviewdog.yml: -------------------------------------------------------------------------------- 1 | name: reviewdog 2 | on: [pull_request] 3 | jobs: 4 | misspell: 5 | name: runner / misspell 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Check out code. 9 | uses: actions/checkout@v4.1.1 10 | - name: misspell 11 | uses: reviewdog/action-misspell@v1 12 | with: 13 | github_token: ${{ secrets.github_token }} 14 | locale: "US" -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: tests 2 | on: [pull_request, push] 3 | env: 4 | GO111MODULE: on 5 | 6 | jobs: 7 | test: 8 | strategy: 9 | matrix: 10 | go-version: [1.22.x, 1.23.x, 1.24.x] 11 | platform: [ubuntu-latest] 12 | runs-on: ${{ matrix.platform }} 13 | 14 | steps: 15 | - uses: actions/setup-go@v5 16 | with: 17 | go-version: ${{ matrix.go-version }} 18 | - uses: actions/checkout@v4.1.1 19 | 20 | - name: Cache go modules 21 | uses: actions/cache@v4.2.3 22 | with: 23 | path: ~/go/pkg/mod 24 | key: ${{ runner.os }}-go-${{ hashFiles('go.sum') }} 25 | restore-keys: ${{ runner.os }}-go- 26 | 27 | - name: Run make fmt 28 | if: runner.os != 'Windows' 29 | run: | 30 | make fmt 31 | git diff --exit-code; code=$?; git checkout -- .; (exit $code) 32 | 33 | - name: Run go vet 34 | run: go vet ./... 35 | 36 | - name: Ensure generating accessors produces a zero diff 37 | shell: bash 38 | run: cd api && go run gen-accessors.go && git diff --exit-code; code=$?; git checkout -- .; (exit $code) 39 | 40 | - name: Run make build 41 | run: make build 42 | 43 | - name: Run make test 44 | run: make testacc TEST="./split/" TESTARGS='-run=TestProvider' -------------------------------------------------------------------------------- /.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 | 28 | website/vendor 29 | 30 | # Test exclusions 31 | !command/test-fixtures/**/*.tfstate 32 | !command/test-fixtures/**/.terraform/ 33 | 34 | # Keep windows files with windows line endings 35 | *.winfile eol=crlf 36 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | depguard: 3 | list-type: blacklist 4 | packages: 5 | # logging is allowed only by logutils.Log, logrus 6 | # is allowed to use only in logutils package 7 | - github.com/sirupsen/logrus 8 | packages-with-error-message: 9 | - github.com/sirupsen/logrus: "logging is allowed only by logutils.Log" 10 | dupl: 11 | threshold: 100 12 | funlen: 13 | lines: 100 14 | statements: 50 15 | gci: 16 | local-prefixes: github.com/golangci/golangci-lint 17 | goconst: 18 | min-len: 2 19 | min-occurrences: 2 20 | gocritic: 21 | enabled-tags: 22 | - diagnostic 23 | - experimental 24 | - opinionated 25 | - performance 26 | - style 27 | disabled-checks: 28 | - dupImport # https://github.com/go-critic/go-critic/issues/845 29 | - ifElseChain 30 | - octalLiteral 31 | - whyNoLint 32 | - wrapperFunc 33 | gocyclo: 34 | min-complexity: 15 35 | goimports: 36 | local-prefixes: github.com/golangci/golangci-lint 37 | golint: 38 | min-confidence: 0 39 | gomnd: 40 | settings: 41 | mnd: 42 | # don't include the "operation" and "assign" 43 | checks: argument,case,condition,return 44 | govet: 45 | check-shadowing: true 46 | settings: 47 | printf: 48 | funcs: 49 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof 50 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf 51 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf 52 | - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf 53 | lll: 54 | line-length: 140 55 | maligned: 56 | suggest-new: true 57 | misspell: 58 | locale: US 59 | nolintlint: 60 | allow-leading-space: true # don't require machine-readable nolint directives (i.e. with no leading space) 61 | allow-unused: false # report any unused nolint directives 62 | require-explanation: false # don't require an explanation for nolint directives 63 | require-specific: false # don't require nolint directives to be specific about which linter is being skipped 64 | nestif: 65 | # minimal complexity of if statements to report, 5 by default 66 | min-complexity: 4 67 | 68 | linters: 69 | # please, do not use `enable-all`: it's deprecated and will be removed soon. 70 | # inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint 71 | disable-all: true 72 | enable: 73 | # - deadcode 74 | # - errcheck 75 | - gosimple 76 | - govet 77 | - ineffassign 78 | - staticcheck 79 | - structcheck 80 | - typecheck 81 | # - unused 82 | - varcheck 83 | - depguard 84 | - dogsled 85 | # - gci 86 | # - gochecknoglobals 87 | - maligned 88 | # - wsl 89 | issues: 90 | # Excluding configuration per-path, per-linter, per-text and per-source 91 | exclude-rules: 92 | - path: _test\.go 93 | linters: 94 | - gomnd 95 | 96 | # https://github.com/go-critic/go-critic/issues/926 97 | - linters: 98 | - gocritic 99 | text: "unnecessaryDefer:" 100 | 101 | # Exclude some staticcheck messages 102 | - linters: 103 | - staticcheck 104 | text: "SA1019:" 105 | 106 | run: 107 | skip-dirs: 108 | - test/testdata_etc 109 | - internal/cache 110 | - internal/renameio 111 | - internal/robustio 112 | 113 | # golangci.com configuration 114 | # https://github.com/golangci/golangci/wiki/Configuration 115 | service: 116 | golangci-lint-version: 1.23.x # use the fixed version to not introduce new linters unexpectedly 117 | prepare: 118 | - echo "here I can run custom commands, but no preparation needed for this repo" 119 | 120 | output: 121 | # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" 122 | format: colored-line-number 123 | # print lines of code with issue, default is true 124 | print-issued-lines: true 125 | # print linter name in the end of issue text, default is true 126 | print-linter-name: true 127 | # make issues output unique by line, default is true 128 | uniq-by-line: true 129 | # add a prefix to the output file references; default is no prefix 130 | path-prefix: "" 131 | # sorts results by: filepath, line and column 132 | sort-results: false 133 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | before: 2 | hooks: 3 | - go mod tidy 4 | 5 | builds: 6 | - env: 7 | - CGO_ENABLED=0 8 | ldflags: 9 | - -s -w -X version.ProviderVersion={{.Version}} 10 | goos: 11 | - freebsd 12 | - openbsd 13 | - solaris 14 | - windows 15 | - linux 16 | - darwin 17 | goarch: 18 | - amd64 19 | - '386' 20 | - arm 21 | - arm64 22 | ignore: 23 | - goos: darwin 24 | goarch: '386' 25 | binary: '{{ .ProjectName }}_{{ .Tag }}' 26 | archives: 27 | - format: zip 28 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 29 | checksum: 30 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 31 | algorithm: sha256 32 | signs: 33 | - artifacts: checksum 34 | args: 35 | # if you are using this is a GitHub action or some other automated pipeline, you 36 | # need to pass the batch flag to indicate its not interactive. 37 | - "--batch" 38 | - "--local-user" 39 | - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key 40 | - "--output" 41 | - "${signature}" 42 | - "--detach-sign" 43 | - "${artifact}" 44 | release: 45 | draft: false 46 | changelog: 47 | skip: true 48 | dist: target -------------------------------------------------------------------------------- /.markdownlinkcheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | { 4 | "pattern": "^^#.*$" 5 | } 6 | ] 7 | } -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | # Configuration for markdownlint 2 | # https://github.com/DavidAnson/markdownlint#configuration 3 | 4 | default: true 5 | MD007: 6 | indent: 4 7 | 8 | # Disabled Rules 9 | # https://github.com/DavidAnson/markdownlint/blob/master/doc/Rules.md 10 | 11 | MD001: false 12 | MD004: false 13 | MD006: false 14 | MD010: false 15 | MD012: false 16 | MD013: false 17 | MD014: false 18 | MD022: false 19 | MD024: false 20 | MD034: false 21 | MD038: false 22 | MD040: false 23 | MD047: false -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | All version information can be found on the repository's releases page. 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 1. Update all relevant documentation files affected by your change. 13 | 1. Request an approval by the repository's maintainer(s) and ensure the pull request tests executed successfully. 14 | 15 | ## Code of Conduct 16 | 17 | ### Our Pledge 18 | 19 | In the interest of fostering an open and welcoming environment, we as 20 | contributors and maintainers pledge to making participation in our project and 21 | our community a harassment-free experience for everyone, regardless of age, body 22 | size, disability, ethnicity, gender identity and expression, level of experience, 23 | nationality, personal appearance, race, religion, or sexual identity and 24 | orientation. 25 | 26 | ### Our Standards 27 | 28 | Examples of behavior that contributes to creating a positive environment 29 | include: 30 | 31 | * Using welcoming and inclusive language 32 | * Being respectful of differing viewpoints and experiences 33 | * Gracefully accepting constructive criticism 34 | * Focusing on what is best for the community 35 | * Showing empathy towards other community members 36 | 37 | Examples of unacceptable behavior by participants include: 38 | 39 | * The use of sexualized language or imagery and unwelcome sexual attention or 40 | advances 41 | * Trolling, insulting/derogatory comments, and personal or political attacks 42 | * Public or private harassment 43 | * Publishing others' private information, such as a physical or electronic 44 | address, without explicit permission 45 | * Other conduct which could reasonably be considered inappropriate in a 46 | professional setting 47 | 48 | ### Our Responsibilities 49 | 50 | Project maintainers are responsible for clarifying the standards of acceptable 51 | behavior and are expected to take appropriate and fair corrective action in 52 | response to any instances of unacceptable behavior. 53 | 54 | Project maintainers have the right and responsibility to remove, edit, or 55 | reject comments, commits, code, wiki edits, issues, and other contributions 56 | that are not aligned to this Code of Conduct, or to ban temporarily or 57 | permanently any contributor for other behaviors that they deem inappropriate, 58 | threatening, offensive, or harmful. 59 | 60 | ### Scope 61 | 62 | This Code of Conduct applies both within project spaces and in public spaces 63 | when an individual is representing the project or its community. Examples of 64 | representing a project or community include using an official project e-mail 65 | address, posting via an official social media account, or acting as an appointed 66 | representative at an online or offline event. Representation of a project may be 67 | further defined and clarified by project maintainers. 68 | 69 | ### Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at [http://contributor-covenant.org/version/1/4][version] 73 | 74 | [homepage]: http://contributor-covenant.org 75 | [version]: http://contributor-covenant.org/version/1/4/ 76 | -------------------------------------------------------------------------------- /GNUmakefile: -------------------------------------------------------------------------------- 1 | TEST?=$$(go list ./... |grep -v 'vendor') 2 | GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) 3 | PKG_NAME=split 4 | WEBSITE_REPO=github.com/davidji99/terraform-provider-${PKG_NAME} 5 | OS := $(shell uname | tr '[:upper:]' '[:lower:]') 6 | VERSION := $(shell go run ${PKG_NAME}/version.go) 7 | SHA := $(shell git rev-parse --short HEAD) 8 | FILE_NAME=terraform-provider-${PKG_NAME}_v${VERSION} 9 | 10 | default: build 11 | 12 | build: fmtcheck 13 | go install 14 | 15 | install: fmtcheck 16 | make fmt 17 | make build 18 | cp ${GOPATH}/bin/terraform-provider-${PKG_NAME} ~/.terraform.d/plugins/${OS}_amd64/${FILE_NAME} 19 | 20 | release: fmtcheck 21 | scripts/build-release 22 | 23 | test: fmtcheck 24 | go test -i $(TEST) || exit 1 25 | echo $(TEST) | \ 26 | xargs -t -n4 go test -v $(TESTARGS) -timeout=30s -parallel=4 27 | 28 | testacc: fmtcheck 29 | TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 120m -ldflags="-X=github.com/davidji99/terraform-provider-${PKG_NAME}/version.ProviderVersion=test" 30 | 31 | vet: 32 | @echo "go vet ." 33 | @go vet $$(go list ./... | grep -v vendor/) ; if [ $$? -eq 1 ]; then \ 34 | echo ""; \ 35 | echo "Vet found suspicious constructs. Please check the reported constructs"; \ 36 | echo "and fix them if necessary before submitting the code for review."; \ 37 | exit 1; \ 38 | fi 39 | 40 | fmt: 41 | gofmt -w $(GOFMT_FILES) 42 | 43 | fmtcheck: 44 | @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" 45 | 46 | errcheck: 47 | @sh -c "'$(CURDIR)/scripts/errcheck.sh'" 48 | 49 | test-compile: 50 | @if [ "$(TEST)" = "./..." ]; then \ 51 | echo "ERROR: Set TEST to a specific package. For example,"; \ 52 | echo " make test-compile TEST=./$(PKG_NAME)"; \ 53 | exit 1; \ 54 | fi 55 | go test -c $(TEST) $(TESTARGS) 56 | 57 | website: 58 | ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO))) 59 | echo "$(WEBSITE_REPO) not found in your GOPATH (necessary for layouts and assets), get-ting..." 60 | git clone https://$(WEBSITE_REPO) $(GOPATH)/src/$(WEBSITE_REPO) 61 | endif 62 | @$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME) 63 | 64 | website-test: 65 | ifeq (,$(wildcard $(GOPATH)/src/$(WEBSITE_REPO))) 66 | echo "$(WEBSITE_REPO) not found in your GOPATH (necessary for layouts and assets), get-ting..." 67 | git clone https://$(WEBSITE_REPO) $(GOPATH)/src/$(WEBSITE_REPO) 68 | endif 69 | @$(MAKE) -C $(GOPATH)/src/$(WEBSITE_REPO) website-provider-test PROVIDER_PATH=$(shell pwd) PROVIDER_NAME=$(PKG_NAME) 70 | 71 | .PHONY: build test testacc vet fmt fmtcheck errcheck test-compile website website-test 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Terraform Provider Split 2 | ========================= 3 | 4 | This provider is used to configure certain resources supported by [Split API](https://docs.split.io/reference#introduction). 5 | 6 | For provider bugs/questions, please open an issue on this repository. 7 | 8 | Documentation 9 | ------------ 10 | 11 | Documentation about resources and data sources can be found 12 | [here](https://registry.terraform.io/providers/davidji99/split/latest/docs). 13 | 14 | Requirements 15 | ------------ 16 | 17 | - [Terraform](https://www.terraform.io/downloads.html) `v0.13.x`+ 18 | - [Go](https://golang.org/doc/install) 1.18 (to build the provider plugin) 19 | 20 | Usage 21 | ----- 22 | 23 | ```hcl 24 | provider "split" { 25 | version = "~> 0.1.0" 26 | } 27 | ``` 28 | 29 | Releases 30 | ------------ 31 | 32 | Provider binaries can be found [here](https://github.com/davidji99/terraform-provider-split/releases). 33 | 34 | Development 35 | ----------- 36 | 37 | If you wish to work on the provider, you'll first need [Go](http://www.golang.org) installed on your machine (version 1.12+ is *required*). 38 | 39 | If you wish to bump the provider version, you can do so in the file `version/version.go`. 40 | 41 | ### Build the Provider 42 | 43 | To compile the provider, run `make build`. This will build the provider and put the provider binary in the `$GOPATH/bin` directory. 44 | 45 | ```shell script 46 | $ make build 47 | ... 48 | $ $GOPATH/bin/terraform-provider-split 49 | ... 50 | ``` 51 | 52 | ### Using the Provider 53 | 54 | To use the dev provider with local Terraform, copy the freshly built plugin into Terraform's local plugins directory: 55 | 56 | ```sh 57 | cp $GOPATH/bin/terraform-provider-split ~/.terraform.d/plugins/ 58 | ``` 59 | 60 | Set the split provider without a version constraint: 61 | 62 | ```hcl 63 | provider "split" {} 64 | ``` 65 | 66 | Then, initialize Terraform: 67 | 68 | ```shell script 69 | terraform init 70 | ``` 71 | 72 | ### Testing 73 | 74 | Please see the [TESTING](TESTING.md) guide for detailed instructions on running tests. 75 | 76 | ### Updating or adding dependencies 77 | 78 | This project uses [Go Modules](https://github.com/golang/go/wiki/Modules) for dependency management. 79 | 80 | This example will fetch a module at the release tag and record it in your project's `go.mod` and `go.sum` files. 81 | It's a good idea to run `go mod tidy` afterward and then `go mod vendor` to copy the dependencies into a `vendor/` directory. 82 | 83 | If a module does not have release tags, then `module@SHA` can be used instead. 84 | -------------------------------------------------------------------------------- /TESTING.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | ## Provider Tests 4 | In order to test the provider, you can simply run `make test`. 5 | 6 | ```bash 7 | $ make test 8 | ``` 9 | 10 | ## Acceptance Tests 11 | 12 | You can run the complete suite of split acceptance tests by doing the following: 13 | 14 | ```bash 15 | $ make testacc TEST="./split/" 2>&1 | tee test.log 16 | ``` 17 | 18 | To run a single acceptance test in isolation replace the last line above with: 19 | 20 | ```bash 21 | $ make testacc TEST="./split/" TESTARGS='-run=TestAccSplitEnvironment_Basic' 22 | ``` 23 | 24 | A set of tests can be selected by passing `TESTARGS` a substring. For example, to run all split tests: 25 | 26 | ```bash 27 | $ make testacc TEST="./split/" TESTARGS='-run=TestAccSplitEnvironment_Basic' 28 | ``` 29 | 30 | ### Test Parameters 31 | 32 | The following parameters are available for running the test. The absence of some non-required parameters 33 | will cause certain tests to be skipped. 34 | 35 | * **TF_ACC** (`integer`) **Required** - must be set to `1`. 36 | * **SPLIT_API_KEY** (`string`) **Required** - A valid Split admin API key. 37 | * **SPLIT_TRAFFIC_TYPE_NAME** (`string`) - A valid Split traffic type name. 38 | * **SPLIT_WORKSPACE_ID** (`string`) - A valid Split workspace ID. 39 | * **SPLIT_WORKSPACE_NAME** (`string`) - A valid Split workspace name. 40 | 41 | **For example:** 42 | ```bash 43 | export TF_ACC=1 44 | export SPLIT_API_KEY= 45 | $ make testacc TEST="./TestAccSplitEnvironment_Basic/" 2>&1 | tee test.log 46 | ``` 47 | -------------------------------------------------------------------------------- /api/api_keys.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import "github.com/davidji99/simpleresty" 4 | 5 | var ( 6 | ValidApiKeyTypes = []string{"client_side", "server_side", "admin"} 7 | ValidApiKeyRoles = []string{"API_ALL_GRANTED", "API_APIKEY", "API_ADMIN", "API_WORKSPACE_ADMIN", "API_FEATURE_FLAG_VIEWER", 8 | "API_FEATURE_FLAG_EDITOR", "API_SEGMENT_VIEWER", "API_SEGMENT_EDITOR"} 9 | ) 10 | 11 | // KeysService handles communication with the api keys related 12 | // methods of the Split.io APIv2. 13 | // 14 | // Reference: https://docs.split.io/reference/api-keys-overview 15 | type KeysService service 16 | 17 | // KeyRequest represents a request to create an API key. 18 | type KeyRequest struct { 19 | Name string `json:"name"` 20 | KeyType string `json:"apiKeyType"` 21 | Roles []string `json:"roles"` 22 | Environments []KeyEnvironmentRequest `json:"environments"` 23 | Workspace *KeyWorkspaceRequest `json:"workspace"` 24 | } 25 | 26 | type KeyEnvironmentRequest struct { 27 | Type string `json:"type"` 28 | Id string `json:"id"` 29 | } 30 | 31 | type KeyWorkspaceRequest struct { 32 | Type string `json:"type"` 33 | Id string `json:"id"` 34 | } 35 | 36 | // KeyResponse represents the created key. 37 | // 38 | // Not all fields are added here. 39 | type KeyResponse struct { 40 | Id *string `json:"id"` 41 | Name *string `json:"name"` 42 | Roles []string `json:"roles"` 43 | Type *string `json:"type"` 44 | ApiKeyType *string `json:"apiKeyType"` 45 | 46 | // Key is the actual API key 47 | Key *string `json:"key"` 48 | } 49 | 50 | // Create an API key. 51 | // 52 | // Reference: https://docs.split.io/reference/create-an-api-key 53 | func (k *KeysService) Create(opts *KeyRequest) (*KeyResponse, *simpleresty.Response, error) { 54 | var result KeyResponse 55 | urlStr := k.client.http.RequestURL("/apiKeys") 56 | 57 | // Execute the request 58 | response, createErr := k.client.post(urlStr, &result, opts) 59 | 60 | return &result, response, createErr 61 | } 62 | 63 | // Delete an API key. 64 | // 65 | // Reference: https://docs.split.io/reference/delete-an-api-key 66 | func (k *KeysService) Delete(key string) (*simpleresty.Response, error) { 67 | urlStr := k.client.http.RequestURL("/apiKeys/%s", key) 68 | // Execute the request 69 | response, err := k.client.delete(urlStr, nil, nil) 70 | 71 | return response, err 72 | } 73 | -------------------------------------------------------------------------------- /api/attributes.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "github.com/davidji99/simpleresty" 6 | "net/url" 7 | ) 8 | 9 | // AttributesService handles communication with the attributes related 10 | // methods of the Split.io APIv2. 11 | // 12 | // Reference: https://docs.split.io/reference/attributes-overview 13 | type AttributesService service 14 | 15 | // Attribute represents an attribute in Split. 16 | type Attribute struct { 17 | ID *string `json:"id"` // this is different from the usually computed ID. 18 | OrganizationId *string `json:"organizationId"` 19 | TrafficTypeID *string `json:"trafficTypeId"` 20 | DisplayName *string `json:"displayName"` 21 | Description *string `json:"description"` 22 | DataType *string `json:"dataType"` // (Optional) The data type of the attribute used for display formatting, defaults to displaying the raw string. Must be one of: null, "string", "datetime", "number", "set" 23 | IsSearchable *bool `json:"isSearchable"` 24 | SuggestedValues []string `json:"suggestedValues"` 25 | } 26 | 27 | // AttributeRequest represents a request to create an attribute. 28 | type AttributeRequest struct { 29 | Identifier *string `json:"id,omitempty"` 30 | DisplayName *string `json:"displayName,omitempty"` 31 | Description *string `json:"description,omitempty"` 32 | TrafficTypeID *string `json:"trafficTypeId,omitempty"` 33 | DataType *string `json:"dataType,omitempty"` 34 | IsSearchable *bool `json:"isSearchable,omitempty"` 35 | SuggestedValues []string `json:"suggestedValues,omitempty"` 36 | } 37 | 38 | // AttributeListQueryParams represents all query parameters available when listing attributes 39 | type AttributeListQueryParams struct { 40 | // Whether to paginate the response. 41 | Paginate bool `url:"Paginate,omitempty"` 42 | 43 | // Search prefix under which to look for attributes (ex. att returns attribute1, but not myAttribute). 44 | // Search is case insensitive, and only available with pagination. 45 | SearchPrefix string `url:"searchPrefix,omitempty"` 46 | 47 | // Get results 'After' the marker passed into this parameter. Only available with pagination. Markers are obfuscated 48 | // strings by design. 49 | AfterMarker string `url:"afterMarker,omitempty"` 50 | 51 | // Get results 'Before' the marker passed into this parameter. Only available with pagination. Markers are obfuscated 52 | // strings by design. 53 | BeforeMarker string `url:"beforeMarker,omitempty"` 54 | 55 | // Limit the number of return objects. If nothing or something invalid is given, then this will be the default markerLimit 56 | // for the query. If something greater than the maximum limit is passed in, this will be the maximum allowed markerLimit for this query. 57 | MarkerLimit int `url:"markerLimit,omitempty"` 58 | } 59 | 60 | // List all attributes for a traffic type. 61 | // 62 | // Reference: https://docs.split.io/reference/get-attributes 63 | func (a *AttributesService) List(workspaceID, trafficTypeID string, opts *AttributeListQueryParams) ([]*Attribute, *simpleresty.Response, error) { 64 | var result []*Attribute 65 | urlStr, urlStrErr := a.client.http.RequestURLWithQueryParams(fmt.Sprintf("/schema/ws/%s/trafficTypes/%s", workspaceID, 66 | trafficTypeID), opts) 67 | if urlStrErr != nil { 68 | return nil, nil, urlStrErr 69 | } 70 | 71 | response, listErr := a.client.get(urlStr, &result, nil) 72 | 73 | return result, response, listErr 74 | } 75 | 76 | // FindByID retrieves an attribute by its ID. 77 | // 78 | // This is a helper method as it is not possible to retrieve a single attribute. 79 | func (a *AttributesService) FindByID(workspaceID, trafficTypeID, attributeID string, opts *AttributeListQueryParams) (*Attribute, *simpleresty.Response, error) { 80 | attributes, listResponse, listErr := a.List(workspaceID, trafficTypeID, opts) 81 | if listErr != nil { 82 | return nil, listResponse, listErr 83 | } 84 | 85 | for _, a := range attributes { 86 | if a.GetID() == attributeID { 87 | return a, nil, nil 88 | } 89 | } 90 | 91 | return nil, nil, fmt.Errorf("could not find attribute [%s]", attributeID) 92 | } 93 | 94 | // Create an attribute. 95 | // 96 | // Reference: https://docs.split.io/reference/save-attribute 97 | func (a *AttributesService) Create(workspaceID, trafficTypeID string, opts *AttributeRequest) (*Attribute, *simpleresty.Response, error) { 98 | var result Attribute 99 | urlStr := a.client.http.RequestURL("/schema/ws/%s/trafficTypes/%s", workspaceID, trafficTypeID) 100 | 101 | // Execute the request 102 | response, createErr := a.client.post(urlStr, &result, opts) 103 | 104 | return &result, response, createErr 105 | } 106 | 107 | // Update an attribute. 108 | // 109 | // Reference: https://docs.split.io/reference/update-attribute 110 | func (a *AttributesService) Update(workspaceID, trafficTypeID, attributeID string, opts *AttributeRequest) (*Attribute, *simpleresty.Response, error) { 111 | var result Attribute 112 | attributeIdEncoded := url.QueryEscape(attributeID) 113 | urlStr := a.client.http.RequestURL("/schema/ws/%s/trafficTypes/%s/%s", workspaceID, trafficTypeID, attributeIdEncoded) 114 | 115 | // Execute the request 116 | response, createErr := a.client.patch(urlStr, &result, opts) 117 | 118 | return &result, response, createErr 119 | } 120 | 121 | // Delete an attribute. 122 | // 123 | // Reference: https://docs.split.io/reference/delete-attribute 124 | func (a *AttributesService) Delete(workspaceID, trafficTypeID, attributeID string) (*simpleresty.Response, error) { 125 | attributeIdEncoded := url.QueryEscape(attributeID) 126 | urlStr := a.client.http.RequestURL("/schema/ws/%s/trafficTypes/%s/%s", workspaceID, trafficTypeID, attributeIdEncoded) 127 | 128 | // Execute the request 129 | response, deleteErr := a.client.delete(urlStr, nil, nil) 130 | 131 | return response, deleteErr 132 | } 133 | -------------------------------------------------------------------------------- /api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | // Config represents all configuration options available to user to customize the API v2. 4 | type Config struct { 5 | // APIBaseURL is the base URL for Sendgrid's API v3. 6 | APIBaseURL string 7 | 8 | // UserAgent used when communicating with the Sendgrid API. 9 | UserAgent string 10 | 11 | // CustomHTTPHeaders are any additional user defined headers. 12 | CustomHTTPHeaders map[string]string 13 | 14 | // ContentTypeHeader 15 | ContentTypeHeader string 16 | 17 | // AcceptHeader 18 | AcceptHeader string 19 | 20 | // APIKey 21 | APIKey string 22 | 23 | // ClientTimeout 24 | ClientTimeout int 25 | } 26 | 27 | // ParseOptions parses the supplied options functions. 28 | func (c *Config) ParseOptions(opts ...Option) error { 29 | // Range over each options function and apply it to our API type to 30 | // configure it. Options functions are applied in order, with any 31 | // conflicting options overriding earlier calls. 32 | for _, option := range opts { 33 | err := option(c) 34 | if err != nil { 35 | return err 36 | } 37 | } 38 | 39 | return nil 40 | } 41 | -------------------------------------------------------------------------------- /api/config_option.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // Option is a functional option for configuring the API client. 8 | type Option func(*Config) error 9 | 10 | // APIBaseURL allows for a custom API v3 base URL. 11 | func APIBaseURL(url string) Option { 12 | return func(c *Config) error { 13 | if err := validateBaseURLOption(url); err != nil { 14 | return err 15 | } 16 | 17 | c.APIBaseURL = url 18 | return nil 19 | } 20 | } 21 | 22 | // UserAgent allows for a custom User Agent. 23 | func UserAgent(userAgent string) Option { 24 | return func(c *Config) error { 25 | c.UserAgent = userAgent 26 | return nil 27 | } 28 | } 29 | 30 | // CustomHTTPHeaders allows for additional HTTPHeaders. 31 | func CustomHTTPHeaders(headers map[string]string) Option { 32 | return func(c *Config) error { 33 | c.CustomHTTPHeaders = headers 34 | return nil 35 | } 36 | } 37 | 38 | // APIKey sets the API key for authentication. 39 | func APIKey(token string) Option { 40 | return func(c *Config) error { 41 | c.APIKey = token 42 | return nil 43 | } 44 | } 45 | 46 | // ClientTimeout sets the client max timeout. 47 | func ClientTimeout(duration int) Option { 48 | return func(c *Config) error { 49 | c.ClientTimeout = duration 50 | return nil 51 | } 52 | } 53 | 54 | // validateBaseURLOption ensures that any custom base URLs do not end with a trailing slash. 55 | func validateBaseURLOption(url string) error { 56 | // Validate that there is no trailing slashes before setting the custom baseURL 57 | if url[len(url)-1:] == "/" { 58 | return fmt.Errorf("custom base URL cannot contain a trailing slash") 59 | } 60 | return nil 61 | } 62 | 63 | // ContentTypeHeader allows for a custom Content-Type header. 64 | func ContentTypeHeader(s string) Option { 65 | return func(c *Config) error { 66 | c.ContentTypeHeader = s 67 | return nil 68 | } 69 | } 70 | 71 | // AcceptHeader allows for a custom Aceept header. 72 | func AcceptHeader(s string) Option { 73 | return func(c *Config) error { 74 | c.AcceptHeader = s 75 | return nil 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /api/groups.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/davidji99/simpleresty" 5 | ) 6 | 7 | // GroupsService handles communication with the group related 8 | // methods of the Split.io APIv2. 9 | // 10 | // Reference: https://docs.split.io/reference#groups-overview 11 | type GroupsService service 12 | 13 | // Group represents a group in Split. 14 | type Group struct { 15 | ID *string `json:"id"` 16 | Name *string `json:"name"` 17 | Description *string `json:"description"` 18 | Type *string `json:"type"` 19 | } 20 | 21 | // GroupListResult 22 | type GroupListResult struct { 23 | Data []*Group `json:"objects"` 24 | NextMarker *string `json:"nextMarker,omitempty"` 25 | PreviousMarker *string `json:"previousMarker,omitempty"` 26 | Limit *int `json:"limit"` 27 | Count *int `json:"count"` 28 | } 29 | 30 | // GroupListOpts 31 | type GroupListOpts struct { 32 | // 1-200 are the potential values. Default=50 33 | Limit int `url:"limit,omitempty"` 34 | } 35 | 36 | // GroupRequest 37 | type GroupRequest struct { 38 | Name string `json:"name,omitempty"` 39 | Description string `json:"description,omitempty"` 40 | } 41 | 42 | // List all active Groups in the organization 43 | // 44 | // Reference: https://docs.split.io/reference#list-groups 45 | func (g *GroupsService) List(opts *GroupListOpts) (*GroupListResult, *simpleresty.Response, error) { 46 | var result GroupListResult 47 | //Here I have a problem with the RequestURLWithParams, TBD 48 | urlStr, urlStrErr := g.client.http.RequestURLWithQueryParams("/groups", opts) 49 | if urlStrErr != nil { 50 | return nil, nil, urlStrErr 51 | } 52 | 53 | // Execute the request 54 | response, getErr := g.client.get(urlStr, &result, nil) 55 | 56 | return &result, response, getErr 57 | } 58 | 59 | // Get a group by their group Id. 60 | // 61 | // Reference: https://docs.split.io/reference#get-group 62 | func (g *GroupsService) Get(id string) (*Group, *simpleresty.Response, error) { 63 | var result Group 64 | urlStr := g.client.http.RequestURL("/groups/%s", id) 65 | 66 | response, getErr := g.client.get(urlStr, &result, nil) 67 | 68 | return &result, response, getErr 69 | } 70 | 71 | // Create a new group in your organization. 72 | // 73 | // Reference: https://docs.split.io/reference#create-group 74 | func (g *GroupsService) Create(opts *GroupRequest) (*Group, *simpleresty.Response, error) { 75 | var result Group 76 | urlStr := g.client.http.RequestURL("/groups") 77 | 78 | // Execute the request 79 | response, getErr := g.client.post(urlStr, &result, opts) 80 | 81 | return &result, response, getErr 82 | } 83 | 84 | // Update a group in your organization. 85 | // 86 | // Reference: https://docs.split.io/reference#update-group 87 | func (g *GroupsService) Update(id string, opts *GroupRequest) (*Group, *simpleresty.Response, error) { 88 | var result Group 89 | urlStr := g.client.http.RequestURL("/groups/%s", id) 90 | 91 | // Execute the request 92 | response, getErr := g.client.put(urlStr, &result, opts) 93 | 94 | return &result, response, getErr 95 | } 96 | 97 | // Delete a group in your organization. 98 | // 99 | // Reference: https://docs.split.io/reference#delete-group 100 | func (g *GroupsService) Delete(id string) (*simpleresty.Response, error) { 101 | urlStr := g.client.http.RequestURL("/groups/%s", id) 102 | 103 | // Execute the request 104 | response, getErr := g.client.delete(urlStr, nil, nil) 105 | 106 | return response, getErr 107 | } 108 | -------------------------------------------------------------------------------- /api/segments.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/davidji99/simpleresty" 5 | ) 6 | 7 | // SegmentsService handles communication with the segment related 8 | // methods of the Split.io APIv2. 9 | // 10 | // Reference: https://docs.split.io/reference#segments-overview 11 | type SegmentsService service 12 | 13 | // Segment represents a Split segment. 14 | type Segment struct { 15 | Name *string `json:"name"` 16 | Description *string `json:"description,omitempty"` 17 | Environment *Environment `json:"environment"` 18 | TrafficType *TrafficType `json:"trafficType"` 19 | CreationTime *int64 `json:"creationTime"` 20 | Tags []*Tag `json:"tags,omitempty"` 21 | } 22 | 23 | // SegmentKeysList 24 | type SegmentKeysList struct { 25 | Keys []*SegmentKey `json:"keys"` 26 | GenericListResult 27 | } 28 | 29 | // SegmentKey 30 | type SegmentKey struct { 31 | Key *string `json:"key"` 32 | } 33 | 34 | // SegmentListResult represents the response returned when listing all segments. 35 | type SegmentListResult struct { 36 | Objects []*Segment `json:"objects"` 37 | GenericListResult 38 | } 39 | 40 | // SegmentRequest represents a request to create a segment. 41 | type SegmentRequest struct { 42 | Name string `json:"name"` 43 | Description string `json:"description,omitempty"` 44 | } 45 | 46 | // List all segments. 47 | // 48 | // Reference: https://docs.split.io/reference#list-segments 49 | func (s *SegmentsService) List(workspaceID string) (*SegmentListResult, *simpleresty.Response, error) { 50 | var result SegmentListResult 51 | urlStr := s.client.http.RequestURL("/segments/ws/%s", workspaceID) 52 | response, getErr := s.client.get(urlStr, &result, nil) 53 | 54 | return &result, response, getErr 55 | } 56 | 57 | // Get a segment. 58 | // 59 | // Reference: n/a 60 | func (s *SegmentsService) Get(workspaceID, name string) (*Segment, *simpleresty.Response, error) { 61 | var result Segment 62 | urlStr := s.client.http.RequestURL("/segments/ws/%s/%s", workspaceID, name) 63 | response, getErr := s.client.get(urlStr, &result, nil) 64 | 65 | return &result, response, getErr 66 | } 67 | 68 | // Create a segment. This API does not configure the Segment in any environment. 69 | // 70 | // Reference: https://docs.split.io/reference#create-segment 71 | func (s *SegmentsService) Create(workspaceID, trafficTypeID string, opts *SegmentRequest) (*Segment, *simpleresty.Response, error) { 72 | var result Segment 73 | urlStr := s.client.http.RequestURL("/segments/ws/%s/trafficTypes/%s", workspaceID, trafficTypeID) 74 | response, createErr := s.client.post(urlStr, &result, opts) 75 | 76 | return &result, response, createErr 77 | } 78 | 79 | // There exists an update (PUT) endpoint to modify the description but that is an internal endpoint: 80 | // https://app.split.io/internal/api/segmentMetadata/updateDescription/ 81 | // 82 | // TODO: implement this method whenever this endpoint is GA. 83 | //func (s *SegmentsService) Update() { 84 | //} 85 | 86 | // Delete a segment. This will automatically unconfigure the Segment Definition from all environments. 87 | // 88 | // Reference: https://docs.split.io/reference#delete-segment 89 | func (s *SegmentsService) Delete(workspaceID, segmentName string) (*simpleresty.Response, error) { 90 | urlStr := s.client.http.RequestURL("/segments/ws/%s/%s", workspaceID, segmentName) 91 | response, deleteErr := s.client.delete(urlStr, nil, nil) 92 | 93 | return response, deleteErr 94 | } 95 | 96 | // Activate a Segment in an environment to be able to set its definitions. 97 | // 98 | // Reference: https://docs.split.io/reference#enable-segment-in-environment 99 | func (s *SegmentsService) Activate(environmentID, segmentName string) (*Segment, *simpleresty.Response, error) { 100 | var result Segment 101 | urlStr := s.client.http.RequestURL("/segments/%s/%s", environmentID, segmentName) 102 | response, err := s.client.post(urlStr, &result, nil) 103 | 104 | return &result, response, err 105 | } 106 | 107 | // Deactivate a Segment in an environment. 108 | // 109 | // Reference: https://docs.split.io/reference#deactivate-segment-in-environment 110 | func (s *SegmentsService) Deactivate(environmentID, segmentName string) (*simpleresty.Response, error) { 111 | urlStr := s.client.http.RequestURL("/segments/%s/%s", environmentID, segmentName) 112 | response, err := s.client.delete(urlStr, nil, nil) 113 | 114 | return response, err 115 | } 116 | -------------------------------------------------------------------------------- /api/split.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | 7 | "github.com/davidji99/simpleresty" 8 | ) 9 | 10 | // SplitsService handles communication with the segment related 11 | // methods of the Split.io APIv2. 12 | // 13 | // Reference: https://docs.split.io/reference/splits-overview 14 | type SplitsService service 15 | 16 | // Split is a feature flag, toggle, or experiment. 17 | type Split struct { 18 | ID *string `json:"id"` 19 | Name *string `json:"name"` 20 | Description *string `json:"description"` 21 | Tags []SplitTag `json:"tags,omitempty"` 22 | CreationTime *int64 `json:"creationTime"` 23 | RolloutStatusTimestamp *int64 `json:"rolloutStatusTimestamp"` 24 | TrafficType *TrafficType `json:"trafficType"` 25 | RolloutStatus *SplitRolloutStatus `json:"rolloutStatus"` 26 | } 27 | 28 | // SplitRolloutStatus represents the rollout status. 29 | type SplitRolloutStatus struct { 30 | ID *string `json:"id"` 31 | Name *string `json:"name"` 32 | } 33 | 34 | // Splits represents all splits. 35 | type Splits struct { 36 | Objects []*Split `json:"objects"` 37 | GenericListResult 38 | } 39 | 40 | // SplitCreateRequest represents a request to create a split. 41 | type SplitCreateRequest struct { 42 | Name string `json:"name"` 43 | Description string `json:"description"` 44 | } 45 | 46 | // SplitUpdateRequest represents a request to update a split. 47 | type SplitUpdateRequest struct { 48 | Description string `json:"description"` 49 | } 50 | 51 | // SplitTag represents a tag in the split 52 | type SplitTag struct { 53 | Name string `json:"name"` 54 | } 55 | 56 | // List all splits. 57 | // 58 | // Reference: https://docs.split.io/reference/list-splits 59 | func (s *SplitsService) List(workspaceId string, opts ...interface{}) (*Splits, *simpleresty.Response, error) { 60 | var result Splits 61 | urlStr, err := s.client.http.RequestURLWithQueryParams(fmt.Sprintf("/splits/ws/%s", workspaceId), opts...) 62 | if err != nil { 63 | return nil, nil, err 64 | } 65 | 66 | // Execute the request 67 | response, getErr := s.client.get(urlStr, &result, nil) 68 | 69 | return &result, response, getErr 70 | } 71 | 72 | // Get a single split. 73 | // 74 | // splitId can be either the name or the UUID. 75 | // 76 | // Reference: https://docs.split.io/reference/get-split 77 | func (s *SplitsService) Get(workspaceId, splitId string) (*Split, *simpleresty.Response, error) { 78 | var result Split 79 | urlStr := s.client.http.RequestURL("/splits/ws/%s/%s", workspaceId, splitId) 80 | response, getErr := s.client.get(urlStr, &result, nil) 81 | 82 | return &result, response, getErr 83 | } 84 | 85 | // Create a single split. 86 | // 87 | // Reference: https://docs.split.io/reference/create-split 88 | func (s *SplitsService) Create(workspaceId, trafficTypeId string, opts *SplitCreateRequest) (*Split, *simpleresty.Response, error) { 89 | var result Split 90 | urlStr := s.client.http.RequestURL("/splits/ws/%s/trafficTypes/%s", workspaceId, trafficTypeId) 91 | 92 | // Execute the request 93 | response, createErr := s.client.post(urlStr, &result, opts) 94 | 95 | return &result, response, createErr 96 | } 97 | 98 | // UpdateDescription of an existing split. 99 | // 100 | // Reference: https://docs.split.io/reference/update-split-description 101 | func (s *SplitsService) UpdateDescription(workspaceId, splitName, description string) (*Split, *simpleresty.Response, error) { 102 | var result Split 103 | 104 | splitNameEncoded := url.QueryEscape(splitName) 105 | urlStr := s.client.http.RequestURL("/splits/ws/%s/%s/updateDescription", workspaceId, splitNameEncoded) 106 | 107 | // Execute the request 108 | response, updateErr := s.client.put(urlStr, &result, description) 109 | 110 | return &result, response, updateErr 111 | } 112 | 113 | // Delete a single split. 114 | // 115 | // This will automatically unconfigure the Split Definition from all environments. Returns `true` in the response body. 116 | // 117 | // Split name is required, not the split UUID. 118 | // 119 | // Reference: https://docs.split.io/reference/delete-split 120 | func (s *SplitsService) Delete(workspaceId, splitName string) (*simpleresty.Response, error) { 121 | splitNameEncoded := url.QueryEscape(splitName) 122 | urlStr := s.client.http.RequestURL("/splits/ws/%s/%s", workspaceId, splitNameEncoded) 123 | 124 | // Execute the request 125 | response, createErr := s.client.delete(urlStr, nil, nil) 126 | 127 | return response, createErr 128 | } 129 | -------------------------------------------------------------------------------- /api/tags.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | type Tag struct { 4 | Name *string `json:"name"` 5 | } 6 | -------------------------------------------------------------------------------- /api/traffic_types.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/davidji99/simpleresty" 7 | ) 8 | 9 | // TrafficTypesService handles communication with the traffic types related 10 | // methods of the Split.io APIv2. 11 | // 12 | // Reference: https://docs.split.io/reference#traffic-types-overview 13 | type TrafficTypesService service 14 | 15 | type TrafficType struct { 16 | Name *string `json:"name,omitempty"` 17 | Type *string `json:"type,omitempty"` 18 | ID *string `json:"id,omitempty"` 19 | DisplayAttributeID *string `json:"displayAttributeId,omitempty"` 20 | Workspace *Workspace `json:"workspace,omitempty"` 21 | } 22 | 23 | type TrafficTypeRequest struct { 24 | // Name must begin with a letter (a-z or A-Z) and can only contain letters, numbers, hyphens and underscores 25 | // (a-z, A-Z, 0-9, -, _). 26 | Name string `json:"name"` 27 | } 28 | 29 | // List all traffic types. 30 | // 31 | // Reference: https://docs.split.io/reference#get-traffic-types 32 | func (t *TrafficTypesService) List(workspaceID string) ([]*TrafficType, *simpleresty.Response, error) { 33 | var result []*TrafficType 34 | urlStr := t.client.http.RequestURL("/trafficTypes/ws/%s", workspaceID) 35 | response, getErr := t.client.get(urlStr, &result, nil) 36 | 37 | return result, response, getErr 38 | } 39 | 40 | // FindByID retrieves a traffic type by its ID. 41 | func (t *TrafficTypesService) FindByID(workspaceID, trafficTypeID string) (*TrafficType, *simpleresty.Response, error) { 42 | trafficTypes, response, listErr := t.List(workspaceID) 43 | if listErr != nil { 44 | return nil, response, listErr 45 | } 46 | 47 | for _, t := range trafficTypes { 48 | if t.GetID() == trafficTypeID { 49 | return t, nil, nil 50 | } 51 | } 52 | 53 | return nil, nil, fmt.Errorf("traffic type [%s] not found", trafficTypeID) 54 | } 55 | 56 | // FindByName retrieves a traffic type by its name. 57 | func (t *TrafficTypesService) FindByName(workspaceID, trafficTypeName string) (*TrafficType, *simpleresty.Response, error) { 58 | trafficTypes, response, listErr := t.List(workspaceID) 59 | if listErr != nil { 60 | return nil, response, listErr 61 | } 62 | 63 | for _, t := range trafficTypes { 64 | if t.GetName() == trafficTypeName { 65 | return t, nil, nil 66 | } 67 | } 68 | 69 | return nil, nil, fmt.Errorf("traffic type [%s] not found", trafficTypeName) 70 | } 71 | 72 | // Create a traffic type. 73 | // 74 | // Reference: https://docs.split.io/reference/create-traffic-types 75 | func (t *TrafficTypesService) Create(workspaceID string, opts *TrafficTypeRequest) (*TrafficType, *simpleresty.Response, error) { 76 | var result TrafficType 77 | urlStr := t.client.http.RequestURL("/trafficTypes/ws/%s", workspaceID) 78 | 79 | // Execute the request 80 | response, createErr := t.client.post(urlStr, &result, opts) 81 | 82 | return &result, response, createErr 83 | } 84 | 85 | // Delete a traffic type. 86 | // 87 | // Reference: https://docs.split.io/reference/delete-trafic-type 88 | func (t *TrafficTypesService) Delete(trafficTypeID string) (*simpleresty.Response, error) { 89 | urlStr := t.client.http.RequestURL("/trafficTypes/%s", trafficTypeID) 90 | 91 | // Execute the request 92 | response, deleteErr := t.client.delete(urlStr, nil, nil) 93 | 94 | return response, deleteErr 95 | } 96 | -------------------------------------------------------------------------------- /api/users.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "github.com/davidji99/simpleresty" 5 | ) 6 | 7 | // UsersService handles communication with the user related 8 | // methods of the Split.io APIv2. 9 | // 10 | // Reference: https://docs.split.io/reference#users-overview 11 | type UsersService service 12 | 13 | // User represents a user. 14 | type User struct { 15 | ID *string `json:"id"` 16 | Type *string `json:"type"` 17 | Name *string `json:"name"` 18 | Email *string `json:"email"` 19 | Status *string `json:"status"` 20 | TFA *bool `json:"2fa"` 21 | Groups []*Group `json:"groups,omitempty"` 22 | } 23 | 24 | // UserListResult 25 | type UserListResult struct { 26 | Data []*User `json:"data"` 27 | NextMarker *string `json:"nextMarker,omitempty"` 28 | PreviousMarker *string `json:"previousMarker,omitempty"` 29 | Limit *int `json:"limit"` 30 | Count *int `json:"count"` 31 | } 32 | 33 | // UserListOpts represents all query parameters when fetching all Users. 34 | type UserListOpts struct { 35 | // ACTIVE | DEACTIVATED | PENDING are the allowed status values to filter by 36 | Status string `url:"status,omitempty"` 37 | 38 | // 1-200 are the potential values. Default=50 39 | Limit int `url:"limit,omitempty"` 40 | 41 | // value of "previousMarker" in response 42 | Before int `url:"limit,omitempty"` 43 | 44 | // value of "nextMarker" in response 45 | After string `url:"limit,omitempty"` 46 | 47 | // eturns Active members of a group 48 | GroupID string `url:"limit,omitempty"` 49 | } 50 | 51 | // UserCreateRequest is to create a new user. 52 | type UserCreateRequest struct { 53 | Email string `json:"email,omitempty"` 54 | Groups []struct { 55 | ID string `json:"id,omitempty"` 56 | Type string `json:"type,omitempty"` 57 | } `json:"groups,omitempty"` 58 | } 59 | 60 | // UserUpdateRequest updates an existing user. 61 | type UserUpdateRequest struct { 62 | Name string `json:"name,omitempty"` 63 | Email string `json:"email,omitempty"` 64 | TFA *bool `json:"2fa,omitempty"` 65 | Status string `json:"status,omitempty"` 66 | } 67 | 68 | // List all active, deactivated, and pending users in the organization. 69 | // 70 | // By default, pending users are not returned via this endpoint. 71 | // 72 | // Reference: https://docs.split.io/reference#list-users 73 | func (u *UsersService) List(opts *UserListOpts) (*UserListResult, *simpleresty.Response, error) { 74 | var result UserListResult 75 | urlStr, urlStrErr := u.client.http.RequestURLWithQueryParams("/users", opts) 76 | if urlStrErr != nil { 77 | return nil, nil, urlStrErr 78 | } 79 | 80 | // Execute the request 81 | response, getErr := u.client.get(urlStr, &result, nil) 82 | 83 | return &result, response, getErr 84 | } 85 | 86 | // Get a user by their user Id. 87 | // 88 | // Reference: https://docs.split.io/reference#get-user 89 | func (u *UsersService) Get(id string) (*User, *simpleresty.Response, error) { 90 | var result User 91 | urlStr := u.client.http.RequestURL("/users/%s", id) 92 | response, getErr := u.client.get(urlStr, &result, nil) 93 | 94 | return &result, response, getErr 95 | } 96 | 97 | // Invite a new user to your organization. They will be created with a Pending status 98 | // 99 | // Reference: https://docs.split.io/reference#invite-a-new-user 100 | func (u *UsersService) Invite(opts *UserCreateRequest) (*User, *simpleresty.Response, error) { 101 | var result User 102 | urlStr := u.client.http.RequestURL("/users") 103 | 104 | // Execute the request 105 | response, err := u.client.post(urlStr, &result, opts) 106 | 107 | return &result, response, err 108 | } 109 | 110 | // Update display name, email, disable 2FA, and Activate/Deactivate of a User. 111 | // 112 | // Reference: https://docs.split.io/reference#full-update-user 113 | func (u *UsersService) Update(id string, opts *UserUpdateRequest) (*User, *simpleresty.Response, error) { 114 | var result User 115 | urlStr := u.client.http.RequestURL("/users/%s", id) 116 | 117 | // Execute the request 118 | response, err := u.client.put(urlStr, &result, opts) 119 | 120 | return &result, response, err 121 | } 122 | 123 | //// UpdateUserGroups Use this endpoint to update the groups that a user is part of. 124 | //// 125 | //// Reference: https://docs.split.io/reference#update-users-groups 126 | //func (s *UsersService) UpdateUserGroups(id string, opts *UserUpdateRequest) (*User, *simpleresty.Response, error) { 127 | // var result User 128 | // urlStr := s.client.http.RequestURL("/users/%s", id) 129 | // 130 | // // Execute the request 131 | // response, err := s.client.put(urlStr, &result, opts) 132 | // 133 | // return &result, response, err 134 | //} 135 | 136 | // DeletePendingUser that have not accepted their invites yet. Once a user is active, 137 | // you can only deactivate the user via a PUT request 138 | // 139 | // Reference: https://docs.split.io/reference#delete-a-pending-user 140 | func (u *UsersService) DeletePendingUser(id string) (*simpleresty.Response, error) { 141 | urlStr := u.client.http.RequestURL("/users/%s", id) 142 | 143 | // Execute the request 144 | response, deleteErr := u.client.delete(urlStr, nil, nil) 145 | 146 | return response, deleteErr 147 | } 148 | -------------------------------------------------------------------------------- /api/workspaces.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/davidji99/simpleresty" 7 | ) 8 | 9 | // WorkspacesService handles communication with the workspace related 10 | // methods of the Split.io APIv2. 11 | // 12 | // Reference: https://docs.split.io/reference/workspaces-overview 13 | type WorkspacesService service 14 | 15 | // Workspaces represents all workspaces. 16 | type Workspaces struct { 17 | Objects []*Workspace `json:"objects"` 18 | GenericListResult 19 | } 20 | 21 | // Workspace represents a workspace. 22 | type Workspace struct { 23 | Name *string `json:"name"` 24 | Type *string `json:"type"` 25 | ID *string `json:"id"` 26 | RequiresTitleAndComments *bool `json:"requiresTitleAndComments"` 27 | } 28 | 29 | // WorkspaceRequest represents a request to create/update a workspace. 30 | type WorkspaceRequest struct { 31 | Name *string `json:"name,omitempty"` 32 | RequiresTitleAndComments *bool `json:"requiresTitleAndComments,omitempty"` // Require title and comments for splits, segment, and metric changes. 33 | } 34 | 35 | // workspaceUpdateRequest represents the full request to update an existing workspace 36 | type workspaceUpdateRequestFull struct { 37 | Op string `json:"op"` 38 | Path string `json:"path"` 39 | Value interface{} `json:"value"` 40 | } 41 | 42 | // WorkspaceListQueryParams represents query parameters when retrieving all workspaces. 43 | type WorkspaceListQueryParams struct { 44 | GenericListQueryParams 45 | 46 | // Filter workspaces by name. 47 | Name string `url:"name,omitempty"` 48 | 49 | // Match operator for the name query parameter. `IS` is the default value but STARTS_WITH and CONTAINS is also supported. 50 | NameOp string `url:"nameOp,omitempty"` 51 | } 52 | 53 | // List all workspaces. 54 | // 55 | // Reference: https://docs.split.io/reference#get-workspaces 56 | func (w *WorkspacesService) List(opts ...interface{}) (*Workspaces, *simpleresty.Response, error) { 57 | var result *Workspaces 58 | // 59 | urlStr, err := w.client.http.RequestURLWithQueryParams("/workspaces", opts...) 60 | if err != nil { 61 | return nil, nil, err 62 | } 63 | 64 | // Execute the request 65 | response, getErr := w.client.get(urlStr, &result, nil) 66 | 67 | return result, response, getErr 68 | } 69 | 70 | // FindById retrieves a workspace by its ID. 71 | // 72 | // Note: this method uses the List() method to first return all workspaces and then look for the target workspace 73 | // by an ID. The Split APIv2 does not provide a GET#show endpoint for workspaces, unfortunately. 74 | func (w *WorkspacesService) FindById(id string) (*Workspace, *simpleresty.Response, error) { 75 | workspaces, listResponse, listErr := w.List() 76 | if listErr != nil { 77 | return nil, listResponse, listErr 78 | } 79 | 80 | if workspaces.HasObjects() { 81 | for _, w := range workspaces.Objects { 82 | if w.GetID() == id { 83 | return w, nil, nil 84 | } 85 | } 86 | } 87 | 88 | return nil, nil, fmt.Errorf("workspace [%s] not found", id) 89 | } 90 | 91 | // FindByName retrieves a workspace by its name. 92 | // 93 | // This method uses List query parameters to find an exact match. 94 | func (w *WorkspacesService) FindByName(name string) (*Workspace, *simpleresty.Response, error) { 95 | params := WorkspaceListQueryParams{Name: name, NameOp: "IS"} 96 | 97 | workspaces, listResponse, listErr := w.List(params) 98 | if listErr != nil { 99 | return nil, listResponse, listErr 100 | } 101 | 102 | if workspaces.HasObjects() { 103 | if len(workspaces.Objects) == 1 { 104 | return workspaces.Objects[0], nil, nil 105 | } 106 | } 107 | 108 | return nil, nil, fmt.Errorf("workspace [%s] not found", name) 109 | } 110 | 111 | // Create a workspaces. 112 | // 113 | // Note: When you create a workspace from this API, this won't create the default environment. 114 | // You must use the create environment API to create an environment. 115 | // 116 | // Reference: https://docs.split.io/reference/create-workspace 117 | func (w *WorkspacesService) Create(opts *WorkspaceRequest) (*Workspace, *simpleresty.Response, error) { 118 | var result Workspace 119 | urlStr := w.client.http.RequestURL("/workspaces") 120 | 121 | // Execute the request 122 | response, createErr := w.client.post(urlStr, &result, opts) 123 | 124 | return &result, response, createErr 125 | } 126 | 127 | // Update a workspaces. 128 | // 129 | // Reference: https://docs.split.io/reference/update-workspace 130 | func (w *WorkspacesService) Update(id string, opts *WorkspaceRequest) (*Workspace, *simpleresty.Response, error) { 131 | var result Workspace 132 | urlStr := w.client.http.RequestURL("/workspaces/%s", id) 133 | 134 | optsFull := make([]workspaceUpdateRequestFull, 0) 135 | if opts.Name != nil { 136 | optsFull = append(optsFull, workspaceUpdateRequestFull{ 137 | Op: "replace", 138 | Path: "/name", 139 | Value: *opts.Name, 140 | }) 141 | } 142 | 143 | if opts.RequiresTitleAndComments != nil { 144 | optsFull = append(optsFull, workspaceUpdateRequestFull{ 145 | Op: "replace", 146 | Path: "/requiresTitleAndComments", 147 | Value: *opts.RequiresTitleAndComments, 148 | }) 149 | } 150 | 151 | // Execute the request 152 | response, updateErr := w.client.patch(urlStr, &result, optsFull) 153 | 154 | return &result, response, updateErr 155 | } 156 | 157 | // Delete a workspace. 158 | // 159 | // Reference: https://docs.split.io/reference/delete-workspace 160 | func (w *WorkspacesService) Delete(id string) (*simpleresty.Response, error) { 161 | urlStr := w.client.http.RequestURL("/workspaces/%s", id) 162 | 163 | // Execute the request 164 | response, deleteErr := w.client.delete(urlStr, nil, nil) 165 | 166 | return response, deleteErr 167 | } 168 | -------------------------------------------------------------------------------- /docs/data-sources/environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_environment" 4 | sidebar_current: "docs-split-datasource-environment" 5 | description: |- 6 | Get information about a Split environment 7 | --- 8 | 9 | # Data Source: split_environment 10 | 11 | Use this data source to get information about a Split [environment](https://help.split.io/hc/en-us/articles/360019915771-Environments). 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_environment" "production" { 17 | workspace_id = "71572aa0-3177-4591-946c-6bd4a7197cdb" 18 | name = "user" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments are supported: 25 | 26 | * `workspace_id` - (Required) `` The UUID of the workspace 27 | * `name` - (Required) `` Name of the environment 28 | 29 | ## Attributes Reference 30 | 31 | The following attributes are exported: 32 | 33 | * `production` - If the environment is production 34 | -------------------------------------------------------------------------------- /docs/data-sources/traffic_type.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_traffic_type" 4 | sidebar_current: "docs-split-datasource-traffic-type" 5 | description: |- 6 | Get information about a Split traffic type 7 | --- 8 | 9 | # Data Source: split_traffic_type 10 | 11 | Use this data source to get information about a Split [traffic type](https://help.split.io/hc/en-us/articles/360019916311-Traffic-type#:~:text=Split%20allows%20you%20to%20have,needed%20during%20your%20account%20setup.). 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_traffic_type" "user" { 17 | workspace_id = "71572aa0-3177-4591-946c-6bd4a7197cdb" 18 | name = "user" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | The following arguments are supported: 25 | 26 | * `workspace_id` - (Required) `` The UUID of the workspace. 27 | * `name` - (Required) `` Name of the traffic type. 28 | 29 | ## Attributes Reference 30 | 31 | The following attributes are exported: 32 | 33 | * `type` - Type of traffic type. 34 | * `display_attribute_id` - The traffic type display attribute ID. 35 | * `workspace_name` - Name of the workspace. -------------------------------------------------------------------------------- /docs/data-sources/workspace.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_workspace" 4 | sidebar_current: "docs-split-datasource-workspace" 5 | description: |- 6 | Get information about a Split workspace 7 | --- 8 | 9 | # Data Source: split_workspace 10 | 11 | Use this data source to get information about a Split workspace. 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_workspace" "default" { 17 | name = "default" 18 | } 19 | ``` 20 | 21 | ## Argument Reference 22 | 23 | The following arguments are supported: 24 | 25 | * `name` - (Required) `` Name of the workspace 26 | 27 | ## Attributes Reference 28 | 29 | The following attributes are exported: 30 | 31 | * `type` - Type of workspace. 32 | 33 | * `requires_title_and_comments` - Whether the workspace requires titles and comments. -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Provider: Split" 4 | sidebar_current: "docs-split-index" 5 | description: |- 6 | The Split provider is used to interact with resources provided by the Split API. 7 | --- 8 | 9 | # Split Provider 10 | 11 | The Split provider is used to interact with resources provided by the 12 | [Split API](https://docs.split.io/reference#introduction). 13 | 14 | ## Contributing 15 | 16 | Development happens in the [GitHub repo](https://github.com/davidji99/terraform-provider-split): 17 | 18 | * [Releases](https://github.com/davidji99/terraform-provider-split/releases) 19 | * [Issues](https://github.com/davidji99/terraform-provider-split/issues) 20 | 21 | ## Example Usage 22 | 23 | ```hcl 24 | provider "split" { 25 | } 26 | 27 | # Create a new Split environment 28 | resource "split_environment" "foobar" { 29 | # ... 30 | } 31 | ``` 32 | 33 | ## Authentication 34 | 35 | The Split provider offers a flexible means of providing credentials for authentication. 36 | The following methods are supported, listed in order of precedence, and explained below: 37 | 38 | - Static credentials 39 | - Environment variables 40 | 41 | ### Static credentials 42 | 43 | Credentials can be provided statically by adding an `api_key` arguments to the Split provider block: 44 | 45 | ```hcl 46 | provider "split" { 47 | api_key = "SOME_API_KEY" 48 | } 49 | ``` 50 | 51 | ### Environment variables 52 | 53 | When the Split provider block does not contain an `api_key` argument, the missing credentials will be sourced 54 | from the environment via the `SPLIT_API_KEY` environment variables respectively: 55 | 56 | ```hcl 57 | provider "split" {} 58 | ``` 59 | 60 | ```shell 61 | $ export SPLIT_API_KEY="SOME_KEY" 62 | $ terraform plan 63 | Refreshing Terraform state in-memory prior to plan... 64 | ``` 65 | 66 | ## Rate Limiting 67 | 68 | The Split provider provides automatic backoff in the event the provider detects the Split API has 69 | [rate limited](https://docs.split.io/reference/rate-limiting) your Terraform operations. Please note 70 | this backoff has a max timeout that can be configured by [`client_timeout`](#argument-reference) within 71 | your `provider {}` block. 72 | 73 | ## Argument Reference 74 | 75 | The following arguments are supported: 76 | 77 | * `api_key` - (Required) Split API key. It must be provided, but it can also 78 | be sourced from [other locations](#Authentication). 79 | 80 | * `base_url` - (Optional) Custom API URL. 81 | Can also be sourced from the `SPLIT_API_URL` environment variable. 82 | 83 | * `remove_environment_from_state_only` - (Optional) Configure `split_environment` to only remove the resource from 84 | state upon deletion. This is to address out-of-band, UI based prerequisites Split has when deleting an environment. 85 | Defaults to `false`. 86 | 87 | * `client_timeout` - (Optional) Configure client (http) timeout before aborting. This is to address the client retrying forever. 88 | It's expressed in an integer that represents seconds. Defaults to `300` seconds, or `5` minutes. -------------------------------------------------------------------------------- /docs/resources/api_key.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_api_key" 4 | sidebar_current: "docs-split-resource-api-key" 5 | description: |- 6 | Provides the ability to manage a Split API key. 7 | --- 8 | 9 | # split_api_key 10 | 11 | This resource provides the ability to manage an [API key](https://docs.split.io/reference/api-keys-overview). 12 | 13 | Due to API limitations, it is not possible update an existing API key. Any modifications to a `split_api_key` resource 14 | will result in a destroy and recreate process. 15 | 16 | -> **IMPORTANT!** 17 | Please be very careful when deleting this resource as the deleted API keys are NOT recoverable and invalidated immediately. 18 | Furthermore, this resource renders the actual API key plain-text in your state file. 19 | Please ensure that your state file is properly secured and encrypted at rest. 20 | 21 | ## Example Usage 22 | 23 | ```hcl-terraform 24 | data "split_workspace" "default" { 25 | name = "default" 26 | } 27 | 28 | resource "split_environment" "foobar" { 29 | workspace_id = data.split_workspace.default.id 30 | name = "staging" 31 | production = true 32 | } 33 | 34 | resource "split_api_key" "foobar" { 35 | workspace_id = data.split_workspace.default.id 36 | name = "my client side key" 37 | type = "client_side" 38 | environment_ids = [split_environment.foobar.id] 39 | } 40 | ``` 41 | 42 | ## Argument Reference 43 | 44 | The following arguments are supported: 45 | 46 | * `workspace_id` - (Required) `` The UUID of the workspace you want to create the environment in. 47 | * `environment_ids` - (Required) `` List of environment UUIDs. 48 | * `name` - (Required) `` Name of the API key. 49 | * `type` - (Required) `` Type of the API key. Refer to Split [documentation](https://docs.split.io/reference/create-an-api-key#supported-types) on acceptable values, case sensitive. 50 | * `roles` - (Required) `` Supported only when `type=admin` API keys. For the full list of allowed Admin API Key roles, refer 51 | to Split [documentation](https://docs.split.io/reference/api-keys-overview#admin-api-key-roles), case sensitive. 52 | 53 | ## Attributes Reference 54 | 55 | The following attributes are exported: 56 | 57 | n/a 58 | 59 | ## Import 60 | 61 | Due to Split API limitations, it is not possible to import an existing API key. 62 | -------------------------------------------------------------------------------- /docs/resources/environment.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_environment" 4 | sidebar_current: "docs-split-resource-environment" 5 | description: |- 6 | Provides the ability to manage a Split environment. 7 | --- 8 | 9 | # split_environment 10 | 11 | This resource provides the ability to manage an [Environment](https://help.split.io/hc/en-us/articles/360019915771-Environments). 12 | Environments allow you to manage your splits throughout your development lifecycle — from local development to staging and production. 13 | 14 | ## Example Usage 15 | 16 | ```hcl-terraform 17 | data "split_workspace" "default" { 18 | name = "default" 19 | } 20 | 21 | resource "split_environment" "foobar" { 22 | workspace_id = data.split_workspace.default.id 23 | name = "production-canary" 24 | production = true 25 | } 26 | ``` 27 | 28 | ## Argument Reference 29 | 30 | The following arguments are supported: 31 | 32 | * `workspace_id` - (Required) `` The UUID of the workspace you want to create the environment in. 33 | * `name` - (Required) `` Name of the environment. 34 | * `production` - (Optional) `` Whether the environment is deemed 'production'. Defaults to `false`. 35 | 36 | ## Attributes Reference 37 | 38 | The following attributes are exported: 39 | 40 | n/a 41 | 42 | ## Import 43 | 44 | An existing environment can be imported using the combination of the workspace UUID 45 | and environment ID separated by a colon (':'). 46 | 47 | For example: 48 | 49 | ```shell script 50 | $ terraform import split_environment.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:110b3876-1d38-11ed-861d-0242ac120002" 51 | ``` -------------------------------------------------------------------------------- /docs/resources/environment_segment_keys.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_environment" 4 | sidebar_current: "docs-split-resource-environment" 5 | description: |- 6 | Provides the ability to manage Segment keys in a Split environment. 7 | --- 8 | 9 | # split_environment 10 | 11 | This resource provides the ability to manage Segment keys in a Split environment. 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_workspace" "default" { 17 | name = "default" 18 | } 19 | 20 | resource "split_environment" "foobar" { 21 | workspace_id = data.split_workspace.default.id 22 | name = "foobar_env" 23 | production = true 24 | } 25 | 26 | resource "split_traffic_type" "foobar" { 27 | workspace_id = data.split_workspace.default.id 28 | name = "foobar_traffic_type_env" 29 | } 30 | 31 | resource "split_segment" "foobar" { 32 | workspace_id = data.split_workspace.default.id 33 | traffic_type_id = split_traffic_type.foobar.id 34 | name = "foobar segment" 35 | description = "description of my foobar segment" 36 | } 37 | 38 | resource "split_segment_environment_association" "foobar" { 39 | workspace_id = data.split_workspace.default.id 40 | environment_id = split_environment.foobar.id 41 | segment_name = split_segment.foobar.name 42 | } 43 | 44 | resource "split_environment_segment_keys" "foobar" { 45 | environment_id = split_environment.foobar.id 46 | segment_name = split_segment_environment_association.foobar.segment_name 47 | keys = ["mytestkey1", "mytestkey2"] 48 | } 49 | ``` 50 | 51 | ## Argument Reference 52 | 53 | The following arguments are supported: 54 | 55 | * `environment_id` - (Required) `` The UUID of the environment. 56 | * `segment_name` - (Required) `` Name of the segment. 57 | * `keys` - (Optional) `` List of identifiers, aka keys. Can only add up to 10,000 keys at a time. 58 | Order of keys does not matter. 59 | 60 | ## Attributes Reference 61 | 62 | The following attributes are exported: 63 | 64 | n/a 65 | 66 | ## Import 67 | 68 | An existing environment can be imported using the combination of the environment UUID 69 | and segment name separated by a colon (':'). 70 | 71 | For example: 72 | 73 | ```shell script 74 | $ terraform import split_environment_segment_keys.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:name_of_my_segment" 75 | ``` 76 | -------------------------------------------------------------------------------- /docs/resources/group.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_group" 4 | sidebar_current: "docs-split-resource-group" 5 | description: |- 6 | Provides the ability to manage a Split group. 7 | --- 8 | 9 | # split_group 10 | 11 | This resource provides the ability to manage a group in Split. 12 | 13 | -> **IMPORTANT!** 14 | Groups are not available on Split's free tier. 15 | 16 | ## Example Usage 17 | 18 | ```hcl-terraform 19 | resource "split_group" "foobar" { 20 | name = "name_of_my_group" 21 | description = "description_of_my_group" 22 | } 23 | ``` 24 | 25 | ## Argument Reference 26 | 27 | The following arguments are supported: 28 | 29 | * `name` - (Required) `` Name of the group. 30 | * `description` - (Optional) `` Description of the group. 31 | 32 | ## Attributes Reference 33 | 34 | The following attributes are exported: 35 | 36 | n/a 37 | 38 | ## Import 39 | 40 | An existing group can be imported using the group UUID. 41 | 42 | For example: 43 | 44 | ```shell script 45 | $ terraform import split_group.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6" 46 | ``` -------------------------------------------------------------------------------- /docs/resources/segment.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_segment" 4 | sidebar_current: "docs-split-resource-segment" 5 | description: |- 6 | Provides the ability to manage a Split segment. 7 | --- 8 | 9 | # split_segment 10 | 11 | This resource provides the ability to manage a [Segment](https://help.split.io/hc/en-us/articles/360020407512-Create-a-segment). 12 | A segment is a pre-defined group of customers that a feature can be targeted to. Segments are best for targeting relatively 13 | fixed or specific groups of users that you can easily identify, like an allowlist of individual targets. 14 | 15 | ## Example Usage 16 | 17 | ```hcl-terraform 18 | data "split_workspace" "default" { 19 | name = "default" 20 | } 21 | 22 | data "split_traffic_type" "user" { 23 | workspace_id = data.split_environment.default.id 24 | name = "user" 25 | } 26 | 27 | resource "split_segment" "foobar" { 28 | workspace_id = data.split_workspace.default.id 29 | traffic_type_id = data.split_traffic_type.user.id 30 | name = "name_of_my_segment" 31 | description = "description_of_my_segment" 32 | } 33 | ``` 34 | 35 | ## Argument Reference 36 | 37 | The following arguments are supported: 38 | 39 | * `workspace_id` - (Required) `` The UUID of the workspace. 40 | * `traffic_type_id` - (Required) `` The UUID of the traffic type. 41 | * `name` - (Required) `` Name of the segment. 42 | * `description` - (Optional) `` Description of the segment. 43 | 44 | ## Attributes Reference 45 | 46 | The following attributes are exported: 47 | 48 | n/a 49 | 50 | ## Import 51 | 52 | An existing segment can be imported using the combination of the workspace UUID 53 | and segment name separated by a colon (':'). 54 | 55 | For example: 56 | 57 | ```shell script 58 | $ terraform import split_segment.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:name_of_my_segment" 59 | ``` -------------------------------------------------------------------------------- /docs/resources/segment_environment_association.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_segment_environment_association" 4 | sidebar_current: "docs-split-resource-segment-environment-association" 5 | description: |- 6 | Provides the ability to enable and disable a Split segment in an environment. 7 | --- 8 | 9 | # split_segment_environment_association 10 | 11 | This resource provides the ability to enable and disable a Split segment in an environment. 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_workspace" "default" { 17 | name = "default" 18 | } 19 | 20 | resource "split_environment" "foobar" { 21 | workspace_id = data.split_workspace.default.id 22 | name = "foobar_env" 23 | production = true 24 | } 25 | 26 | resource "split_traffic_type" "foobar" { 27 | workspace_id = data.split_workspace.default.id 28 | name = "foobar_traffic_type_env" 29 | } 30 | 31 | resource "split_segment" "foobar" { 32 | workspace_id = data.split_workspace.default.id 33 | traffic_type_id = split_traffic_type.foobar.id 34 | name = "foobar segment" 35 | description = "description of my foobar segment" 36 | } 37 | 38 | resource "split_segment_environment_association" "foobar" { 39 | workspace_id = data.split_workspace.default.id 40 | environment_id = split_environment.foobar.id 41 | segment_name = split_segment.foobar.name 42 | } 43 | ``` 44 | 45 | ## Argument Reference 46 | 47 | The following arguments are supported: 48 | 49 | * `workspace_id` - (Required) `` The UUID of the workspace. 50 | * `environment_id` - (Required) `` The UUID of the environment. 51 | * `segment_name` - (Required) `` Name of the segment. 52 | 53 | ## Attributes Reference 54 | 55 | The following attributes are exported: 56 | 57 | n/a 58 | 59 | ## Import 60 | 61 | An existing segment can be imported using the combination of the workspace UUID, environment UUID, and 62 | segment name separated by a colon (':'). 63 | 64 | For example: 65 | 66 | ```shell script 67 | $ terraform import split_segment_environment_association.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:a6d5d991-4069-44bd-8d00-949d8cafd120:name_of_my_segment" 68 | ``` 69 | -------------------------------------------------------------------------------- /docs/resources/split_definition.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_split_definition" 4 | sidebar_current: "docs-split-resource-split-definition" 5 | description: |- 6 | Provides the ability to manage a split definition. 7 | --- 8 | 9 | # split_definition 10 | 11 | This resource provides the ability to manage a Split Definition. 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_workspace" "default" { 17 | name = "default" 18 | } 19 | 20 | resource "split_environment" "foobar" { 21 | workspace_id = data.split_workspace.default.id 22 | name = "production-canary" 23 | production = true 24 | } 25 | 26 | resource "split_traffic_type" "foobar" { 27 | workspace_id = data.split_workspace.default.id 28 | name = "my_traffic_type" 29 | } 30 | 31 | resource "split_split" "foobar" { 32 | workspace_id = data.split_workspace.default.id 33 | traffic_type_id = split_traffic_type.foobar.id 34 | name = "my_split" 35 | description = "my split description" 36 | } 37 | 38 | resource "split_split_definition" "foobar" { 39 | workspace_id = data.split_workspace.default.id 40 | split_name = split_split.foobar.name 41 | environment_id = split_environment.foobar.id 42 | 43 | default_treatment = "treatment_123" 44 | treatment { 45 | name = "treatment_123" 46 | configurations = "{\"key\":\"value\"}" 47 | description = "my treatment 123" 48 | } 49 | treatment { 50 | name = "treatment_456" 51 | configurations = "{\"key2\":\"value2\"}" 52 | description = "my treatment 456" 53 | } 54 | 55 | default_rule { 56 | treatment = "treatment_123" 57 | size = 100 58 | } 59 | rule { 60 | bucket { 61 | treatment = "treatment_123" 62 | size = 100 63 | } 64 | condition { 65 | combiner = "AND" 66 | matcher { 67 | type = "EQUAL_SET" 68 | attribute = "test_string" 69 | strings = ["test_string"] 70 | } 71 | } 72 | } 73 | } 74 | ``` 75 | 76 | ## Argument Reference 77 | 78 | The following arguments are supported: 79 | 80 | * `workspace_id` - (Required) `` The UUID of the workspace. 81 | * `split_name` - (Required) `` The name, not UUID, of the Split 82 | * `environment_id` - (Required) `` The UUID of the environment 83 | * `default_treatment` - (Required) `` Default treatment to place unassigned customers into or randomly distribute 84 | these customers between your treatments/variations based off of percentages you decide. This attribute value should 85 | match one of your `treatment.name` values. 86 | * `treatment` - (Required) `` See the [specification](#treatment) below for more details. 87 | * `default_rule` - (Required) `` See the [specification](#default_rule) below for more details. 88 | * `rule` - (Required) `` See the [specification](#rule) below for more details. 89 | 90 | ### `treatment` 91 | 92 | What are the different variations of this feature? Define the treatments and add a description for each to quickly 93 | explain the difference between each treatment. This attribute block supports the following: 94 | 95 | * `name` - (Required) `` Name of the treatment. 96 | * `configurations` - (Required) `` Dynamically configure components of your feature (e.g. A button's color or backend API pagination). 97 | This attribute's value must be a valid JSON string. 98 | * `description` - (Optional) `` Description of the treatment. 99 | * `keys` - (Optional) `` List of target key ids. 100 | * `segments` - (Optional) `` List of segments. 101 | 102 | ### `default_rule` 103 | 104 | For any of your customers that weren't assigned a treatment in the sections above, 105 | use this section to place them into treatments or randomly distribute these customers among your treatments/variations 106 | based off of percentages you decide. This attribute block supports the following: 107 | 108 | * `treatment` - (Required) `` Name of a valid Treatment. 109 | * `size` - (Required) `` Treatment size. The sum of all your `default_rule` blocks must equal `100`. 110 | 111 | ### `rule` 112 | 113 | Target specific subsets of your customers based on a specific attribute (e.g., location or last-login date). These subsets 114 | are placed in specific treatments or, based on the percentages you decide, we’ll take these subsets and randomly distribute 115 | customers in the subset between your treatments. This attribute block supports the following: 116 | 117 | * `bucket` - (Required) `` List of treatments. 118 | * `treatment` - (Required) `` Name of a valid Treatment. 119 | * `size` - (Required) `` Treatment size. 120 | * `condition` - (Required) `` Rule conditions. 121 | * `combiner` - (Required) `` rule condition combiner. 122 | * `matcher` - (Required) `` rule condition matcher. 123 | * `type` - (Required) `` rule condition matcher type. 124 | * `attribute` - (Required) `` rule condition matcher type. 125 | * `string` - (Optional) `` This matcher selects customers with an attribute or key that matches the regex pattern set by this attribute. 126 | * `strings` - (Optional) `` rule condition matcher type. 127 | 128 | It is recommended to view the UI in order to determine what are some of the possible attribute values. 129 | 130 | ## Attributes Reference 131 | 132 | The following attributes are exported: 133 | 134 | n/a 135 | 136 | ## Import 137 | 138 | An existing split definition can be imported using the combination of the workspace UUID, split name, and environment UUID 139 | separated by a colon (':'). 140 | 141 | For example: 142 | 143 | ```shell script 144 | $ terraform import split_split_definition.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:my-split:8e52ce80-e05b-11ec-800d-5a826ff9ecd9" 145 | ``` -------------------------------------------------------------------------------- /docs/resources/split_split.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_split" 4 | sidebar_current: "docs-split-resource-split" 5 | description: |- 6 | Provides the ability to manage a split. 7 | --- 8 | 9 | # split_split 10 | 11 | This resource provides the ability to manage a Split. A Split is a feature flag, toggle, or experiment. 12 | 13 | ## Example Usage 14 | 15 | ```hcl-terraform 16 | data "split_workspace" "default" { 17 | name = "default" 18 | } 19 | 20 | resource "split_traffic_type" "foobar" { 21 | workspace_id = data.split_workspace.default.id 22 | name = "my_traffic_type" 23 | } 24 | 25 | resource "split_split" "foobar" { 26 | workspace_id = data.split_workspace.default.id 27 | traffic_type_id = split_traffic_type.foobar.id 28 | name = "my_split" 29 | description = "my split description" 30 | } 31 | ``` 32 | 33 | ## Argument Reference 34 | 35 | The following arguments are supported: 36 | 37 | * `workspace_id` - (Required) `` The UUID of the workspace. 38 | * `traffic_type_id` - (Required) `` The UUID of the traffic type. 39 | * `name` - (Required) `` Name of Split. Name must start with a letter and can contain hyphens, underscores, letters, and numbers 40 | * `description` - (Optional) `` Description of Split. 41 | 42 | ## Attributes Reference 43 | 44 | The following attributes are exported: 45 | 46 | n/a 47 | 48 | ## Import 49 | 50 | An existing split can be imported using the combination of the workspace UUID 51 | and split UUID separated by a colon (':'). 52 | 53 | For example: 54 | 55 | ```shell script 56 | $ terraform import split_split.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:8e52ce80-e05b-11ec-800d-5a826ff9ecd9" 57 | ``` -------------------------------------------------------------------------------- /docs/resources/traffic_type.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_traffic_type" 4 | sidebar_current: "docs-split-resource-traffic-type" 5 | description: |- 6 | Provides the ability to manage a Split traffic type. 7 | --- 8 | 9 | # split_traffic_type 10 | 11 | This resource provides the ability to manage a traffic type. 12 | 13 | A traffic type is a particular identifier type for any hierarchy of your customer base. Traffic types in Split are 14 | completely customizable and can be any database key you choose to send to Split, i.e. a user ID, account ID, IP address, 15 | browser ID, etc. Essentially, any internal database key you're using to track what "customer" means to you. 16 | 17 | ## Example Usage 18 | 19 | ```hcl-terraform 20 | data "split_workspace" "default" { 21 | name = "default" 22 | } 23 | 24 | resource "split_traffic_type" "foobar" { 25 | workspace_id = data.split_workspace.default.id 26 | name = "name_of_my_traffic_type" 27 | } 28 | ``` 29 | 30 | ## Argument Reference 31 | 32 | The following arguments are supported: 33 | 34 | * `workspace_id` - (Required) `` The UUID of the workspace. 35 | * `name` - (Required) `` Name of the traffic type. 36 | 37 | ## Attributes Reference 38 | 39 | The following attributes are exported: 40 | 41 | * `type` - Type of traffic type. 42 | * `display_attribute_id` - Attribute used for display name in UI. 43 | 44 | ## Import 45 | 46 | An existing traffic type can be imported using the combination of the workspace UUID 47 | and traffic type UUID separated by a colon (':'). 48 | 49 | For example: 50 | 51 | ```shell script 52 | $ terraform import split_traffic_type.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:268103c0-8eef-11ec-a34a-1ae28fca10c9" 53 | ``` -------------------------------------------------------------------------------- /docs/resources/traffic_type_attribute.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_traffic_type_attribute" 4 | sidebar_current: "docs-split-resource-traffic-type-attribute" 5 | description: |- 6 | Provides the ability to manage a Split traffic type attribute. 7 | --- 8 | 9 | # split_traffic_type_attribute 10 | 11 | This resource provides the ability to manage an [Traffic type Attribute](https://help.split.io/hc/en-us/articles/360020793231-Target-with-custom-attributes). 12 | 13 | Attribute schemas (commonly called Attributes) are definitions for attribute values, which are stored on Traffic 14 | ID Keys (Identities). 15 | 16 | ## Default attributes 17 | 18 | Attributes are also created automatically when new keys are saved to Split. Those newly created attributes are 19 | created with an unknown data type and with no display or description information. You will need to import these 20 | attributes first before managing them via Terraform. 21 | 22 | ## Example Usage 23 | 24 | ```hcl-terraform 25 | resource "split_traffic_type" "foobar" { 26 | workspace_id = "my_workspace_id" 27 | name = "my_workspace_name" 28 | } 29 | 30 | resource "split_traffic_type_attribute" "foobar" { 31 | workspace_id = "my_workspace_id" 32 | traffic_type_id = split_traffic_type.foobar.id 33 | identifier = "my-attribute" 34 | display_name = "my attribute display name" 35 | description = "this is my attribute description" 36 | data_type = "NUMBER" 37 | suggested_values = ["1", "2", "3"] 38 | is_searchable = true 39 | } 40 | ``` 41 | 42 | ## Argument Reference 43 | 44 | The following arguments are supported: 45 | 46 | * `workspace_id` - (Required) `` The UUID of the workspace. 47 | * `traffic_type_id` - (Required) `` The UUID of the traffic type. 48 | * `identifier` - (Required) `` Id of the attribute. Max length is 200 characters. 49 | * `display_name` - (Required) `` Display name of the attribute. Max length is 200 characters. 50 | * `description` - (Required) `` Description of the attribute. Max length is 500 characters. 51 | * `data_type` - (Optional) `` Data type of the attribute. See the [specification](#data_type) below for more details. 52 | * `suggested_values` - (Optional) `` Suggested value(s) of the attribute. See the [specification](#suggested_values) below for more details. 53 | * `is_searchable` - (Optional) `` Whether or not the attribute is searchable. 54 | 55 | ### `data_type` 56 | 57 | Valid options are `STRING`, `DATETIME`, `NUMBER`, `SET` and are case sensitive. If you don't want 58 | to explicitly set a type, do not define it in your resource configuration. 59 | 60 | The data type is an optional parameter which is used for display formatting purposes. 61 | This parameter is not used to validate the values on inbound keys, but incorrectly typed data will 62 | be displayed as their raw string. 63 | 64 | ### `suggested_values` 65 | 66 | Regardless of what [`data_type`](#data_type) is set to, all values need to be defined as strings. 67 | 68 | The maximum number of values is 50 and each value cannot exceed 50 characters. 69 | 70 | Values for datetime fields are expected to be passed as milliseconds past the epoch. Values for set fields are expected 71 | to be passed in a string as comma separated values. If an unsupported data type is sent you will receive a 400 error code 72 | as a response. 73 | 74 | ## Attributes Reference 75 | 76 | The following attributes are exported: 77 | 78 | * `organization_id` - The attribute's organization id. 79 | 80 | ## Import 81 | 82 | An existing attribute can be imported using the combination of the workspace UUID, traffic type UUID, 83 | and attribute ID separated by a colon (':'). 84 | 85 | For example: 86 | 87 | ```shell script 88 | $ terraform import split_traffic_type_attribute.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6:110b3876-1d38-11ed-861d-0242ac120002:my-attribute" 89 | ``` 90 | -------------------------------------------------------------------------------- /docs/resources/user.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_user" 4 | sidebar_current: "docs-split-resource-user" 5 | description: |- 6 | Provides the ability to manage a Split user. 7 | --- 8 | 9 | # split_user 10 | 11 | This resource provides the ability to manage a user in Split. 12 | 13 | Due to API behavior, this resource does not provide the ability 14 | to set a user's `name` attribute as you cannot update a user 15 | that hasn't accepted its invitation. The resource cannot accurately 16 | determine the user as accepted the invitation within a reasonable 17 | amount of time. 18 | 19 | ### Deletion Behavior 20 | 21 | Upon resource deletion, the following may occur: 22 | 23 | * If the user has not accepted the invitation, the invitation will be deleted. 24 | * If the user has accepted the invitation already, the user will be deactivated. 25 | 26 | ## Example Usage 27 | 28 | ```hcl-terraform 29 | resource "split_user" "user" { 30 | email = "user@company.com" 31 | } 32 | ``` 33 | 34 | ## Argument Reference 35 | 36 | The following arguments are supported: 37 | 38 | * `email` - (Required) `` Name of the user. 39 | 40 | ## Attributes Reference 41 | 42 | The following attributes are exported: 43 | 44 | * `name` - Name of the user. By default, the `name` is everything before the '@' in the user `email`. 45 | * `2fa` - Whether 2FA is enabled. 46 | * `status` - Status of the user. 47 | 48 | ## Import 49 | 50 | An existing user can be imported using the user UUID. 51 | 52 | For example: 53 | 54 | ```shell script 55 | $ terraform import split_user.foobar "0b46d8f7-9435-4f74-a770-3fcb22fbbfe6" 56 | ``` -------------------------------------------------------------------------------- /docs/resources/workspace.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: "split" 3 | page_title: "Split: split_workspace" 4 | sidebar_current: "docs-split-resource-workspace" 5 | description: |- 6 | Provides the ability to manage a Split workspace. 7 | --- 8 | 9 | # split_workspace 10 | 11 | Use this resource to manage a Split workspace. 12 | 13 | Workspaces allow you to separately manage your feature flags and experiments across your different business units, 14 | product lines, and applications. 15 | 16 | ## Example Usage 17 | 18 | ```hcl-terraform 19 | resource "split_workspace" "foobar" { 20 | name = "my_new_workspace" 21 | require_title_comments = true 22 | } 23 | ``` 24 | 25 | ## Argument Reference 26 | 27 | The following arguments are supported: 28 | 29 | * `name` - (Required) `` Name of the workspace. 30 | * `require_title_comments` - (Optional) `` Require title and comments for splits, segment, and metric changes. 31 | 32 | ## Attributes Reference 33 | 34 | The following attributes are exported: 35 | 36 | n/a 37 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/davidji99/terraform-provider-split 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.3 6 | 7 | require ( 8 | github.com/davidji99/simpleresty v0.4.2 9 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 10 | ) 11 | 12 | require ( 13 | github.com/ProtonMail/go-crypto v1.1.6 // indirect 14 | github.com/agext/levenshtein v1.2.2 // indirect 15 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 16 | github.com/cloudflare/circl v1.6.0 // indirect 17 | github.com/davidji99/go-querystring v1.0.2 // indirect 18 | github.com/fatih/color v1.16.0 // indirect 19 | github.com/go-resty/resty/v2 v2.16.5 // indirect 20 | github.com/golang/protobuf v1.5.4 // indirect 21 | github.com/google/go-cmp v0.7.0 // indirect 22 | github.com/hashicorp/errwrap v1.0.0 // indirect 23 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 24 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 25 | github.com/hashicorp/go-cty v1.5.0 // indirect 26 | github.com/hashicorp/go-hclog v1.6.3 // indirect 27 | github.com/hashicorp/go-multierror v1.1.1 // indirect 28 | github.com/hashicorp/go-plugin v1.6.3 // indirect 29 | github.com/hashicorp/go-retryablehttp v0.7.7 // indirect 30 | github.com/hashicorp/go-uuid v1.0.3 // indirect 31 | github.com/hashicorp/go-version v1.7.0 // indirect 32 | github.com/hashicorp/hc-install v0.9.2 // indirect 33 | github.com/hashicorp/hcl/v2 v2.23.0 // indirect 34 | github.com/hashicorp/logutils v1.0.0 // indirect 35 | github.com/hashicorp/terraform-exec v0.23.0 // indirect 36 | github.com/hashicorp/terraform-json v0.25.0 // indirect 37 | github.com/hashicorp/terraform-plugin-go v0.27.0 // indirect 38 | github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect 39 | github.com/hashicorp/terraform-registry-address v0.2.5 // indirect 40 | github.com/hashicorp/terraform-svchost v0.1.1 // indirect 41 | github.com/hashicorp/yamux v0.1.1 // indirect 42 | github.com/mattn/go-colorable v0.1.13 // indirect 43 | github.com/mattn/go-isatty v0.0.20 // indirect 44 | github.com/mitchellh/copystructure v1.2.0 // indirect 45 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 46 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 47 | github.com/mitchellh/mapstructure v1.5.0 // indirect 48 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 49 | github.com/oklog/run v1.0.0 // indirect 50 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 51 | github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect 52 | github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect 53 | github.com/zclconf/go-cty v1.16.2 // indirect 54 | golang.org/x/crypto v0.38.0 // indirect 55 | golang.org/x/mod v0.24.0 // indirect 56 | golang.org/x/net v0.40.0 // indirect 57 | golang.org/x/oauth2 v0.30.0 // indirect 58 | golang.org/x/sync v0.14.0 // indirect 59 | golang.org/x/sys v0.33.0 // indirect 60 | golang.org/x/text v0.25.0 // indirect 61 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect 62 | google.golang.org/appengine v1.6.8 // indirect 63 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect 64 | google.golang.org/grpc v1.72.1 // indirect 65 | google.golang.org/protobuf v1.36.6 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /helper/test/config.go: -------------------------------------------------------------------------------- 1 | package test 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 8 | ) 9 | 10 | type TestConfigKey int 11 | 12 | const ( 13 | TestConfigSplitAPIKey TestConfigKey = iota 14 | TestConfigSplitTrafficTypeName 15 | TestConfigSplitWorkspaceID 16 | TestConfigSplitWorkspaceName 17 | TestConfigSplitEnvironmentID 18 | TestConfigSplitTrafficTypeID 19 | TestConfigSplitUserEmail 20 | TestConfigAcceptanceTestKey 21 | ) 22 | 23 | var testConfigKeyToEnvName = map[TestConfigKey]string{ 24 | TestConfigSplitAPIKey: "SPLIT_API_KEY", 25 | TestConfigSplitTrafficTypeName: "SPLIT_TRAFFIC_TYPE_NAME", 26 | TestConfigSplitTrafficTypeID: "SPLIT_TRAFFIC_TYPE_ID", 27 | TestConfigSplitWorkspaceID: "SPLIT_WORKSPACE_ID", 28 | TestConfigSplitWorkspaceName: "SPLIT_WORKSPACE_NAME", 29 | TestConfigSplitEnvironmentID: "SPLIT_ENVIRONMENT_ID", 30 | TestConfigSplitUserEmail: "SPLIT_USER_EMAIL", 31 | TestConfigAcceptanceTestKey: resource.EnvTfAcc, 32 | } 33 | 34 | func (k TestConfigKey) String() (name string) { 35 | if val, ok := testConfigKeyToEnvName[k]; ok { 36 | name = val 37 | } 38 | return 39 | } 40 | 41 | type TestConfig struct{} 42 | 43 | func NewTestConfig() *TestConfig { 44 | return &TestConfig{} 45 | } 46 | 47 | func (t *TestConfig) Get(keys ...TestConfigKey) (val string) { 48 | for _, key := range keys { 49 | val = os.Getenv(key.String()) 50 | if val != "" { 51 | break 52 | } 53 | } 54 | return 55 | } 56 | 57 | func (t *TestConfig) GetOrSkip(testing *testing.T, keys ...TestConfigKey) (val string) { 58 | t.SkipUnlessAccTest(testing) 59 | val = t.Get(keys...) 60 | if val == "" { 61 | testing.Skipf("skipping test: config %v not set", keys) 62 | } 63 | return 64 | } 65 | 66 | func (t *TestConfig) GetOrAbort(testing *testing.T, keys ...TestConfigKey) (val string) { 67 | t.SkipUnlessAccTest(testing) 68 | val = t.Get(keys...) 69 | if val == "" { 70 | testing.Fatalf("stopping test: config %v must be set", keys) 71 | } 72 | return 73 | } 74 | 75 | func (t *TestConfig) SkipUnlessAccTest(testing *testing.T) { 76 | val := t.Get(TestConfigAcceptanceTestKey) 77 | if val == "" { 78 | testing.Skipf("Acceptance tests skipped unless env '%s' set", TestConfigAcceptanceTestKey.String()) 79 | } 80 | } 81 | 82 | func (t *TestConfig) GetWorkspaceIDorSkip(testing *testing.T) (val string) { 83 | return t.GetOrSkip(testing, TestConfigSplitWorkspaceID) 84 | } 85 | 86 | func (t *TestConfig) GetEnvironmentIDorSkip(testing *testing.T) (val string) { 87 | return t.GetOrSkip(testing, TestConfigSplitEnvironmentID) 88 | } 89 | 90 | func (t *TestConfig) GetTrafficTypeIDorSkip(testing *testing.T) (val string) { 91 | return t.GetOrSkip(testing, TestConfigSplitTrafficTypeID) 92 | } 93 | 94 | func (t *TestConfig) GetWorkspaceNameorSkip(testing *testing.T) (val string) { 95 | return t.GetOrSkip(testing, TestConfigSplitWorkspaceName) 96 | } 97 | 98 | func (t *TestConfig) GetTrafficTypeNameorSkip(testing *testing.T) (val string) { 99 | return t.GetOrSkip(testing, TestConfigSplitTrafficTypeName) 100 | } 101 | 102 | func (t *TestConfig) GetUserEmailorSkip(testing *testing.T) (val string) { 103 | return t.GetOrSkip(testing, TestConfigSplitUserEmail) 104 | } 105 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/davidji99/terraform-provider-split/split" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 6 | ) 7 | 8 | func main() { 9 | plugin.Serve(&plugin.ServeOpts{ProviderFunc: split.New}) 10 | } 11 | -------------------------------------------------------------------------------- /scripts/build-release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | os=$(uname) 4 | PROVIDER="split" 5 | VERSION="v$(go run ${PROVIDER}/version.go)" 6 | 7 | if [ "$(command -v goreleaser)" = "" ]; then 8 | echo "goreleaser is not installed. Please visit https://goreleaser.com/install/ for more information." 9 | exit 1 10 | fi 11 | 12 | echo "Pulling down latest from origin" 13 | git pull origin master 14 | 15 | echo "Switching to master branch" 16 | git checkout master 17 | 18 | echo "Checking if master branch is clean" 19 | if [ "$(git status --porcelain)" != "" ]; then 20 | echo "branch is not clean. please add/commit or stage any changes first" 21 | exit 1 22 | fi 23 | 24 | echo "Checking if the tag already exists" 25 | if git show ${VERSION} >> /dev/null 2>&1 || false ; then 26 | echo "tag ${VERSION} already exists. Did you forget to bump the version in version.go file?" 27 | exit 1 28 | fi 29 | 30 | echo "Checking if .goreleaser.yml is valid" 31 | goreleaser check 32 | 33 | echo "Creating new release tag ${VERSION}" 34 | git tag -a "${VERSION}" -m "Release ${VERSION}" 35 | 36 | echo "Pushing new release tag ${VERSION} to upstream origin" 37 | git push origin "${VERSION}" -------------------------------------------------------------------------------- /scripts/gofmtcheck.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Check gofmt 4 | echo "==> Checking that code complies with gofmt requirements..." 5 | gofmt_files=$(gofmt -l `find . -name '*.go' | grep -v vendor`) 6 | if [[ -n ${gofmt_files} ]]; then 7 | echo 'gofmt needs running on the following files:' 8 | echo "${gofmt_files}" 9 | echo "You can use the command: \`make fmt\` to reformat code." 10 | exit 1 11 | fi 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /split/config.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/davidji99/terraform-provider-split/api" 8 | "github.com/davidji99/terraform-provider-split/version" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | var ( 13 | UserAgent = fmt.Sprintf("terraform-provider-split/v%s", version.ProviderVersion) 14 | ) 15 | 16 | type Config struct { 17 | API *api.Client 18 | Headers map[string]string 19 | 20 | apiKey string 21 | apiBaseURL string 22 | 23 | clientTimeout int 24 | 25 | RemoveEnvFromStateOnly bool 26 | } 27 | 28 | func NewConfig() *Config { 29 | return &Config{ 30 | RemoveEnvFromStateOnly: false, 31 | } 32 | } 33 | 34 | func (c *Config) initializeAPI() error { 35 | api, clientInitErr := api.New(api.APIKey(c.apiKey), 36 | api.APIBaseURL(c.apiBaseURL), api.UserAgent(UserAgent), 37 | api.CustomHTTPHeaders(c.Headers), api.ClientTimeout(c.clientTimeout)) 38 | if clientInitErr != nil { 39 | return clientInitErr 40 | } 41 | 42 | c.API = api 43 | 44 | log.Printf("[INFO] Split Client configured") 45 | 46 | return nil 47 | } 48 | 49 | func (c *Config) applySchema(d *schema.ResourceData) (err error) { 50 | if v, ok := d.GetOk("headers"); ok { 51 | headersRaw := v.(map[string]interface{}) 52 | h := make(map[string]string) 53 | 54 | for k, v := range headersRaw { 55 | h[k] = fmt.Sprintf("%v", v) 56 | } 57 | 58 | c.Headers = h 59 | } 60 | 61 | if v, ok := d.GetOk("base_url"); ok { 62 | vs := v.(string) 63 | c.apiBaseURL = vs 64 | } 65 | 66 | if v, ok := d.GetOk("remove_environment_from_state_only"); ok { 67 | c.RemoveEnvFromStateOnly = v.(bool) 68 | } 69 | 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /split/data_source_split_environment.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 8 | ) 9 | 10 | func dataSourceSplitEnvironment() *schema.Resource { 11 | return &schema.Resource{ 12 | ReadContext: dataSourceSplitEnvironmentRead, 13 | Schema: map[string]*schema.Schema{ 14 | "workspace_id": { 15 | Type: schema.TypeString, 16 | Required: true, 17 | ValidateFunc: validation.IsUUID, 18 | }, 19 | 20 | "name": { 21 | Type: schema.TypeString, 22 | Required: true, 23 | }, 24 | 25 | "production": { 26 | Type: schema.TypeBool, 27 | Computed: true, 28 | }, 29 | }, 30 | } 31 | } 32 | 33 | func dataSourceSplitEnvironmentRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 34 | client := m.(*Config).API 35 | 36 | name := d.Get("name").(string) 37 | workspaceID := d.Get("workspace_id").(string) 38 | 39 | env, _, findErr := client.Environments.FindByName(workspaceID, name) 40 | if findErr != nil { 41 | return diag.FromErr(findErr) 42 | } 43 | 44 | d.SetId(env.GetID()) 45 | d.Set("name", env.GetName()) 46 | d.Set("production", env.GetProduction()) 47 | 48 | return nil 49 | } 50 | -------------------------------------------------------------------------------- /split/data_source_split_environment_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccDatasourceSplitEnvironment_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 13 | 14 | resource.Test(t, resource.TestCase{ 15 | PreCheck: func() { 16 | testAccPreCheck(t) 17 | }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitEnvironmentDataSource_Basic(workspaceID, name), 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttr( 24 | "data.split_environment.foobar", "workspace_id", workspaceID), 25 | resource.TestCheckResourceAttr( 26 | "data.split_environment.foobar", "name", name), 27 | resource.TestCheckResourceAttr( 28 | "data.split_environment.foobar", "production", "false"), 29 | ), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccCheckSplitEnvironmentDataSource_Basic(workspaceID, envName string) string { 36 | return fmt.Sprintf(` 37 | provider "split" { 38 | remove_environment_from_state_only = true 39 | } 40 | 41 | resource "split_environment" "foobar" { 42 | workspace_id = "%[1]s" 43 | name = "%[2]s" 44 | } 45 | 46 | data "split_environment" "foobar" { 47 | workspace_id = "%[1]s" 48 | name = split_environment.foobar.name 49 | } 50 | `, workspaceID, envName) 51 | } 52 | -------------------------------------------------------------------------------- /split/data_source_split_traffic_type.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 8 | ) 9 | 10 | func dataSourceSplitTrafficType() *schema.Resource { 11 | return &schema.Resource{ 12 | ReadContext: dataSourceSplitTrafficTypeRead, 13 | Schema: map[string]*schema.Schema{ 14 | "workspace_id": { 15 | Type: schema.TypeString, 16 | Required: true, 17 | ValidateFunc: validation.IsUUID, 18 | }, 19 | 20 | "name": { 21 | Type: schema.TypeString, 22 | Required: true, 23 | }, 24 | 25 | "type": { 26 | Type: schema.TypeString, 27 | Computed: true, 28 | }, 29 | 30 | "display_attribute_id": { 31 | Type: schema.TypeString, 32 | Computed: true, 33 | }, 34 | 35 | "workspace_name": { 36 | Type: schema.TypeString, 37 | Computed: true, 38 | }, 39 | }, 40 | } 41 | } 42 | 43 | func dataSourceSplitTrafficTypeRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 44 | client := m.(*Config).API 45 | 46 | name := d.Get("name").(string) 47 | workspaceID := d.Get("workspace_id").(string) 48 | 49 | trafficType, _, findErr := client.TrafficTypes.FindByName(workspaceID, name) 50 | if findErr != nil { 51 | return diag.FromErr(findErr) 52 | } 53 | 54 | d.SetId(trafficType.GetID()) 55 | d.Set("workspace_id", trafficType.GetWorkspace().GetID()) 56 | d.Set("name", trafficType.GetName()) 57 | d.Set("type", trafficType.GetType()) 58 | d.Set("display_attribute_id", trafficType.GetDisplayAttributeID()) 59 | d.Set("workspace_name", trafficType.GetWorkspace().GetName()) 60 | 61 | return nil 62 | } 63 | -------------------------------------------------------------------------------- /split/data_source_split_traffic_type_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 6 | "testing" 7 | ) 8 | 9 | func TestAccDatasourceSplitTrafficType_Basic(t *testing.T) { 10 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 11 | name := testAccConfig.GetTrafficTypeNameorSkip(t) 12 | 13 | resource.Test(t, resource.TestCase{ 14 | PreCheck: func() { 15 | testAccPreCheck(t) 16 | }, 17 | ProviderFactories: testAccProviderFactories, 18 | Steps: []resource.TestStep{ 19 | { 20 | Config: testAccCheckSplitTrafficTypeDataSource_Basic(workspaceID, name), 21 | Check: resource.ComposeTestCheckFunc( 22 | resource.TestCheckResourceAttr( 23 | "data.split_traffic_type.foobar", "workspace_id", workspaceID), 24 | resource.TestCheckResourceAttr( 25 | "data.split_traffic_type.foobar", "name", name), 26 | resource.TestCheckResourceAttrSet( 27 | "data.split_traffic_type.foobar", "type"), 28 | resource.TestCheckResourceAttrSet( 29 | "data.split_traffic_type.foobar", "display_attribute_id"), 30 | resource.TestCheckResourceAttrSet( 31 | "data.split_traffic_type.foobar", "workspace_name"), 32 | ), 33 | }, 34 | }, 35 | }) 36 | } 37 | 38 | func testAccCheckSplitTrafficTypeDataSource_Basic(workspaceID, name string) string { 39 | return fmt.Sprintf(` 40 | data "split_traffic_type" "foobar" { 41 | workspace_id = "%s" 42 | name = "%s" 43 | } 44 | `, workspaceID, name) 45 | } 46 | -------------------------------------------------------------------------------- /split/data_source_split_workspace.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceSplitWorkspace() *schema.Resource { 10 | return &schema.Resource{ 11 | ReadContext: dataSourceSplitWorkspaceRead, 12 | Schema: map[string]*schema.Schema{ 13 | "name": { 14 | Type: schema.TypeString, 15 | Required: true, 16 | }, 17 | 18 | "type": { 19 | Type: schema.TypeString, 20 | Computed: true, 21 | }, 22 | 23 | "requires_title_and_comments": { 24 | Type: schema.TypeBool, 25 | Computed: true, 26 | }, 27 | }, 28 | } 29 | } 30 | 31 | func dataSourceSplitWorkspaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { 32 | client := m.(*Config).API 33 | 34 | name := d.Get("name").(string) 35 | 36 | workspace, _, findErr := client.Workspaces.FindByName(name) 37 | if findErr != nil { 38 | return diag.FromErr(findErr) 39 | } 40 | 41 | d.SetId(workspace.GetID()) 42 | d.Set("name", workspace.GetName()) 43 | d.Set("type", workspace.GetType()) 44 | d.Set("requires_title_and_comments", workspace.GetRequiresTitleAndComments()) 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /split/data_source_split_workspace_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 6 | "testing" 7 | ) 8 | 9 | func TestAccDatasourceSplitWorkspace_Basic(t *testing.T) { 10 | name := testAccConfig.GetWorkspaceNameorSkip(t) 11 | 12 | resource.Test(t, resource.TestCase{ 13 | PreCheck: func() { 14 | testAccPreCheck(t) 15 | }, 16 | ProviderFactories: testAccProviderFactories, 17 | Steps: []resource.TestStep{ 18 | { 19 | Config: testAccCheckSplitWorkspaceDataSource_Basic(name), 20 | Check: resource.ComposeTestCheckFunc( 21 | resource.TestCheckResourceAttr( 22 | "data.split_workspace.foobar", "name", name), 23 | resource.TestCheckResourceAttr( 24 | "data.split_workspace.foobar", "type", "workspace"), 25 | resource.TestCheckResourceAttr( 26 | "data.split_workspace.foobar", "requires_title_and_comments", "false"), 27 | ), 28 | }, 29 | }, 30 | }) 31 | } 32 | 33 | func testAccCheckSplitWorkspaceDataSource_Basic(name string) string { 34 | return fmt.Sprintf(` 35 | data "split_workspace" "foobar" { 36 | name = "%s" 37 | } 38 | `, name) 39 | } 40 | -------------------------------------------------------------------------------- /split/helper.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | // getWorkspaceID extracts the workspace ID attribute generically from a Split resource. 11 | func getWorkspaceID(d *schema.ResourceData) string { 12 | var workspaceID string 13 | if v, ok := d.GetOk("workspace_id"); ok { 14 | vs := v.(string) 15 | log.Printf("[DEBUG] workspace_id: %s", vs) 16 | workspaceID = vs 17 | } 18 | 19 | return workspaceID 20 | } 21 | 22 | // getTrafficTypeID extracts the traffic type ID attribute generically from a Split resource. 23 | func getTrafficTypeID(d *schema.ResourceData) string { 24 | var trafficTypeID string 25 | if v, ok := d.GetOk("traffic_type_id"); ok { 26 | vs := v.(string) 27 | log.Printf("[DEBUG] traffic_type_id: %s", vs) 28 | trafficTypeID = vs 29 | } 30 | 31 | return trafficTypeID 32 | } 33 | 34 | // getEnvironmentID extracts the environment ID attribute generically from a Split resource. 35 | func getEnvironmentID(d *schema.ResourceData) string { 36 | var envID string 37 | if v, ok := d.GetOk("environment_id"); ok { 38 | vs := v.(string) 39 | log.Printf("[DEBUG] environment_id: %s", vs) 40 | envID = vs 41 | } 42 | 43 | return envID 44 | } 45 | 46 | // getSplitName extracts the split name attribute generically from a Split resource. 47 | func getSplitName(d *schema.ResourceData) string { 48 | var n string 49 | if v, ok := d.GetOk("split_name"); ok { 50 | vs := v.(string) 51 | log.Printf("[DEBUG] split_name: %s", vs) 52 | n = vs 53 | } 54 | 55 | return n 56 | } 57 | 58 | func parseCompositeID(id string, numOfSplits int) ([]string, error) { 59 | parts := strings.SplitN(id, ":", numOfSplits) 60 | 61 | if len(parts) != numOfSplits { 62 | return nil, fmt.Errorf("error: import composite ID requires %d parts separated by a colon (x:y). "+ 63 | "Please check resource documentation for more information", numOfSplits) 64 | } 65 | return parts, nil 66 | } 67 | 68 | func Truncate(text string, width int) string { 69 | if width < 0 { 70 | return "" 71 | } 72 | 73 | r := []rune(text) 74 | trunc := r[:width] 75 | return string(trunc) + "..." 76 | } 77 | -------------------------------------------------------------------------------- /split/import_split_api_key_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "regexp" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitApiKey_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 14 | envName := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { 18 | testAccPreCheck(t) 19 | }, 20 | ProviderFactories: testAccProviderFactories, 21 | Steps: []resource.TestStep{ 22 | { 23 | Config: testAccCheckSplitApiKey_basic(workspaceID, name, "client_side", envName), 24 | }, 25 | { 26 | ResourceName: "split_api_key.foobar", 27 | ImportState: true, 28 | ImportStateVerify: true, 29 | ExpectError: regexp.MustCompile(`not possible to import existing API keys due to API limitations`), 30 | }, 31 | }, 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /split/import_split_environment_segment_keys_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitEnvironmentSegmentKeys_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | envName := fmt.Sprintf("tftest-env-%s", acctest.RandString(3)) 14 | trafficTypeName := fmt.Sprintf("tftest-tt-%s", acctest.RandString(8)) 15 | segmentName := fmt.Sprintf("tftest-seg-%s", acctest.RandString(8)) 16 | 17 | resource.Test(t, resource.TestCase{ 18 | PreCheck: func() { 19 | testAccPreCheck(t) 20 | }, 21 | ProviderFactories: testAccProviderFactories, 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: testAccCheckSplitEnvironmentSegmentKeys_basic(workspaceID, envName, trafficTypeName, 25 | segmentName, true), 26 | }, 27 | { 28 | ResourceName: "split_environment_segment_keys.foobar", 29 | ImportState: true, 30 | ImportStateVerify: true, 31 | ImportStateIdFunc: testAccSplitEnvironmentSegmentKeysImportStateIDFunc("split_environment_segment_keys.foobar"), 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | func testAccSplitEnvironmentSegmentKeysImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 38 | return func(s *terraform.State) (string, error) { 39 | rs, ok := s.RootModule().Resources[resourceName] 40 | if !ok { 41 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 42 | } 43 | 44 | return fmt.Sprintf("%s:%s", rs.Primary.Attributes["environment_id"], rs.Primary.Attributes["segment_name"]), nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /split/import_split_environment_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitEnvironment_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 14 | 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { 17 | testAccPreCheck(t) 18 | }, 19 | ProviderFactories: testAccProviderFactories, 20 | Steps: []resource.TestStep{ 21 | { 22 | Config: testAccCheckSplitEnvironment_basic(workspaceID, name, "true"), 23 | }, 24 | { 25 | ResourceName: "split_environment.foobar", 26 | ImportState: true, 27 | ImportStateVerify: true, 28 | ImportStateIdFunc: testAccSplitEnvironmentImportStateIDFunc("split_environment.foobar"), 29 | }, 30 | }, 31 | }) 32 | } 33 | 34 | func testAccSplitEnvironmentImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 35 | return func(s *terraform.State) (string, error) { 36 | rs, ok := s.RootModule().Resources[resourceName] 37 | if !ok { 38 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 39 | } 40 | 41 | return fmt.Sprintf("%s:%s", rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["id"]), nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /split/import_split_group_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitGroup_importBasic(t *testing.T) { 11 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 12 | 13 | resource.Test(t, resource.TestCase{ 14 | PreCheck: func() { 15 | testAccPreCheck(t) 16 | }, 17 | ProviderFactories: testAccProviderFactories, 18 | Steps: []resource.TestStep{ 19 | { 20 | Config: testAccCheckSplitGroup_basic(name, "created from terraform"), 21 | }, 22 | { 23 | ResourceName: "split_group.foobar", 24 | ImportState: true, 25 | ImportStateVerify: true, 26 | }, 27 | }, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /split/import_split_segment_environment_association_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitSegmentEnvironmentAssociation_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | envName := fmt.Sprintf("tftest-env-%s", acctest.RandString(3)) 14 | trafficTypeName := fmt.Sprintf("tftest-tt-%s", acctest.RandString(8)) 15 | segmentName := fmt.Sprintf("tftest-seg-%s", acctest.RandString(8)) 16 | 17 | resource.Test(t, resource.TestCase{ 18 | PreCheck: func() { 19 | testAccPreCheck(t) 20 | }, 21 | ProviderFactories: testAccProviderFactories, 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: testAccCheckSplitSegmentEnvironmentAssociation_basic(workspaceID, 25 | envName, trafficTypeName, segmentName), 26 | }, 27 | { 28 | ResourceName: "split_segment_environment_association.foobar", 29 | ImportState: true, 30 | ImportStateVerify: true, 31 | ImportStateIdFunc: testAccSplitSegmentEnvironmentAssociationImportStateIDFunc( 32 | "split_segment_environment_association.foobar"), 33 | }, 34 | }, 35 | }) 36 | } 37 | 38 | func testAccSplitSegmentEnvironmentAssociationImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 39 | return func(s *terraform.State) (string, error) { 40 | rs, ok := s.RootModule().Resources[resourceName] 41 | if !ok { 42 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 43 | } 44 | 45 | return fmt.Sprintf("%s:%s:%s", rs.Primary.Attributes["workspace_id"], 46 | rs.Primary.Attributes["environment_id"], rs.Primary.Attributes["segment_name"]), nil 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /split/import_split_segment_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitSegment_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | trafficTypeID := testAccConfig.GetTrafficTypeNameorSkip(t) 14 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { 18 | testAccPreCheck(t) 19 | }, 20 | ProviderFactories: testAccProviderFactories, 21 | Steps: []resource.TestStep{ 22 | { 23 | Config: testAccCheckSplitSegment_basic(workspaceID, trafficTypeID, name, "created from Terraform"), 24 | }, 25 | { 26 | ResourceName: "split_segment.foobar", 27 | ImportState: true, 28 | ImportStateVerify: true, 29 | ImportStateIdFunc: testAccSplitSegmentImportStateIDFunc("split_segment.foobar"), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccSplitSegmentImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 36 | return func(s *terraform.State) (string, error) { 37 | rs, ok := s.RootModule().Resources[resourceName] 38 | if !ok { 39 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 40 | } 41 | 42 | return fmt.Sprintf("%s:%s", rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["name"]), nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /split/import_split_split_definition_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 10 | ) 11 | 12 | func TestAccSplitSplitDefinition_importBasic(t *testing.T) { 13 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 14 | envID := testAccConfig.GetEnvironmentIDorSkip(t) 15 | trafficTypeID := testAccConfig.GetTrafficTypeIDorSkip(t) 16 | trafficTypeName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(10)) 17 | splitName := fmt.Sprintf("s-tftest-%s", acctest.RandString(10)) 18 | 19 | resource.Test(t, resource.TestCase{ 20 | PreCheck: func() { 21 | testAccPreCheck(t) 22 | }, 23 | ProviderFactories: testAccProviderFactories, 24 | Steps: []resource.TestStep{ 25 | { 26 | Config: testAccCheckSplitSplitDefinition_multipleMatchers(workspaceID, trafficTypeName, splitName, 27 | "my split description", envID, trafficTypeID), 28 | }, 29 | { 30 | ResourceName: "split_split_definition.foobar", 31 | ImportState: true, 32 | ImportStateVerify: true, 33 | ImportStateIdFunc: testAccSplitSplitDefinitionImportStateIDFunc("split_split_definition.foobar"), 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | func testAccSplitSplitDefinitionImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 40 | return func(s *terraform.State) (string, error) { 41 | rs, ok := s.RootModule().Resources[resourceName] 42 | if !ok { 43 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 44 | } 45 | 46 | return fmt.Sprintf("%s:%s:%s", rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["split_name"], 47 | rs.Primary.Attributes["environment_id"]), nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /split/import_split_split_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitSplit_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | trafficTypeName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(10)) 14 | splitName := fmt.Sprintf("s-tftest-%s", acctest.RandString(10)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { 18 | testAccPreCheck(t) 19 | }, 20 | ProviderFactories: testAccProviderFactories, 21 | Steps: []resource.TestStep{ 22 | { 23 | Config: testAccCheckSplitSplit_basic(workspaceID, trafficTypeName, splitName, "my split description"), 24 | }, 25 | { 26 | ResourceName: "split_split.foobar", 27 | ImportState: true, 28 | ImportStateVerify: true, 29 | ImportStateIdFunc: testAccSplitSplitImportStateIDFunc("split_split.foobar"), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccSplitSplitImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 36 | return func(s *terraform.State) (string, error) { 37 | rs, ok := s.RootModule().Resources[resourceName] 38 | if !ok { 39 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 40 | } 41 | 42 | return fmt.Sprintf("%s:%s", rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["id"]), nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /split/import_split_traffic_type_attribute_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitTrafficTypeAttribute_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | ttName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(8)) 14 | attrIdentifier := acctest.RandString(8) 15 | attrName := fmt.Sprintf("attr-tftest-%s", acctest.RandString(8)) 16 | 17 | resource.Test(t, resource.TestCase{ 18 | PreCheck: func() { 19 | testAccPreCheck(t) 20 | }, 21 | ProviderFactories: testAccProviderFactories, 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: testAccCheckSplitTrafficTypeAttribute_basic(workspaceID, attrIdentifier, ttName, attrName), 25 | }, 26 | { 27 | ResourceName: "split_traffic_type_attribute.foobar", 28 | ImportState: true, 29 | ImportStateVerify: true, 30 | ImportStateIdFunc: testAccSplitAttributeImportStateIDFunc("split_traffic_type_attribute.foobar"), 31 | }, 32 | }, 33 | }) 34 | } 35 | 36 | func testAccSplitAttributeImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 37 | return func(s *terraform.State) (string, error) { 38 | rs, ok := s.RootModule().Resources[resourceName] 39 | if !ok { 40 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 41 | } 42 | 43 | return fmt.Sprintf("%s:%s:%s", rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["traffic_type_id"], 44 | rs.Primary.Attributes["id"]), nil 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /split/import_split_traffic_type_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitTrafficType_importBasic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | name := fmt.Sprintf("tt-tftest-%s", acctest.RandString(10)) 14 | 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { 17 | testAccPreCheck(t) 18 | }, 19 | ProviderFactories: testAccProviderFactories, 20 | Steps: []resource.TestStep{ 21 | { 22 | Config: testAccCheckSplitTrafficType_basic(workspaceID, name), 23 | }, 24 | { 25 | ResourceName: "split_traffic_type.foobar", 26 | ImportState: true, 27 | ImportStateVerify: true, 28 | ImportStateIdFunc: testAccSplitTrafficTypeImportStateIDFunc("split_traffic_type.foobar"), 29 | }, 30 | }, 31 | }) 32 | } 33 | 34 | func testAccSplitTrafficTypeImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 35 | return func(s *terraform.State) (string, error) { 36 | rs, ok := s.RootModule().Resources[resourceName] 37 | if !ok { 38 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 39 | } 40 | 41 | return fmt.Sprintf("%s:%s", rs.Primary.Attributes["workspace_id"], rs.Primary.Attributes["id"]), nil 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /split/import_split_user_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitUser_importBasic(t *testing.T) { 12 | email := testAccConfig.GetUserEmailorSkip(t) 13 | emailSplit := strings.Split(email, "@") 14 | emailFormatted := fmt.Sprintf("%s+%s@%s", emailSplit[0], acctest.RandString(8), emailSplit[1]) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { 18 | testAccPreCheck(t) 19 | }, 20 | ProviderFactories: testAccProviderFactories, 21 | Steps: []resource.TestStep{ 22 | { 23 | Config: testAccCheckSplitUser_basic(emailFormatted), 24 | }, 25 | { 26 | ResourceName: "split_user.foobar", 27 | ImportState: true, 28 | ImportStateVerify: true, 29 | }, 30 | }, 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /split/import_split_workspace_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitWorkspace_importBasic(t *testing.T) { 12 | name := fmt.Sprintf("w-tftest-%s", acctest.RandString(10)) 13 | 14 | resource.Test(t, resource.TestCase{ 15 | PreCheck: func() { 16 | testAccPreCheck(t) 17 | }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitWorkspace_basic(name, "true"), 22 | }, 23 | { 24 | ResourceName: "split_workspace.foobar", 25 | ImportState: true, 26 | ImportStateVerify: true, 27 | ImportStateIdFunc: testAccSplitWorkspaceImportStateIDFunc("split_workspace.foobar"), 28 | }, 29 | }, 30 | }) 31 | } 32 | 33 | func testAccSplitWorkspaceImportStateIDFunc(resourceName string) resource.ImportStateIdFunc { 34 | return func(s *terraform.State) (string, error) { 35 | rs, ok := s.RootModule().Resources[resourceName] 36 | if !ok { 37 | return "", fmt.Errorf("[ERROR] Not found: %s", resourceName) 38 | } 39 | 40 | return rs.Primary.Attributes["name"], nil 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /split/provider.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "log" 6 | 7 | "github.com/davidji99/terraform-provider-split/api" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | func New() *schema.Provider { 13 | return &schema.Provider{ 14 | Schema: map[string]*schema.Schema{ 15 | "api_key": { 16 | Type: schema.TypeString, 17 | Optional: true, 18 | DefaultFunc: schema.EnvDefaultFunc("SPLIT_API_KEY", nil), 19 | }, 20 | 21 | "base_url": { 22 | Type: schema.TypeString, 23 | Optional: true, 24 | DefaultFunc: schema.EnvDefaultFunc("SPLIT_API_URL", api.DefaultAPIBaseURL), 25 | }, 26 | 27 | "headers": { 28 | Type: schema.TypeMap, 29 | Elem: schema.TypeString, 30 | Optional: true, 31 | }, 32 | 33 | "client_timeout": { 34 | Type: schema.TypeInt, 35 | Optional: true, 36 | Default: 300, // 5 min 37 | }, 38 | 39 | "remove_environment_from_state_only": { 40 | Type: schema.TypeBool, 41 | Optional: true, 42 | Default: false, 43 | }, 44 | }, 45 | 46 | DataSourcesMap: map[string]*schema.Resource{ 47 | "split_environment": dataSourceSplitEnvironment(), 48 | "split_traffic_type": dataSourceSplitTrafficType(), 49 | "split_workspace": dataSourceSplitWorkspace(), 50 | }, 51 | 52 | ResourcesMap: map[string]*schema.Resource{ 53 | "split_api_key": resourceSplitApiKey(), 54 | "split_environment": resourceSplitEnvironment(), 55 | "split_environment_segment_keys": resourceSplitEnvironmentSegmentKeys(), 56 | "split_group": resourceSplitGroup(), 57 | "split_segment": resourceSplitSegment(), 58 | "split_segment_environment_association": resourceSplitSegmentEnvironmentAssociation(), 59 | "split_split": resourceSplitSplit(), 60 | "split_split_definition": resourceSplitSplitDefinition(), 61 | "split_traffic_type": resourceSplitTrafficType(), 62 | "split_traffic_type_attribute": resourceSplitTrafficTypeAttribute(), 63 | "split_user": resourceSplitUser(), 64 | "split_workspace": resourceSplitWorkspace(), 65 | }, 66 | 67 | ConfigureContextFunc: providerConfigure, 68 | } 69 | } 70 | 71 | func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 72 | log.Println("[INFO] Initializing Split Provider") 73 | 74 | var diags diag.Diagnostics 75 | 76 | config := NewConfig() 77 | 78 | if applySchemaErr := config.applySchema(d); applySchemaErr != nil { 79 | diags = append(diags, diag.Diagnostic{ 80 | Severity: diag.Error, 81 | Summary: "Unable to retrieve and set provider attributes", 82 | Detail: applySchemaErr.Error(), 83 | }) 84 | 85 | return nil, diags 86 | } 87 | 88 | if apiKey, ok := d.GetOk("api_key"); ok { 89 | config.apiKey = apiKey.(string) 90 | } 91 | 92 | if clientTimeout, ok := d.GetOk("client_timeout"); ok { 93 | config.clientTimeout = clientTimeout.(int) 94 | } 95 | 96 | if err := config.initializeAPI(); err != nil { 97 | diags = append(diags, diag.Diagnostic{ 98 | Severity: diag.Error, 99 | Summary: "Unable to initialize API client", 100 | Detail: err.Error(), 101 | }) 102 | 103 | return nil, diags 104 | } 105 | 106 | log.Printf("[DEBUG] Split Provider initialized") 107 | 108 | return config, diags 109 | } 110 | -------------------------------------------------------------------------------- /split/provider_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "testing" 5 | 6 | helper "github.com/davidji99/terraform-provider-split/helper/test" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 8 | ) 9 | 10 | var providers []*schema.Provider 11 | var testAccProviderFactories map[string]func() (*schema.Provider, error) 12 | var testAccProviders map[string]*schema.Provider 13 | var testAccProvider *schema.Provider 14 | var testAccConfig *helper.TestConfig 15 | 16 | func init() { 17 | testAccProvider = New() 18 | testAccProviders = map[string]*schema.Provider{ 19 | "split": testAccProvider, 20 | } 21 | testAccProviderFactories = testAccProviderFactoriesInit(providers, []string{"split"}) 22 | testAccConfig = helper.NewTestConfig() 23 | } 24 | 25 | func TestProvider(t *testing.T) { 26 | if err := New().InternalValidate(); err != nil { 27 | t.Fatalf("err: %s", err) 28 | } 29 | } 30 | 31 | func TestProvider_impl(t *testing.T) { 32 | var _ *schema.Provider = New() 33 | } 34 | 35 | func testAccPreCheck(t *testing.T) { 36 | testAccConfig.GetOrAbort(t, helper.TestConfigSplitAPIKey) 37 | } 38 | 39 | func testAccProviderFactoriesInit(providers []*schema.Provider, providerNames []string) map[string]func() (*schema.Provider, error) { 40 | var factories = make(map[string]func() (*schema.Provider, error), len(providerNames)) 41 | 42 | for _, name := range providerNames { 43 | p := New() 44 | 45 | factories[name] = func() (*schema.Provider, error) { 46 | return p, nil 47 | } 48 | 49 | if providers != nil { 50 | providers = append(providers, p) 51 | } 52 | } 53 | 54 | return factories 55 | } 56 | -------------------------------------------------------------------------------- /split/resource_split_api_key.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | "log" 11 | ) 12 | 13 | func resourceSplitApiKey() *schema.Resource { 14 | return &schema.Resource{ 15 | CreateContext: resourceSplitApiKeyCreate, 16 | ReadContext: resourceSplitApiKeyRead, 17 | DeleteContext: resourceSplitApiKeyDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: resourceSplitApiKeyImport, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "workspace_id": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | ForceNew: true, 28 | ValidateFunc: validation.IsUUID, 29 | }, 30 | 31 | "environment_ids": { 32 | Type: schema.TypeList, 33 | Required: true, 34 | ForceNew: true, 35 | MinItems: 1, 36 | Elem: &schema.Schema{ 37 | Type: schema.TypeString, 38 | ValidateFunc: validation.IsUUID, 39 | }, 40 | }, 41 | 42 | "name": { 43 | Type: schema.TypeString, 44 | Required: true, 45 | ForceNew: true, 46 | ValidateFunc: validation.StringLenBetween(1, 15), 47 | }, 48 | 49 | "type": { 50 | Type: schema.TypeString, 51 | Required: true, 52 | ForceNew: true, 53 | ValidateFunc: validation.StringInSlice(api.ValidApiKeyTypes, false), 54 | }, 55 | 56 | "roles": { 57 | Type: schema.TypeList, 58 | Optional: true, 59 | ForceNew: true, 60 | Elem: &schema.Schema{ 61 | Type: schema.TypeString, 62 | ValidateFunc: validation.StringInSlice(api.ValidApiKeyRoles, false), 63 | }, 64 | }, 65 | }, 66 | } 67 | } 68 | 69 | // Since there's no `GET https://api.split.io/internal/api/v2/apiKeys` endpoint, it is not possible to import existing keys. 70 | func resourceSplitApiKeyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 71 | return nil, fmt.Errorf("not possible to import existing API keys due to API limitations") 72 | } 73 | 74 | func resourceSplitApiKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 75 | var diags diag.Diagnostics 76 | client := meta.(*Config).API 77 | opts := &api.KeyRequest{} 78 | 79 | if v, ok := d.GetOk("workspace_id"); ok { 80 | vs := v.(string) 81 | log.Printf("[DEBUG] new api key workspace_id: %s", vs) 82 | opts.Workspace = &api.KeyWorkspaceRequest{ 83 | Type: "workspace", 84 | Id: vs, 85 | } 86 | } 87 | 88 | if v, ok := d.GetOk("name"); ok { 89 | vs := v.(string) 90 | opts.Name = vs 91 | log.Printf("[DEBUG] new api key name is : %v", vs) 92 | } 93 | 94 | if v, ok := d.GetOk("type"); ok { 95 | vs := v.(string) 96 | opts.KeyType = vs 97 | log.Printf("[DEBUG] new api key type is : %v", vs) 98 | } 99 | 100 | if v, ok := d.GetOk("roles"); ok { 101 | rolesListRaw := v.([]interface{}) 102 | roleList := make([]string, 0) 103 | for _, roleRaw := range rolesListRaw { 104 | roleList = append(roleList, roleRaw.(string)) 105 | } 106 | opts.Roles = roleList 107 | log.Printf("[DEBUG] new api key roles is : %v", roleList) 108 | } 109 | 110 | if v, ok := d.GetOk("environment_ids"); ok { 111 | envIdsListRaw := v.([]interface{}) 112 | envIdsMapList := make([]api.KeyEnvironmentRequest, 0) 113 | for _, envIdRaw := range envIdsListRaw { 114 | envIdsMapList = append(envIdsMapList, api.KeyEnvironmentRequest{ 115 | Type: "environment", 116 | Id: envIdRaw.(string), 117 | }) 118 | } 119 | opts.Environments = envIdsMapList 120 | log.Printf("[DEBUG] new api key environments is : %v", envIdsMapList) 121 | } 122 | 123 | log.Printf("[DEBUG] Creating new api key %v", opts.Name) 124 | 125 | apiKey, _, createErr := client.ApiKeys.Create(opts) 126 | if createErr != nil { 127 | diags = append(diags, diag.Diagnostic{ 128 | Severity: diag.Error, 129 | Summary: fmt.Sprintf("Unable to create new api key %v", opts.Name), 130 | Detail: createErr.Error(), 131 | }) 132 | return diags 133 | } 134 | 135 | log.Printf("[DEBUG] Created new api key %v", apiKey.GetName()) 136 | 137 | d.SetId(apiKey.GetKey()) 138 | 139 | return resourceSplitApiKeyRead(ctx, d, meta) 140 | } 141 | 142 | // Since there's no `GET https://api.split.io/internal/api/v2/apiKeys` endpoint, resourceSplitApiKeyRead will be a `noop`. 143 | func resourceSplitApiKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 144 | log.Printf("[WARN] can't refresh API key resource due to API limiitations") 145 | return nil 146 | } 147 | 148 | func resourceSplitApiKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 149 | client := meta.(*Config).API 150 | var diags diag.Diagnostics 151 | 152 | // For logging purposes, only use the first four digits of the key, 153 | apiKeyTrunc := Truncate(d.Id(), 4) 154 | 155 | log.Printf("[DEBUG] Deleting API key [%s]", apiKeyTrunc) 156 | _, deleteErr := client.ApiKeys.Delete(d.Id()) 157 | if deleteErr != nil { 158 | diags = append(diags, diag.Diagnostic{ 159 | Severity: diag.Error, 160 | Summary: fmt.Sprintf("unable to delete API key [%s]", apiKeyTrunc), 161 | Detail: deleteErr.Error(), 162 | }) 163 | return diags 164 | } 165 | 166 | log.Printf("[DEBUG] Deleted API key [%s]", apiKeyTrunc) 167 | 168 | d.SetId("") 169 | 170 | return diags 171 | } 172 | -------------------------------------------------------------------------------- /split/resource_split_api_key_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitApiKey_ClientSide_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 13 | envName := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 14 | 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProviderFactories: testAccProviderFactories, 18 | Steps: []resource.TestStep{ 19 | { 20 | Config: testAccCheckSplitApiKey_basic(workspaceID, name, "client_side", envName), 21 | Check: resource.ComposeTestCheckFunc( 22 | resource.TestCheckResourceAttrSet( 23 | "split_environment.foobar", "id"), 24 | ), 25 | }, 26 | }, 27 | }) 28 | } 29 | 30 | func TestAccSplitApiKey_ServerSide_Basic(t *testing.T) { 31 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 32 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 33 | envName := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 34 | 35 | resource.Test(t, resource.TestCase{ 36 | PreCheck: func() { testAccPreCheck(t) }, 37 | ProviderFactories: testAccProviderFactories, 38 | Steps: []resource.TestStep{ 39 | { 40 | Config: testAccCheckSplitApiKey_basic(workspaceID, name, "server_side", envName), 41 | Check: resource.ComposeTestCheckFunc( 42 | resource.TestCheckResourceAttrSet( 43 | "split_environment.foobar", "id"), 44 | ), 45 | }, 46 | }, 47 | }) 48 | } 49 | 50 | // TestAccSplitApiKey_Admin_Basic may or may not work depending on your account's functionality. You may get the following 51 | // error: `402 {"code":402,"message":"Forbidden by Paywalls:` 52 | func TestAccSplitApiKey_Admin_Basic(t *testing.T) { 53 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 54 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 55 | envName := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 56 | 57 | resource.Test(t, resource.TestCase{ 58 | PreCheck: func() { testAccPreCheck(t) }, 59 | ProviderFactories: testAccProviderFactories, 60 | Steps: []resource.TestStep{ 61 | { 62 | Config: testAccCheckSplitApiKey_basic_WithRoles(workspaceID, name, "admin", envName), 63 | Check: resource.ComposeTestCheckFunc( 64 | resource.TestCheckResourceAttrSet( 65 | "split_environment.foobar", "id"), 66 | ), 67 | }, 68 | }, 69 | }) 70 | } 71 | 72 | func testAccCheckSplitApiKey_basic(workspaceID, name, keyType, environmentName string) string { 73 | return fmt.Sprintf(` 74 | provider "split" { 75 | remove_environment_from_state_only = true 76 | } 77 | 78 | resource "split_environment" "foobar" { 79 | workspace_id = "%[1]s" 80 | name = "%[4]s" 81 | production = true 82 | } 83 | 84 | resource "split_api_key" "foobar" { 85 | workspace_id = "%[1]s" 86 | name = "%[2]s" 87 | type = "%[3]s" 88 | environment_ids = [split_environment.foobar.id] 89 | } 90 | `, workspaceID, name, keyType, environmentName) 91 | } 92 | 93 | func testAccCheckSplitApiKey_basic_WithRoles(workspaceID, name, keyType, environmentName string) string { 94 | return fmt.Sprintf(` 95 | provider "split" { 96 | remove_environment_from_state_only = true 97 | } 98 | 99 | resource "split_environment" "foobar" { 100 | workspace_id = "%[1]s" 101 | name = "%[4]s" 102 | production = true 103 | } 104 | 105 | resource "split_api_key" "foobar" { 106 | workspace_id = "%[1]s" 107 | name = "%[2]s" 108 | type = "%[3]s" 109 | environment_ids = [split_environment.foobar.id] 110 | roles = ["API_ALL_GRANTED"] 111 | } 112 | `, workspaceID, name, keyType, environmentName) 113 | } 114 | -------------------------------------------------------------------------------- /split/resource_split_environment.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | "log" 11 | ) 12 | 13 | func resourceSplitEnvironment() *schema.Resource { 14 | return &schema.Resource{ 15 | CreateContext: resourceSplitEnvironmentCreate, 16 | ReadContext: resourceSplitEnvironmentRead, 17 | UpdateContext: resourceSplitEnvironmentUpdate, 18 | DeleteContext: resourceSplitEnvironmentDelete, 19 | 20 | Importer: &schema.ResourceImporter{ 21 | StateContext: resourceSplitEnvironmentImport, 22 | }, 23 | 24 | Schema: map[string]*schema.Schema{ 25 | "workspace_id": { 26 | Type: schema.TypeString, 27 | Required: true, 28 | ForceNew: true, 29 | ValidateFunc: validation.IsUUID, 30 | }, 31 | 32 | "name": { 33 | Type: schema.TypeString, 34 | Required: true, 35 | ValidateFunc: validation.StringLenBetween(1, 15), 36 | }, 37 | 38 | "production": { 39 | Type: schema.TypeBool, 40 | Optional: true, 41 | Default: false, 42 | }, 43 | }, 44 | } 45 | } 46 | 47 | func resourceSplitEnvironmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 48 | client := meta.(*Config).API 49 | 50 | importID, parseErr := parseCompositeID(d.Id(), 2) 51 | if parseErr != nil { 52 | return nil, parseErr 53 | } 54 | 55 | workspaceID := importID[0] 56 | envID := importID[1] 57 | 58 | e, _, getErr := client.Environments.FindByID(workspaceID, envID) 59 | if getErr != nil { 60 | return nil, getErr 61 | } 62 | 63 | d.SetId(e.GetID()) 64 | 65 | d.Set("workspace_id", workspaceID) 66 | d.Set("name", e.GetName()) 67 | d.Set("production", e.GetProduction()) 68 | 69 | return []*schema.ResourceData{d}, nil 70 | } 71 | 72 | func resourceSplitEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 73 | var diags diag.Diagnostics 74 | client := meta.(*Config).API 75 | opts := &api.EnvironmentRequest{} 76 | workspaceID := getWorkspaceID(d) 77 | 78 | if v, ok := d.GetOk("name"); ok { 79 | vs := v.(string) 80 | opts.Name = &vs 81 | log.Printf("[DEBUG] new environment name is : %v", opts.GetName()) 82 | } 83 | 84 | production := d.Get("production").(bool) 85 | opts.Production = &production 86 | log.Printf("[DEBUG] new environment production is : %v", opts.GetProduction()) 87 | 88 | log.Printf("[DEBUG] Creating Environment named %v", opts.GetName()) 89 | 90 | e, _, createErr := client.Environments.Create(workspaceID, opts) 91 | if createErr != nil { 92 | diags = append(diags, diag.Diagnostic{ 93 | Severity: diag.Error, 94 | Summary: fmt.Sprintf("Unable to create environment %v", opts.GetName()), 95 | Detail: createErr.Error(), 96 | }) 97 | return diags 98 | } 99 | 100 | log.Printf("[DEBUG] Created Environment named %v", opts.GetName()) 101 | 102 | d.SetId(e.GetID()) 103 | 104 | return resourceSplitEnvironmentRead(ctx, d, meta) 105 | } 106 | 107 | func resourceSplitEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 108 | var diags diag.Diagnostics 109 | client := meta.(*Config).API 110 | 111 | e, _, getErr := client.Environments.FindByID(getWorkspaceID(d), d.Id()) 112 | if getErr != nil { 113 | diags = append(diags, diag.Diagnostic{ 114 | Severity: diag.Error, 115 | Summary: fmt.Sprintf("unable to fetch environment %s", d.Id()), 116 | Detail: getErr.Error(), 117 | }) 118 | return diags 119 | } 120 | 121 | d.Set("workspace_id", getWorkspaceID(d)) 122 | d.Set("name", e.GetName()) 123 | d.Set("production", e.GetProduction()) 124 | 125 | return diags 126 | } 127 | 128 | func resourceSplitEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 129 | var diags diag.Diagnostics 130 | client := meta.(*Config).API 131 | opts := &api.EnvironmentRequest{} 132 | 133 | if ok := d.HasChange("name"); ok { 134 | vs := d.Get("name").(string) 135 | opts.Name = &vs 136 | log.Printf("[DEBUG] updated environment name is : %v", opts.GetName()) 137 | } 138 | 139 | production := d.Get("production").(bool) 140 | opts.Production = &production 141 | log.Printf("[DEBUG] updated environment production is : %v", opts.GetProduction()) 142 | 143 | log.Printf("[DEBUG] Updating environment") 144 | 145 | _, _, updateErr := client.Environments.Update(getWorkspaceID(d), d.Id(), opts) 146 | if updateErr != nil { 147 | diags = append(diags, diag.Diagnostic{ 148 | Severity: diag.Error, 149 | Summary: fmt.Sprintf("unable to update environment %s", d.Id()), 150 | Detail: updateErr.Error(), 151 | }) 152 | return diags 153 | } 154 | 155 | log.Printf("[DEBUG] Updated environment") 156 | 157 | return resourceSplitEnvironmentRead(ctx, d, meta) 158 | } 159 | 160 | func resourceSplitEnvironmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 161 | config := meta.(*Config) 162 | var diags diag.Diagnostics 163 | 164 | if !config.RemoveEnvFromStateOnly { 165 | client := config.API 166 | 167 | log.Printf("[DEBUG] Deleting Environment %s", d.Id()) 168 | _, deleteErr := client.Environments.Delete(getWorkspaceID(d), d.Id()) 169 | if deleteErr != nil { 170 | diags = append(diags, diag.Diagnostic{ 171 | Severity: diag.Error, 172 | Summary: fmt.Sprintf("unable to delete environment %s", d.Id()), 173 | Detail: deleteErr.Error(), 174 | }) 175 | return diags 176 | } 177 | 178 | log.Printf("[DEBUG] Deleted Environment %s", d.Id()) 179 | } 180 | 181 | d.SetId("") 182 | 183 | return diags 184 | } 185 | -------------------------------------------------------------------------------- /split/resource_split_environment_segment_keys_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitEnvironmentSegmentKeys_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | envName := fmt.Sprintf("tftest-env-%s", acctest.RandString(3)) 13 | trafficTypeName := fmt.Sprintf("tftest-tt-%s", acctest.RandString(8)) 14 | segmentName := fmt.Sprintf("tftest-seg-%s", acctest.RandString(8)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitEnvironmentSegmentKeys_basic(workspaceID, envName, trafficTypeName, 22 | segmentName, true), 23 | Check: resource.ComposeTestCheckFunc( 24 | resource.TestCheckResourceAttr( 25 | "split_environment_segment_keys.foobar", "segment_name", segmentName), 26 | resource.TestCheckResourceAttr( 27 | "split_environment_segment_keys.foobar", "keys.#", "2"), 28 | //resource.TestCheckResourceAttr( 29 | // "split_environment_segment_keys.foobar", "keys.#.0", "tester1"), 30 | //resource.TestCheckResourceAttr( 31 | // "split_environment_segment_keys.foobar", "keys.#.1", "tester2"), 32 | resource.TestCheckResourceAttrSet( 33 | "split_environment_segment_keys.foobar", "environment_id"), 34 | resource.TestCheckResourceAttr( 35 | "split_environment_segment_keys.foobar", "comment", "test comment"), 36 | resource.TestCheckResourceAttr( 37 | "split_environment_segment_keys.foobar", "title", "test title"), 38 | ), 39 | }, 40 | { 41 | Config: testAccCheckSplitEnvironmentSegmentKeys_updatedKeys(workspaceID, envName, trafficTypeName, 42 | segmentName, true), 43 | Check: resource.ComposeTestCheckFunc( 44 | resource.TestCheckResourceAttr( 45 | "split_environment_segment_keys.foobar", "segment_name", segmentName), 46 | resource.TestCheckResourceAttr( 47 | "split_environment_segment_keys.foobar", "keys.#", "3"), 48 | //resource.TestCheckResourceAttr( 49 | // "split_environment_segment_keys.foobar", "keys.#.0", "tester2"), 50 | //resource.TestCheckResourceAttr( 51 | // "split_environment_segment_keys.foobar", "keys.#.1", "tester3"), 52 | //resource.TestCheckResourceAttr( 53 | // "split_environment_segment_keys.foobar", "keys.#.2", "tester3"), 54 | resource.TestCheckResourceAttrSet( 55 | "split_environment_segment_keys.foobar", "environment_id"), 56 | ), 57 | }, 58 | }, 59 | }) 60 | } 61 | 62 | func testAccCheckSplitEnvironmentSegmentKeys_basic( 63 | workspaceID, environmentName, trafficTypeName, segmentName string, production bool) string { 64 | return fmt.Sprintf(` 65 | provider "split" { 66 | remove_environment_from_state_only = true 67 | } 68 | 69 | resource "split_environment" "foobar" { 70 | workspace_id = "%[1]s" 71 | name = "%[2]s" 72 | production = %[5]v 73 | } 74 | 75 | resource "split_traffic_type" "foobar" { 76 | workspace_id = "%[1]s" 77 | name = "%[3]s" 78 | } 79 | 80 | resource "split_segment" "foobar" { 81 | workspace_id = "%[1]s" 82 | traffic_type_id = split_traffic_type.foobar.id 83 | name = "%[4]s" 84 | description = "description_of_my_segment" 85 | } 86 | 87 | resource "split_segment_environment_association" "foobar" { 88 | workspace_id = "%[1]s" 89 | environment_id = split_environment.foobar.id 90 | segment_name = split_segment.foobar.name 91 | } 92 | 93 | resource "split_environment_segment_keys" "foobar" { 94 | environment_id = split_environment.foobar.id 95 | segment_name = split_segment_environment_association.foobar.segment_name 96 | keys = ["tester1", "tester2"] 97 | title = "test title" 98 | comment = "test comment" 99 | } 100 | 101 | `, workspaceID, environmentName, trafficTypeName, segmentName, production) 102 | } 103 | 104 | func testAccCheckSplitEnvironmentSegmentKeys_updatedKeys( 105 | workspaceID, environmentName, trafficTypeName, segmentName string, production bool) string { 106 | return fmt.Sprintf(` 107 | provider "split" { 108 | remove_environment_from_state_only = true 109 | } 110 | 111 | resource "split_environment" "foobar" { 112 | workspace_id = "%[1]s" 113 | name = "%[2]s" 114 | production = %[5]v 115 | } 116 | 117 | resource "split_traffic_type" "foobar" { 118 | workspace_id = "%[1]s" 119 | name = "%[3]s" 120 | } 121 | 122 | resource "split_segment" "foobar" { 123 | workspace_id = "%[1]s" 124 | traffic_type_id = split_traffic_type.foobar.id 125 | name = "%[4]s" 126 | description = "description_of_my_segment" 127 | } 128 | 129 | resource "split_segment_environment_association" "foobar" { 130 | workspace_id = "%[1]s" 131 | environment_id = split_environment.foobar.id 132 | segment_name = split_segment.foobar.name 133 | } 134 | 135 | resource "split_environment_segment_keys" "foobar" { 136 | environment_id = split_environment.foobar.id 137 | segment_name = split_segment_environment_association.foobar.segment_name 138 | keys = ["tester2", "tester3", "tester4"] 139 | } 140 | 141 | `, workspaceID, environmentName, trafficTypeName, segmentName, production) 142 | } 143 | -------------------------------------------------------------------------------- /split/resource_split_environment_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitEnvironment_Basic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 14 | editedName := strings.ReplaceAll(name, "tftest", "edited") 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitEnvironment_basic(workspaceID, name, "true"), 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttr( 24 | "split_environment.foobar", "workspace_id", workspaceID), 25 | resource.TestCheckResourceAttr( 26 | "split_environment.foobar", "name", name), 27 | resource.TestCheckResourceAttr( 28 | "split_environment.foobar", "production", "true"), 29 | ), 30 | }, 31 | { 32 | Config: testAccCheckSplitEnvironment_basic(workspaceID, editedName, "false"), 33 | Check: resource.ComposeTestCheckFunc( 34 | resource.TestCheckResourceAttr( 35 | "split_environment.foobar", "workspace_id", workspaceID), 36 | resource.TestCheckResourceAttr( 37 | "split_environment.foobar", "name", editedName), 38 | resource.TestCheckResourceAttr( 39 | "split_environment.foobar", "production", "false"), 40 | ), 41 | }, 42 | }, 43 | }) 44 | } 45 | 46 | func testAccCheckSplitEnvironment_basic(workspaceID, name, production string) string { 47 | return fmt.Sprintf(` 48 | provider "split" { 49 | remove_environment_from_state_only = true 50 | } 51 | 52 | resource "split_environment" "foobar" { 53 | workspace_id = "%s" 54 | name = "%s" 55 | production = %s 56 | } 57 | `, workspaceID, name, production) 58 | } 59 | -------------------------------------------------------------------------------- /split/resource_split_group.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "log" 10 | ) 11 | 12 | func resourceSplitGroup() *schema.Resource { 13 | return &schema.Resource{ 14 | CreateContext: resourceSplitGroupCreate, 15 | ReadContext: resourceSplitGroupRead, 16 | UpdateContext: resourceSplitGroupUpdate, 17 | DeleteContext: resourceSplitGroupDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: schema.ImportStatePassthroughContext, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "name": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | }, 28 | 29 | "description": { 30 | Type: schema.TypeString, 31 | Optional: true, 32 | Computed: true, 33 | }, 34 | }, 35 | } 36 | } 37 | 38 | func resourceSplitGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 39 | var diags diag.Diagnostics 40 | client := meta.(*Config).API 41 | opts := &api.GroupRequest{} 42 | 43 | if v, ok := d.GetOk("name"); ok { 44 | opts.Name = v.(string) 45 | log.Printf("[DEBUG] new group name is : %v", opts.Name) 46 | } 47 | 48 | if v, ok := d.GetOk("description"); ok { 49 | opts.Description = v.(string) 50 | log.Printf("[DEBUG] new group description is : %v", opts.Description) 51 | } 52 | 53 | log.Printf("[DEBUG] Creating group %s", opts.Name) 54 | 55 | g, _, createErr := client.Groups.Create(opts) 56 | if createErr != nil { 57 | diags = append(diags, diag.Diagnostic{ 58 | Severity: diag.Error, 59 | Summary: fmt.Sprintf("Unable to create group %v", opts.Name), 60 | Detail: createErr.Error(), 61 | }) 62 | return diags 63 | } 64 | 65 | log.Printf("[DEBUG] Created group %s", opts.Name) 66 | 67 | d.SetId(g.GetID()) 68 | 69 | return resourceSplitGroupRead(ctx, d, meta) 70 | } 71 | 72 | func resourceSplitGroupRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 73 | var diags diag.Diagnostics 74 | client := meta.(*Config).API 75 | 76 | g, _, getErr := client.Groups.Get(d.Id()) 77 | if getErr != nil { 78 | diags = append(diags, diag.Diagnostic{ 79 | Severity: diag.Error, 80 | Summary: fmt.Sprintf("unable to fetch group %s", d.Id()), 81 | Detail: getErr.Error(), 82 | }) 83 | return diags 84 | } 85 | 86 | d.Set("name", g.GetName()) 87 | d.Set("description", g.GetDescription()) 88 | 89 | return diags 90 | } 91 | 92 | func resourceSplitGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 93 | var diags diag.Diagnostics 94 | client := meta.(*Config).API 95 | opts := &api.GroupRequest{} 96 | 97 | if ok := d.HasChange("name"); ok { 98 | opts.Name = d.Get("name").(string) 99 | log.Printf("[DEBUG] updated group name is : %v", opts.Name) 100 | } 101 | 102 | if ok := d.HasChange("description"); ok { 103 | opts.Description = d.Get("description").(string) 104 | log.Printf("[DEBUG] updated group description is : %v", opts.Description) 105 | } 106 | 107 | log.Printf("[DEBUG] Updating group %s", d.Id()) 108 | 109 | _, _, updateErr := client.Groups.Update(d.Id(), opts) 110 | if updateErr != nil { 111 | diags = append(diags, diag.Diagnostic{ 112 | Severity: diag.Error, 113 | Summary: fmt.Sprintf("Unable to update group %v", d.Id()), 114 | Detail: updateErr.Error(), 115 | }) 116 | return diags 117 | } 118 | 119 | log.Printf("[DEBUG] Updated group %s", d.Id()) 120 | 121 | return resourceSplitGroupRead(ctx, d, meta) 122 | } 123 | 124 | func resourceSplitGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 125 | var diags diag.Diagnostics 126 | client := meta.(*Config).API 127 | 128 | log.Printf("[DEBUG] Deleting group %s", d.Id()) 129 | 130 | _, deleteErr := client.Groups.Delete(d.Id()) 131 | if deleteErr != nil { 132 | diags = append(diags, diag.Diagnostic{ 133 | Severity: diag.Error, 134 | Summary: fmt.Sprintf("unable to delete group %s", d.Id()), 135 | Detail: deleteErr.Error(), 136 | }) 137 | return diags 138 | } 139 | 140 | log.Printf("[DEBUG] Deleted group %s", d.Id()) 141 | 142 | d.SetId("") 143 | 144 | return diags 145 | } 146 | -------------------------------------------------------------------------------- /split/resource_split_group_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitGroup_Basic(t *testing.T) { 11 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 12 | 13 | resource.Test(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: testAccProviderFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccCheckSplitGroup_basic(name, "created from Terraform"), 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestCheckResourceAttr( 21 | "split_group.foobar", "name", name), 22 | resource.TestCheckResourceAttr( 23 | "split_group.foobar", "description", "created from Terraform"), 24 | ), 25 | }, 26 | { 27 | Config: testAccCheckSplitGroup_basic(name+" edited", "created from Terraform edited"), 28 | Check: resource.ComposeTestCheckFunc( 29 | resource.TestCheckResourceAttr( 30 | "split_group.foobar", "name", name+" edited"), 31 | resource.TestCheckResourceAttr( 32 | "split_group.foobar", "description", "created from Terraform edited"), 33 | ), 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | func testAccCheckSplitGroup_basic(name, description string) string { 40 | return fmt.Sprintf(` 41 | resource "split_group" "foobar" { 42 | name = "%s" 43 | description = "%s" 44 | } 45 | `, name, description) 46 | } 47 | -------------------------------------------------------------------------------- /split/resource_split_segment.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | "log" 11 | ) 12 | 13 | func resourceSplitSegment() *schema.Resource { 14 | return &schema.Resource{ 15 | CreateContext: resourceSplitSegmentCreate, 16 | ReadContext: resourceSplitSegmentRead, 17 | DeleteContext: resourceSplitSegmentDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: resourceSplitSegmentImport, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "workspace_id": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | ForceNew: true, 28 | ValidateFunc: validation.IsUUID, 29 | }, 30 | 31 | "traffic_type_id": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | ValidateFunc: validation.IsUUID, 36 | }, 37 | 38 | "name": { 39 | Type: schema.TypeString, 40 | Required: true, 41 | ForceNew: true, 42 | }, 43 | 44 | "description": { 45 | Type: schema.TypeString, 46 | Optional: true, 47 | Computed: true, 48 | ForceNew: true, 49 | }, 50 | }, 51 | } 52 | } 53 | 54 | func resourceSplitSegmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 55 | client := meta.(*Config).API 56 | 57 | importID, parseErr := parseCompositeID(d.Id(), 2) 58 | if parseErr != nil { 59 | return nil, parseErr 60 | } 61 | 62 | workspaceID := importID[0] 63 | segmentName := importID[1] 64 | 65 | s, _, getErr := client.Segments.Get(workspaceID, segmentName) 66 | if getErr != nil { 67 | return nil, getErr 68 | } 69 | 70 | d.SetId(s.GetName()) 71 | d.Set("workspace_id", workspaceID) 72 | d.Set("traffic_type_id", s.GetTrafficType().GetID()) 73 | d.Set("name", s.GetName()) 74 | d.Set("description", s.GetDescription()) 75 | 76 | return []*schema.ResourceData{d}, nil 77 | } 78 | 79 | func resourceSplitSegmentCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 80 | var diags diag.Diagnostics 81 | client := meta.(*Config).API 82 | opts := &api.SegmentRequest{} 83 | workspaceID := getWorkspaceID(d) 84 | 85 | var trafficTypeID string 86 | if v, ok := d.GetOk("traffic_type_id"); ok { 87 | trafficTypeID = v.(string) 88 | log.Printf("[DEBUG] new segment traffic_type_id is : %v", trafficTypeID) 89 | } 90 | 91 | if v, ok := d.GetOk("name"); ok { 92 | opts.Name = v.(string) 93 | log.Printf("[DEBUG] new segment name is : %v", opts.Name) 94 | } 95 | 96 | if v, ok := d.GetOk("description"); ok { 97 | opts.Description = v.(string) 98 | log.Printf("[DEBUG] new segment description is : %v", opts.Description) 99 | } 100 | 101 | log.Printf("[DEBUG] Creating segment %s", opts.Name) 102 | 103 | s, _, createErr := client.Segments.Create(workspaceID, trafficTypeID, opts) 104 | if createErr != nil { 105 | diags = append(diags, diag.Diagnostic{ 106 | Severity: diag.Error, 107 | Summary: fmt.Sprintf("Unable to create segment %v", opts.Name), 108 | Detail: createErr.Error(), 109 | }) 110 | return diags 111 | } 112 | 113 | log.Printf("[DEBUG] Created segment %s", opts.Name) 114 | 115 | d.SetId(s.GetName()) 116 | 117 | return resourceSplitSegmentRead(ctx, d, meta) 118 | } 119 | 120 | func resourceSplitSegmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 121 | var diags diag.Diagnostics 122 | client := meta.(*Config).API 123 | workspaceID := getWorkspaceID(d) 124 | 125 | s, _, getErr := client.Segments.Get(workspaceID, d.Id()) 126 | if getErr != nil { 127 | diags = append(diags, diag.Diagnostic{ 128 | Severity: diag.Error, 129 | Summary: fmt.Sprintf("unable to fetch segment %s", d.Id()), 130 | Detail: getErr.Error(), 131 | }) 132 | return diags 133 | } 134 | 135 | d.Set("workspace_id", workspaceID) 136 | d.Set("traffic_type_id", s.GetTrafficType().GetID()) 137 | d.Set("name", s.GetName()) 138 | d.Set("description", s.GetDescription()) 139 | 140 | return diags 141 | } 142 | 143 | func resourceSplitSegmentDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 144 | var diags diag.Diagnostics 145 | client := meta.(*Config).API 146 | workspaceID := getWorkspaceID(d) 147 | 148 | log.Printf("[DEBUG] Deleting segment %s", d.Id()) 149 | 150 | _, deleteErr := client.Segments.Delete(workspaceID, d.Id()) 151 | if deleteErr != nil { 152 | diags = append(diags, diag.Diagnostic{ 153 | Severity: diag.Error, 154 | Summary: fmt.Sprintf("unable to delete segment %s", d.Id()), 155 | Detail: deleteErr.Error(), 156 | }) 157 | return diags 158 | } 159 | 160 | log.Printf("[DEBUG] Deleted segment %s", d.Id()) 161 | 162 | d.SetId("") 163 | 164 | return diags 165 | } 166 | -------------------------------------------------------------------------------- /split/resource_split_segment_environment_association.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | "log" 11 | ) 12 | 13 | func resourceSplitSegmentEnvironmentAssociation() *schema.Resource { 14 | return &schema.Resource{ 15 | CreateContext: resourceSplitSegmentEnvironmentAssociationCreate, 16 | ReadContext: resourceSplitSegmentEnvironmentAssociationRead, 17 | DeleteContext: resourceSplitSegmentEnvironmentAssociationDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: resourceSplitSegmentEnvironmentAssociationImport, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "workspace_id": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | ForceNew: true, 28 | ValidateFunc: validation.IsUUID, 29 | }, 30 | 31 | "environment_id": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | ValidateFunc: validation.IsUUID, 36 | }, 37 | 38 | "segment_name": { 39 | Type: schema.TypeString, 40 | Required: true, 41 | ForceNew: true, 42 | }, 43 | }, 44 | } 45 | } 46 | 47 | func resourceSplitSegmentEnvironmentAssociationImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 48 | client := meta.(*Config).API 49 | 50 | importID, parseErr := parseCompositeID(d.Id(), 3) 51 | if parseErr != nil { 52 | return nil, parseErr 53 | } 54 | 55 | workspaceID := importID[0] 56 | environmentID := importID[1] 57 | segmentName := importID[2] 58 | 59 | segments, _, getErr := client.Environments.ListSegments(workspaceID, environmentID) 60 | if getErr != nil { 61 | return nil, fmt.Errorf(fmt.Sprintf("unable to fetch all segments in environment %s", environmentID)) 62 | } 63 | 64 | // Iterate through all segments to find the right one 65 | var segment *api.Segment 66 | for _, s := range segments.Objects { 67 | if s.GetName() == segmentName { 68 | segment = s 69 | } 70 | } 71 | 72 | if segment == nil { 73 | return nil, fmt.Errorf(fmt.Sprintf("did not find to segment [%s] in environment [%s]", d.Id(), environmentID)) 74 | } 75 | 76 | d.SetId(segment.GetName()) 77 | d.Set("workspace_id", workspaceID) 78 | d.Set("environment_id", segment.GetEnvironment().GetID()) 79 | 80 | return []*schema.ResourceData{d}, nil 81 | } 82 | 83 | func resourceSplitSegmentEnvironmentAssociationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 84 | var diags diag.Diagnostics 85 | client := meta.(*Config).API 86 | environmentID := getEnvironmentID(d) 87 | segmentName := d.Get("segment_name").(string) 88 | 89 | log.Printf("[DEBUG] Activating segment [%s] in environment [%s]", segmentName, environmentID) 90 | 91 | s, _, createErr := client.Segments.Activate(environmentID, segmentName) 92 | if createErr != nil { 93 | diags = append(diags, diag.Diagnostic{ 94 | Severity: diag.Error, 95 | Summary: fmt.Sprintf("Unable to activate segment [%s] in environment [%s]", segmentName, environmentID), 96 | Detail: createErr.Error(), 97 | }) 98 | return diags 99 | } 100 | 101 | log.Printf("[DEBUG] Activated segment [%s] in environment [%s]", segmentName, environmentID) 102 | 103 | d.SetId(s.GetName()) 104 | 105 | return resourceSplitSegmentEnvironmentAssociationRead(ctx, d, meta) 106 | } 107 | 108 | func resourceSplitSegmentEnvironmentAssociationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 109 | var diags diag.Diagnostics 110 | client := meta.(*Config).API 111 | workspaceID := getWorkspaceID(d) 112 | environmentID := getEnvironmentID(d) 113 | 114 | segments, _, getErr := client.Environments.ListSegments(workspaceID, environmentID) 115 | if getErr != nil { 116 | diags = append(diags, diag.Diagnostic{ 117 | Severity: diag.Error, 118 | Summary: fmt.Sprintf("unable to fetch all segments in environment %s", environmentID), 119 | Detail: getErr.Error(), 120 | }) 121 | return diags 122 | } 123 | 124 | // Iterate through all segments to find the right one 125 | var segment *api.Segment 126 | for _, s := range segments.Objects { 127 | if s.GetName() == d.Id() { 128 | segment = s 129 | } 130 | } 131 | 132 | if segment == nil { 133 | diags = append(diags, diag.Diagnostic{ 134 | Severity: diag.Error, 135 | Summary: fmt.Sprintf("did not find to segment [%s] in environment [%s]", d.Id(), environmentID), 136 | Detail: getErr.Error(), 137 | }) 138 | return diags 139 | } 140 | 141 | d.Set("workspace_id", workspaceID) 142 | d.Set("environment_id", segment.GetEnvironment().GetID()) 143 | d.Set("segment_name", segment.GetName()) 144 | 145 | return diags 146 | } 147 | 148 | func resourceSplitSegmentEnvironmentAssociationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 149 | var diags diag.Diagnostics 150 | client := meta.(*Config).API 151 | environmentID := getEnvironmentID(d) 152 | 153 | log.Printf("[DEBUG] Deactivating segment [%s] from environment [%s]", d.Id(), environmentID) 154 | 155 | _, deleteErr := client.Segments.Deactivate(environmentID, d.Id()) 156 | if deleteErr != nil { 157 | diags = append(diags, diag.Diagnostic{ 158 | Severity: diag.Error, 159 | Summary: fmt.Sprintf("unable to delete segment %s", d.Id()), 160 | Detail: deleteErr.Error(), 161 | }) 162 | return diags 163 | } 164 | 165 | log.Printf("[DEBUG] Deactivated segment [%s] from environment [%s]", d.Id(), environmentID) 166 | 167 | d.SetId("") 168 | 169 | return diags 170 | } 171 | -------------------------------------------------------------------------------- /split/resource_split_segment_environment_association_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitSegmentEnvironmentAssociation_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | envName := fmt.Sprintf("tftest-env-%s", acctest.RandString(3)) 13 | trafficTypeName := fmt.Sprintf("tftest-tt-%s", acctest.RandString(8)) 14 | segmentName := fmt.Sprintf("tftest-seg-%s", acctest.RandString(8)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitSegmentEnvironmentAssociation_basic(workspaceID, 22 | envName, trafficTypeName, segmentName), 23 | Check: resource.ComposeTestCheckFunc( 24 | resource.TestCheckResourceAttr( 25 | "split_segment_environment_association.foobar", "workspace_id", workspaceID), 26 | resource.TestCheckResourceAttr( 27 | "split_segment_environment_association.foobar", "segment_name", segmentName), 28 | resource.TestCheckResourceAttrSet( 29 | "split_segment_environment_association.foobar", "environment_id"), 30 | ), 31 | }, 32 | }, 33 | }) 34 | } 35 | 36 | func testAccCheckSplitSegmentEnvironmentAssociation_basic(workspaceID, envName, trafficTypeName, segmentName string) string { 37 | return fmt.Sprintf(` 38 | provider "split" { 39 | remove_environment_from_state_only = true 40 | } 41 | 42 | resource "split_environment" "foobar" { 43 | workspace_id = "%[1]s" 44 | name = "%[2]s" 45 | production = false 46 | } 47 | 48 | resource "split_traffic_type" "foobar" { 49 | workspace_id = "%[1]s" 50 | name = "%[3]s" 51 | } 52 | 53 | resource "split_segment" "foobar" { 54 | workspace_id = "%[1]s" 55 | traffic_type_id = split_traffic_type.foobar.id 56 | name = "%[4]s" 57 | description = "made by TF tester" 58 | } 59 | 60 | resource "split_segment_environment_association" "foobar" { 61 | workspace_id = "%[1]s" 62 | environment_id = split_environment.foobar.id 63 | segment_name = split_segment.foobar.name 64 | } 65 | `, workspaceID, envName, trafficTypeName, segmentName) 66 | } 67 | -------------------------------------------------------------------------------- /split/resource_split_segment_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitSegment_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | trafficTypeID := testAccConfig.GetTrafficTypeNameorSkip(t) 13 | name := fmt.Sprintf("tftest-%s", acctest.RandString(8)) 14 | 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | ProviderFactories: testAccProviderFactories, 18 | Steps: []resource.TestStep{ 19 | { 20 | Config: testAccCheckSplitSegment_basic(workspaceID, trafficTypeID, name, "created from Terraform"), 21 | Check: resource.ComposeTestCheckFunc( 22 | resource.TestCheckResourceAttr( 23 | "split_segment.foobar", "workspace_id", workspaceID), 24 | resource.TestCheckResourceAttr( 25 | "split_segment.foobar", "traffic_type_id", trafficTypeID), 26 | resource.TestCheckResourceAttr( 27 | "split_segment.foobar", "name", name), 28 | resource.TestCheckResourceAttr( 29 | "split_segment.foobar", "description", "created from Terraform"), 30 | ), 31 | }, 32 | }, 33 | }) 34 | } 35 | 36 | func testAccCheckSplitSegment_basic(workspaceID, trafficTypeID, name, description string) string { 37 | return fmt.Sprintf(` 38 | resource "split_segment" "foobar" { 39 | workspace_id = "%s" 40 | traffic_type_id = "%s" 41 | name = "%s" 42 | description = "%s" 43 | } 44 | `, workspaceID, trafficTypeID, name, description) 45 | } 46 | -------------------------------------------------------------------------------- /split/resource_split_split.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | "log" 11 | "regexp" 12 | ) 13 | 14 | func resourceSplitSplit() *schema.Resource { 15 | return &schema.Resource{ 16 | CreateContext: resourceSplitSplitCreate, 17 | ReadContext: resourceSplitSplitRead, 18 | UpdateContext: resourceSplitSplitUpdate, 19 | DeleteContext: resourceSplitSplitDelete, 20 | 21 | Importer: &schema.ResourceImporter{ 22 | StateContext: resourceSplitSplitImport, 23 | }, 24 | 25 | Schema: map[string]*schema.Schema{ 26 | "workspace_id": { 27 | Type: schema.TypeString, 28 | Required: true, 29 | ForceNew: true, 30 | ValidateFunc: validation.IsUUID, 31 | }, 32 | 33 | "traffic_type_id": { 34 | Type: schema.TypeString, 35 | Required: true, 36 | ForceNew: true, 37 | ValidateFunc: validation.IsUUID, 38 | }, 39 | 40 | "name": { 41 | Type: schema.TypeString, 42 | Required: true, 43 | ForceNew: true, 44 | ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[a-zA-Z][a-zA-Z-_\d]+$`), 45 | "Name must start with a letter and can contain hyphens, underscores, letters, and numbers"), 46 | }, 47 | 48 | "description": { 49 | Type: schema.TypeString, 50 | Optional: true, 51 | Computed: true, 52 | }, 53 | }, 54 | } 55 | } 56 | 57 | func resourceSplitSplitImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 58 | client := meta.(*Config).API 59 | 60 | importID, parseErr := parseCompositeID(d.Id(), 2) 61 | if parseErr != nil { 62 | return nil, parseErr 63 | } 64 | 65 | workspaceID := importID[0] 66 | splitID := importID[1] 67 | 68 | s, _, getErr := client.Splits.Get(workspaceID, splitID) 69 | if getErr != nil { 70 | return nil, getErr 71 | } 72 | 73 | d.SetId(s.GetID()) 74 | d.Set("workspace_id", workspaceID) 75 | d.Set("traffic_type_id", s.GetTrafficType().GetID()) 76 | d.Set("name", s.GetName()) 77 | d.Set("description", s.GetDescription()) 78 | 79 | return []*schema.ResourceData{d}, nil 80 | } 81 | 82 | func resourceSplitSplitCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 83 | var diags diag.Diagnostics 84 | client := meta.(*Config).API 85 | opts := &api.SplitCreateRequest{} 86 | workspaceID := getWorkspaceID(d) 87 | trafficTypeID := getTrafficTypeID(d) 88 | 89 | if v, ok := d.GetOk("name"); ok { 90 | opts.Name = v.(string) 91 | log.Printf("[DEBUG] new split name is : %v", opts.Name) 92 | } 93 | 94 | if v, ok := d.GetOk("description"); ok { 95 | opts.Description = v.(string) 96 | log.Printf("[DEBUG] new split description is : %v", opts.Description) 97 | } 98 | 99 | log.Printf("[DEBUG] Creating split %v", opts.Name) 100 | 101 | s, _, createErr := client.Splits.Create(workspaceID, trafficTypeID, opts) 102 | if createErr != nil { 103 | diags = append(diags, diag.Diagnostic{ 104 | Severity: diag.Error, 105 | Summary: fmt.Sprintf("Unable to create split %v", opts.Name), 106 | Detail: createErr.Error(), 107 | }) 108 | return diags 109 | } 110 | 111 | log.Printf("[DEBUG] Created split %v", s.GetID()) 112 | 113 | d.SetId(s.GetID()) 114 | d.Set("workspace_id", workspaceID) 115 | 116 | return resourceSplitSplitRead(ctx, d, meta) 117 | } 118 | 119 | func resourceSplitSplitUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 120 | var diags diag.Diagnostics 121 | client := meta.(*Config).API 122 | workspaceID := getWorkspaceID(d) 123 | 124 | if ok := d.HasChange("description"); ok { 125 | description := d.Get("description").(string) 126 | log.Printf("[DEBUG] updated split description is : %v", description) 127 | 128 | log.Printf("[DEBUG] Updating split description %v", d.Id()) 129 | 130 | _, _, updateErr := client.Splits.UpdateDescription(workspaceID, d.Get("name").(string), description) 131 | if updateErr != nil { 132 | diags = append(diags, diag.Diagnostic{ 133 | Severity: diag.Error, 134 | Summary: fmt.Sprintf("Unable to update split description %v", description), 135 | Detail: updateErr.Error(), 136 | }) 137 | return diags 138 | } 139 | 140 | log.Printf("[DEBUG] Updated split description %v", d.Id()) 141 | } 142 | 143 | return resourceSplitSplitRead(ctx, d, meta) 144 | } 145 | 146 | func resourceSplitSplitRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 147 | var diags diag.Diagnostics 148 | client := meta.(*Config).API 149 | 150 | s, _, getErr := client.Splits.Get(getWorkspaceID(d), d.Id()) 151 | if getErr != nil { 152 | diags = append(diags, diag.Diagnostic{ 153 | Severity: diag.Error, 154 | Summary: fmt.Sprintf("unable to fetch split %s", d.Id()), 155 | Detail: getErr.Error(), 156 | }) 157 | return diags 158 | } 159 | 160 | d.Set("name", s.GetName()) 161 | d.Set("description", s.GetDescription()) 162 | d.Set("traffic_type_id", s.GetTrafficType().GetID()) 163 | 164 | return diags 165 | } 166 | 167 | func resourceSplitSplitDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 168 | client := meta.(*Config).API 169 | var diags diag.Diagnostics 170 | 171 | log.Printf("[DEBUG] Deleting split %s", d.Id()) 172 | 173 | _, deleteErr := client.Splits.Delete(getWorkspaceID(d), d.Get("name").(string)) 174 | if deleteErr != nil { 175 | diags = append(diags, diag.Diagnostic{ 176 | Severity: diag.Error, 177 | Summary: fmt.Sprintf("unable to delete split %s", d.Id()), 178 | Detail: deleteErr.Error(), 179 | }) 180 | return diags 181 | } 182 | 183 | log.Printf("[DEBUG] Deleted split %s", d.Id()) 184 | 185 | d.SetId("") 186 | 187 | return diags 188 | } 189 | -------------------------------------------------------------------------------- /split/resource_split_split_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "regexp" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitSplit_Basic(t *testing.T) { 12 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 13 | trafficTypeName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(10)) 14 | splitName := fmt.Sprintf("s-tftest-%s", acctest.RandString(10)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitSplit_basic(workspaceID, trafficTypeName, splitName, "my split description"), 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttr( 24 | "split_split.foobar", "workspace_id", workspaceID), 25 | resource.TestCheckResourceAttr( 26 | "split_split.foobar", "name", splitName), 27 | resource.TestCheckResourceAttr( 28 | "split_split.foobar", "description", "my split description"), 29 | resource.TestCheckResourceAttrSet( 30 | "split_split.foobar", "traffic_type_id"), 31 | ), 32 | }, 33 | { 34 | Config: testAccCheckSplitSplit_basic(workspaceID, trafficTypeName, splitName, "my split edited description"), 35 | Check: resource.ComposeTestCheckFunc( 36 | resource.TestCheckResourceAttr( 37 | "split_split.foobar", "workspace_id", workspaceID), 38 | resource.TestCheckResourceAttr( 39 | "split_split.foobar", "name", splitName), 40 | resource.TestCheckResourceAttr( 41 | "split_split.foobar", "description", "my split edited description"), 42 | resource.TestCheckResourceAttrSet( 43 | "split_split.foobar", "traffic_type_id"), 44 | ), 45 | }, 46 | }, 47 | }) 48 | } 49 | 50 | func TestAccSplitSplit_InvalidName(t *testing.T) { 51 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 52 | trafficTypeName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(10)) 53 | 54 | resource.Test(t, resource.TestCase{ 55 | PreCheck: func() { testAccPreCheck(t) }, 56 | ProviderFactories: testAccProviderFactories, 57 | Steps: []resource.TestStep{ 58 | { 59 | Config: testAccCheckSplitSplit_basic(workspaceID, trafficTypeName, "1invalidname", "my split description"), 60 | ExpectError: regexp.MustCompile(`Name must start with a letter and can contain hyphens, underscores, letters, and numbers`), 61 | }, 62 | }, 63 | }) 64 | } 65 | 66 | func testAccCheckSplitSplit_basic(workspaceID, trafficTypeName, splitName, splitDescription string) string { 67 | return fmt.Sprintf(` 68 | provider "split" { 69 | remove_environment_from_state_only = true 70 | } 71 | 72 | resource "split_traffic_type" "foobar" { 73 | workspace_id = "%[1]s" 74 | name = "%[2]s" 75 | } 76 | 77 | resource "split_split" "foobar" { 78 | workspace_id = "%[1]s" 79 | traffic_type_id = split_traffic_type.foobar.id 80 | name = "%[3]s" 81 | description = "%[4]s" 82 | } 83 | `, workspaceID, trafficTypeName, splitName, splitDescription) 84 | } 85 | -------------------------------------------------------------------------------- /split/resource_split_traffic_type.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | "log" 11 | ) 12 | 13 | func resourceSplitTrafficType() *schema.Resource { 14 | return &schema.Resource{ 15 | CreateContext: resourceSplitTrafficTypeCreate, 16 | ReadContext: resourceSplitTrafficTypeRead, 17 | DeleteContext: resourceSplitTrafficTypeDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: resourceSplitTrafficTypeImport, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "workspace_id": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | ForceNew: true, 28 | ValidateFunc: validation.IsUUID, 29 | }, 30 | 31 | "name": { 32 | Type: schema.TypeString, 33 | Required: true, 34 | ForceNew: true, 35 | }, 36 | 37 | "type": { 38 | Type: schema.TypeString, 39 | Computed: true, 40 | }, 41 | 42 | "display_attribute_id": { 43 | Type: schema.TypeString, 44 | Computed: true, 45 | }, 46 | }, 47 | } 48 | } 49 | 50 | func resourceSplitTrafficTypeImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 51 | client := meta.(*Config).API 52 | 53 | importID, parseErr := parseCompositeID(d.Id(), 2) 54 | if parseErr != nil { 55 | return nil, parseErr 56 | } 57 | 58 | workspaceID := importID[0] 59 | trafficTypeID := importID[1] 60 | 61 | tt, _, getErr := client.TrafficTypes.FindByID(workspaceID, trafficTypeID) 62 | if getErr != nil { 63 | return nil, getErr 64 | } 65 | 66 | d.SetId(tt.GetID()) 67 | d.Set("workspace_id", tt.Workspace.GetID()) 68 | d.Set("name", tt.GetName()) 69 | d.Set("type", tt.GetType()) 70 | d.Set("display_attribute_id", tt.GetDisplayAttributeID()) 71 | 72 | return []*schema.ResourceData{d}, nil 73 | } 74 | 75 | func resourceSplitTrafficTypeCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 76 | var diags diag.Diagnostics 77 | client := meta.(*Config).API 78 | opts := &api.TrafficTypeRequest{} 79 | workspaceID := getWorkspaceID(d) 80 | 81 | if v, ok := d.GetOk("name"); ok { 82 | opts.Name = v.(string) 83 | log.Printf("[DEBUG] new traffic type is : %v", opts.Name) 84 | } 85 | 86 | log.Printf("[DEBUG] Creating traffic type %v", opts.Name) 87 | 88 | tt, _, createErr := client.TrafficTypes.Create(workspaceID, opts) 89 | if createErr != nil { 90 | diags = append(diags, diag.Diagnostic{ 91 | Severity: diag.Error, 92 | Summary: fmt.Sprintf("Unable to create traffic type %v", opts.Name), 93 | Detail: createErr.Error(), 94 | }) 95 | return diags 96 | } 97 | 98 | log.Printf("[DEBUG] Created traffic type %v", tt.GetID()) 99 | 100 | d.SetId(tt.GetID()) 101 | d.Set("workspace_id", tt.Workspace.GetID()) 102 | 103 | return resourceSplitTrafficTypeRead(ctx, d, meta) 104 | } 105 | 106 | func resourceSplitTrafficTypeRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 107 | var diags diag.Diagnostics 108 | client := meta.(*Config).API 109 | 110 | tt, _, getErr := client.TrafficTypes.FindByID(getWorkspaceID(d), d.Id()) 111 | if getErr != nil { 112 | diags = append(diags, diag.Diagnostic{ 113 | Severity: diag.Error, 114 | Summary: fmt.Sprintf("unable to fetch traffic type %s", d.Id()), 115 | Detail: getErr.Error(), 116 | }) 117 | return diags 118 | } 119 | 120 | d.Set("name", tt.GetName()) 121 | d.Set("type", tt.GetType()) 122 | d.Set("display_attribute_id", tt.GetDisplayAttributeID()) 123 | 124 | return diags 125 | } 126 | 127 | func resourceSplitTrafficTypeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 128 | client := meta.(*Config).API 129 | var diags diag.Diagnostics 130 | 131 | log.Printf("[DEBUG] Deleting traffic type %s", d.Id()) 132 | _, deleteErr := client.TrafficTypes.Delete(d.Id()) 133 | if deleteErr != nil { 134 | diags = append(diags, diag.Diagnostic{ 135 | Severity: diag.Error, 136 | Summary: fmt.Sprintf("unable to delete traffic type %s", d.Id()), 137 | Detail: deleteErr.Error(), 138 | }) 139 | return diags 140 | } 141 | 142 | log.Printf("[DEBUG] Deleted traffic type %s", d.Id()) 143 | 144 | d.SetId("") 145 | 146 | return diags 147 | } 148 | -------------------------------------------------------------------------------- /split/resource_split_traffic_type_attribute_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitTrafficTypeAttribute_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | ttName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(8)) 13 | attrIdentifier := acctest.RandString(8) 14 | attrName := fmt.Sprintf("attr-tftest-%s", acctest.RandString(8)) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitTrafficTypeAttribute_basic(workspaceID, attrIdentifier, ttName, attrName), 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttr( 24 | "split_traffic_type_attribute.foobar", "workspace_id", workspaceID), 25 | resource.TestCheckResourceAttrSet( 26 | "split_traffic_type_attribute.foobar", "traffic_type_id"), 27 | resource.TestCheckResourceAttr( 28 | "split_traffic_type_attribute.foobar", "display_name", attrName), 29 | resource.TestCheckResourceAttr( 30 | "split_traffic_type_attribute.foobar", "description", "this is my attribute description"), 31 | resource.TestCheckResourceAttr( 32 | "split_traffic_type_attribute.foobar", "data_type", "STRING"), 33 | resource.TestCheckResourceAttr( 34 | "split_traffic_type_attribute.foobar", "suggested_values.#", "3"), 35 | resource.TestCheckResourceAttr( 36 | "split_traffic_type_attribute.foobar", "is_searchable", "true"), 37 | resource.TestCheckResourceAttrSet( 38 | "split_traffic_type_attribute.foobar", "organization_id"), 39 | ), 40 | }, 41 | }, 42 | }) 43 | } 44 | 45 | func TestAccSplitTrafficTypeAttribute_Updates(t *testing.T) { 46 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 47 | ttName := fmt.Sprintf("tt-tftest-%s", acctest.RandString(8)) 48 | attrIdentifier := acctest.RandString(8) 49 | attrName := fmt.Sprintf("attr-tftest-%s", acctest.RandString(8)) 50 | 51 | resource.Test(t, resource.TestCase{ 52 | PreCheck: func() { testAccPreCheck(t) }, 53 | ProviderFactories: testAccProviderFactories, 54 | Steps: []resource.TestStep{ 55 | { 56 | Config: testAccCheckSplitTrafficTypeAttribute_basic(workspaceID, attrIdentifier, ttName, attrName), 57 | Check: resource.ComposeTestCheckFunc( 58 | resource.TestCheckResourceAttr( 59 | "split_traffic_type_attribute.foobar", "workspace_id", workspaceID), 60 | resource.TestCheckResourceAttrSet( 61 | "split_traffic_type_attribute.foobar", "traffic_type_id"), 62 | resource.TestCheckResourceAttr( 63 | "split_traffic_type_attribute.foobar", "display_name", attrName), 64 | resource.TestCheckResourceAttr( 65 | "split_traffic_type_attribute.foobar", "description", "this is my attribute description"), 66 | resource.TestCheckResourceAttr( 67 | "split_traffic_type_attribute.foobar", "data_type", "STRING"), 68 | resource.TestCheckResourceAttr( 69 | "split_traffic_type_attribute.foobar", "suggested_values.#", "3"), 70 | resource.TestCheckResourceAttr( 71 | "split_traffic_type_attribute.foobar", "is_searchable", "true"), 72 | resource.TestCheckResourceAttrSet( 73 | "split_traffic_type_attribute.foobar", "organization_id"), 74 | ), 75 | }, 76 | { 77 | Config: testAccCheckSplitTrafficTypeAttribute_updates(workspaceID, attrIdentifier, ttName, attrName), 78 | Check: resource.ComposeTestCheckFunc( 79 | resource.TestCheckResourceAttr( 80 | "split_traffic_type_attribute.foobar", "workspace_id", workspaceID), 81 | resource.TestCheckResourceAttrSet( 82 | "split_traffic_type_attribute.foobar", "traffic_type_id"), 83 | resource.TestCheckResourceAttr( 84 | "split_traffic_type_attribute.foobar", "display_name", attrName+" edited"), 85 | resource.TestCheckResourceAttr( 86 | "split_traffic_type_attribute.foobar", "description", "this is my attribute description + edited"), 87 | resource.TestCheckResourceAttr( 88 | "split_traffic_type_attribute.foobar", "data_type", "NUMBER"), 89 | resource.TestCheckResourceAttr( 90 | "split_traffic_type_attribute.foobar", "suggested_values.#", "3"), 91 | resource.TestCheckResourceAttr( 92 | "split_traffic_type_attribute.foobar", "suggested_values.0", "1"), 93 | resource.TestCheckResourceAttr( 94 | "split_traffic_type_attribute.foobar", "suggested_values.1", "2"), 95 | resource.TestCheckResourceAttr( 96 | "split_traffic_type_attribute.foobar", "suggested_values.2", "3"), 97 | resource.TestCheckResourceAttr( 98 | "split_traffic_type_attribute.foobar", "is_searchable", "true"), 99 | resource.TestCheckResourceAttrSet( 100 | "split_traffic_type_attribute.foobar", "organization_id"), 101 | ), 102 | }, 103 | }, 104 | }) 105 | } 106 | 107 | func testAccCheckSplitTrafficTypeAttribute_basic(workspaceID, attrIdentifier, ttName, attrName string) string { 108 | return fmt.Sprintf(` 109 | provider "split" { 110 | remove_environment_from_state_only = true 111 | } 112 | 113 | resource "split_traffic_type" "foobar" { 114 | workspace_id = "%[1]s" 115 | name = "%[3]s" 116 | } 117 | 118 | resource "split_traffic_type_attribute" "foobar" { 119 | workspace_id = "%[1]s" 120 | traffic_type_id = split_traffic_type.foobar.id 121 | identifier = "%[2]s" 122 | display_name = "%[4]s" 123 | description = "this is my attribute description" 124 | data_type = "STRING" 125 | suggested_values = ["a", "b", "c"] 126 | is_searchable = true 127 | } 128 | `, workspaceID, attrIdentifier, ttName, attrName) 129 | } 130 | 131 | func testAccCheckSplitTrafficTypeAttribute_updates(workspaceID, attrIdentifier, ttName, attrName string) string { 132 | return fmt.Sprintf(` 133 | provider "split" { 134 | remove_environment_from_state_only = true 135 | } 136 | 137 | resource "split_traffic_type" "foobar" { 138 | workspace_id = "%[1]s" 139 | name = "%[3]s" 140 | } 141 | 142 | resource "split_traffic_type_attribute" "foobar" { 143 | workspace_id = "%[1]s" 144 | traffic_type_id = split_traffic_type.foobar.id 145 | identifier = "%[2]s" 146 | display_name = "%[4]s edited" 147 | description = "this is my attribute description + edited" 148 | data_type = "NUMBER" 149 | suggested_values = ["1", "2", "3"] 150 | is_searchable = true 151 | } 152 | `, workspaceID, attrIdentifier, ttName, attrName) 153 | } 154 | -------------------------------------------------------------------------------- /split/resource_split_traffic_type_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitTrafficType_Basic(t *testing.T) { 11 | workspaceID := testAccConfig.GetWorkspaceIDorSkip(t) 12 | name := fmt.Sprintf("tt-tftest-%s", acctest.RandString(10)) 13 | 14 | resource.Test(t, resource.TestCase{ 15 | PreCheck: func() { testAccPreCheck(t) }, 16 | ProviderFactories: testAccProviderFactories, 17 | Steps: []resource.TestStep{ 18 | { 19 | Config: testAccCheckSplitTrafficType_basic(workspaceID, name), 20 | Check: resource.ComposeTestCheckFunc( 21 | resource.TestCheckResourceAttr( 22 | "split_traffic_type.foobar", "workspace_id", workspaceID), 23 | resource.TestCheckResourceAttr( 24 | "split_traffic_type.foobar", "name", name), 25 | resource.TestCheckResourceAttrSet( 26 | "split_traffic_type.foobar", "type"), 27 | resource.TestCheckResourceAttrSet( 28 | "split_traffic_type.foobar", "display_attribute_id"), 29 | ), 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | func testAccCheckSplitTrafficType_basic(workspaceID, name string) string { 36 | return fmt.Sprintf(` 37 | provider "split" { 38 | remove_environment_from_state_only = true 39 | } 40 | 41 | resource "split_traffic_type" "foobar" { 42 | workspace_id = "%s" 43 | name = "%s" 44 | } 45 | `, workspaceID, name) 46 | } 47 | -------------------------------------------------------------------------------- /split/resource_split_user.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "log" 10 | ) 11 | 12 | func resourceSplitUser() *schema.Resource { 13 | return &schema.Resource{ 14 | CreateContext: resourceSplitUserCreate, 15 | ReadContext: resourceSplitUserRead, 16 | UpdateContext: resourceSplitUserUpdate, 17 | DeleteContext: resourceSplitUserDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: schema.ImportStatePassthroughContext, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "email": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | }, 28 | 29 | "name": { 30 | Type: schema.TypeString, 31 | Computed: true, 32 | }, 33 | 34 | "2fa": { 35 | Type: schema.TypeBool, 36 | Computed: true, 37 | }, 38 | 39 | "status": { 40 | Type: schema.TypeString, 41 | Computed: true, 42 | }, 43 | }, 44 | } 45 | } 46 | 47 | func resourceSplitUserCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 48 | var diags diag.Diagnostics 49 | client := meta.(*Config).API 50 | opts := &api.UserCreateRequest{} 51 | 52 | if v, ok := d.GetOk("email"); ok { 53 | opts.Email = v.(string) 54 | log.Printf("[DEBUG] new user email is : %v", opts.Email) 55 | } 56 | 57 | log.Printf("[DEBUG] Inviting user %s", opts.Email) 58 | 59 | u, _, inviteErr := client.Users.Invite(opts) 60 | if inviteErr != nil { 61 | diags = append(diags, diag.Diagnostic{ 62 | Severity: diag.Error, 63 | Summary: fmt.Sprintf("Unable to create/invite user %v", opts.Email), 64 | Detail: inviteErr.Error(), 65 | }) 66 | return diags 67 | } 68 | 69 | log.Printf("[DEBUG] Invited user %s", opts.Email) 70 | 71 | d.SetId(u.GetID()) 72 | 73 | return resourceSplitUserRead(ctx, d, meta) 74 | } 75 | 76 | func resourceSplitUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 77 | var diags diag.Diagnostics 78 | client := meta.(*Config).API 79 | 80 | u, _, getErr := client.Users.Get(d.Id()) 81 | if getErr != nil { 82 | diags = append(diags, diag.Diagnostic{ 83 | Severity: diag.Error, 84 | Summary: fmt.Sprintf("Unable to fetch user %v", d.Id()), 85 | Detail: getErr.Error(), 86 | }) 87 | return diags 88 | } 89 | 90 | d.Set("email", u.GetEmail()) 91 | d.Set("name", u.GetName()) 92 | d.Set("2fa", u.GetTFA()) 93 | d.Set("status", u.GetStatus()) 94 | 95 | return diags 96 | } 97 | 98 | func resourceSplitUserUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 99 | var diags diag.Diagnostics 100 | client := meta.(*Config).API 101 | opts := &api.UserUpdateRequest{} 102 | 103 | if v, ok := d.GetOk("name"); ok { 104 | opts.Name = v.(string) 105 | log.Printf("[DEBUG] updated user name is : %v", opts.Name) 106 | } 107 | 108 | _, _, updateErr := client.Users.Update(d.Id(), opts) 109 | if updateErr != nil { 110 | diags = append(diags, diag.Diagnostic{ 111 | Severity: diag.Error, 112 | Summary: fmt.Sprintf("Unable to update user %v", opts.Email), 113 | Detail: updateErr.Error(), 114 | }) 115 | return diags 116 | } 117 | 118 | return resourceSplitUserRead(ctx, d, meta) 119 | } 120 | 121 | func resourceSplitUserDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 122 | var diags diag.Diagnostics 123 | client := meta.(*Config).API 124 | 125 | // Check the status of the user prior to deletion. 126 | u, _, getErr := client.Users.Get(d.Id()) 127 | if getErr != nil { 128 | diags = append(diags, diag.Diagnostic{ 129 | Severity: diag.Error, 130 | Summary: fmt.Sprintf("Unable to fetch user %v for deletion", d.Id()), 131 | Detail: getErr.Error(), 132 | }) 133 | return diags 134 | } 135 | 136 | // If the user's status is 'PENDING', delete the invitation. 137 | if u.GetStatus() == api.UserStatusPending { 138 | log.Printf("[DEBUG] Deleting invitation for user %s", d.Id()) 139 | 140 | _, deleteErr := client.Users.DeletePendingUser(d.Id()) 141 | if deleteErr != nil { 142 | diags = append(diags, diag.Diagnostic{ 143 | Severity: diag.Error, 144 | Summary: fmt.Sprintf("Unable to delete invitation for user %v", d.Id()), 145 | Detail: deleteErr.Error(), 146 | }) 147 | return diags 148 | } 149 | 150 | log.Printf("[DEBUG] Deleted invitation for user %s", d.Id()) 151 | } 152 | 153 | // If the user's status is 'ACTIVE', deactivate the user. 154 | if u.GetStatus() == api.UserStatusActive { 155 | log.Printf("[DEBUG] Disabling user %s", d.Id()) 156 | 157 | _, _, deleteErr := client.Users.Update(d.Id(), &api.UserUpdateRequest{Status: api.UserStatusDeactivated}) 158 | if deleteErr != nil { 159 | diags = append(diags, diag.Diagnostic{ 160 | Severity: diag.Error, 161 | Summary: fmt.Sprintf("Unable to disable user %v", d.Id()), 162 | Detail: deleteErr.Error(), 163 | }) 164 | return diags 165 | } 166 | 167 | log.Printf("[DEBUG] Disabled user %s", d.Id()) 168 | } 169 | 170 | if u.GetStatus() != api.UserStatusActive && u.GetStatus() != api.UserStatusPending { 171 | diags = append(diags, diag.Diagnostic{ 172 | Severity: diag.Error, 173 | Summary: fmt.Sprintf("Unable to disable/delete user %v", d.Id()), 174 | Detail: fmt.Sprintf("unsupported user status: %s. Expected 'PENDING' or 'ACTIVE'", u.GetStatus()), 175 | }) 176 | return diags 177 | } 178 | 179 | d.SetId("") 180 | 181 | return diags 182 | } 183 | -------------------------------------------------------------------------------- /split/resource_split_user_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestAccSplitUser_Basic(t *testing.T) { 12 | email := testAccConfig.GetUserEmailorSkip(t) 13 | emailSplit := strings.Split(email, "@") 14 | emailFormatted := fmt.Sprintf("%s+%s@%s", emailSplit[0], acctest.RandString(8), emailSplit[1]) 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | ProviderFactories: testAccProviderFactories, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckSplitUser_basic(emailFormatted), 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttr( 24 | "split_user.foobar", "email", emailFormatted), 25 | resource.TestCheckResourceAttr( 26 | "split_user.foobar", "2fa", "false"), 27 | resource.TestCheckResourceAttr( 28 | "split_user.foobar", "status", "PENDING"), 29 | resource.TestCheckResourceAttr( 30 | "split_user.foobar", "name", strings.Split(emailFormatted, "@")[0]), 31 | ), 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | func testAccCheckSplitUser_basic(email string) string { 38 | return fmt.Sprintf(` 39 | resource "split_user" "foobar" { 40 | email = "%s" 41 | } 42 | `, email) 43 | } 44 | -------------------------------------------------------------------------------- /split/resource_split_workspace.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/davidji99/terraform-provider-split/api" 7 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "log" 10 | ) 11 | 12 | func resourceSplitWorkspace() *schema.Resource { 13 | return &schema.Resource{ 14 | CreateContext: resourceSplitWorkspaceCreate, 15 | ReadContext: resourceSplitWorkspaceRead, 16 | UpdateContext: resourceSplitWorkspaceUpdate, 17 | DeleteContext: resourceSplitWorkspaceDelete, 18 | 19 | Importer: &schema.ResourceImporter{ 20 | StateContext: resourceSplitWorkspaceImport, 21 | }, 22 | 23 | Schema: map[string]*schema.Schema{ 24 | "name": { 25 | Type: schema.TypeString, 26 | Required: true, 27 | }, 28 | 29 | "require_title_comments": { 30 | Type: schema.TypeBool, 31 | Optional: true, 32 | Default: false, 33 | }, 34 | }, 35 | } 36 | } 37 | 38 | func resourceSplitWorkspaceImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { 39 | client := meta.(*Config).API 40 | 41 | w, _, getErr := client.Workspaces.FindByName(d.Id()) 42 | if getErr != nil { 43 | return nil, getErr 44 | } 45 | 46 | d.SetId(w.GetID()) 47 | d.Set("name", w.GetName()) 48 | d.Set("require_title_comments", w.GetRequiresTitleAndComments()) 49 | 50 | return []*schema.ResourceData{d}, nil 51 | } 52 | 53 | func resourceSplitWorkspaceCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 54 | var diags diag.Diagnostics 55 | client := meta.(*Config).API 56 | opts := &api.WorkspaceRequest{} 57 | 58 | if v, ok := d.GetOk("name"); ok { 59 | vs := v.(string) 60 | opts.Name = &vs 61 | log.Printf("[DEBUG] new workspace name is : %s", *opts.Name) 62 | } 63 | 64 | rtc := d.Get("require_title_comments").(bool) 65 | opts.RequiresTitleAndComments = &rtc 66 | log.Printf("[DEBUG] new workspace require_title_comments is : %v", *opts.RequiresTitleAndComments) 67 | 68 | log.Printf("[DEBUG] Creating new workspace %v", opts.Name) 69 | 70 | w, _, createErr := client.Workspaces.Create(opts) 71 | if createErr != nil { 72 | diags = append(diags, diag.Diagnostic{ 73 | Severity: diag.Error, 74 | Summary: fmt.Sprintf("Unable to create workspace %v", opts.Name), 75 | Detail: createErr.Error(), 76 | }) 77 | return diags 78 | } 79 | 80 | log.Printf("[DEBUG] Created new workspace %v", w.GetName()) 81 | 82 | d.SetId(w.GetID()) 83 | d.Set("name", w.GetName()) 84 | 85 | return resourceSplitWorkspaceRead(ctx, d, meta) 86 | } 87 | 88 | func resourceSplitWorkspaceUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 89 | var diags diag.Diagnostics 90 | client := meta.(*Config).API 91 | 92 | opts := &api.WorkspaceRequest{} 93 | 94 | if ok := d.HasChange("name"); ok { 95 | vs := d.Get("name").(string) 96 | opts.Name = &vs 97 | log.Printf("[DEBUG] updated workspace name is : %s", *opts.Name) 98 | } 99 | 100 | if ok := d.HasChange("require_title_comments"); ok { 101 | vs := d.Get("require_title_comments").(bool) 102 | opts.RequiresTitleAndComments = &vs 103 | log.Printf("[DEBUG] updated workspace require_title_comments is : %v", *opts.RequiresTitleAndComments) 104 | } 105 | 106 | log.Printf("[DEBUG] Updating workspace %v", d.Id()) 107 | 108 | _, _, updateErr := client.Workspaces.Update(d.Id(), opts) 109 | if updateErr != nil { 110 | diags = append(diags, diag.Diagnostic{ 111 | Severity: diag.Error, 112 | Summary: fmt.Sprintf("Unable to update workspace %v", d.Id()), 113 | Detail: updateErr.Error(), 114 | }) 115 | return diags 116 | } 117 | 118 | log.Printf("[DEBUG] Updated workspace %v", d.Id()) 119 | 120 | return resourceSplitWorkspaceRead(ctx, d, meta) 121 | } 122 | 123 | func resourceSplitWorkspaceRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 124 | var diags diag.Diagnostics 125 | client := meta.(*Config).API 126 | 127 | workspace, _, findErr := client.Workspaces.FindByName(d.Get("name").(string)) 128 | if findErr != nil { 129 | diags = append(diags, diag.Diagnostic{ 130 | Severity: diag.Error, 131 | Summary: fmt.Sprintf("unable to fetch workspace %s", d.Id()), 132 | Detail: findErr.Error(), 133 | }) 134 | return diags 135 | } 136 | 137 | d.Set("name", workspace.GetName()) 138 | d.Set("require_title_comments", workspace.GetRequiresTitleAndComments()) 139 | 140 | return diags 141 | } 142 | 143 | func resourceSplitWorkspaceDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { 144 | client := meta.(*Config).API 145 | var diags diag.Diagnostics 146 | 147 | // You can't delete a workspace if the workspace has child traffic types. By default, a `user` traffic type 148 | // is created with a new workspace. Therefore, we need to first delete the workspace traffic types before 149 | // deleting the workspace itself. 150 | log.Printf("[DEBUG] Finding traffic types prior to workspace %s deletion", d.Id()) 151 | 152 | trafficTypes, _, listErr := client.TrafficTypes.List(d.Id()) 153 | if listErr != nil { 154 | diags = append(diags, diag.Diagnostic{ 155 | Severity: diag.Error, 156 | Summary: fmt.Sprintf("unable to lookup traffic types for workspace %s", d.Id()), 157 | Detail: listErr.Error(), 158 | }) 159 | return diags 160 | } 161 | 162 | log.Printf("[DEBUG] Deleting all traffic types associated with workspace %s", d.Id()) 163 | for _, tt := range trafficTypes { 164 | log.Printf("[DEBUG] Deleting traffic type %s associated with workspace %s", tt.GetID(), d.Id()) 165 | _, deleteErr := client.TrafficTypes.Delete(tt.GetID()) 166 | if deleteErr != nil { 167 | diags = append(diags, diag.Diagnostic{ 168 | Severity: diag.Error, 169 | Summary: fmt.Sprintf("unable to delete traffic type %s", d.Id()), 170 | Detail: deleteErr.Error(), 171 | }) 172 | return diags 173 | } 174 | } 175 | log.Printf("[DEBUG] Deleted all traffic types associated with workspace %s", d.Id()) 176 | 177 | log.Printf("[DEBUG] Deleting workspace %s", d.Id()) 178 | _, deleteErr := client.Workspaces.Delete(d.Id()) 179 | if deleteErr != nil { 180 | diags = append(diags, diag.Diagnostic{ 181 | Severity: diag.Error, 182 | Summary: fmt.Sprintf("unable to delete workspace %s", d.Id()), 183 | Detail: deleteErr.Error(), 184 | }) 185 | return diags 186 | } 187 | 188 | log.Printf("[DEBUG] Deleted workspace %s", d.Id()) 189 | 190 | d.SetId("") 191 | 192 | return diags 193 | } 194 | -------------------------------------------------------------------------------- /split/resource_split_workspace_test.go: -------------------------------------------------------------------------------- 1 | package split 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | "testing" 8 | ) 9 | 10 | func TestAccSplitWorkspace_Basic(t *testing.T) { 11 | name := fmt.Sprintf("w-tftest-%s", acctest.RandString(10)) 12 | 13 | resource.Test(t, resource.TestCase{ 14 | PreCheck: func() { testAccPreCheck(t) }, 15 | ProviderFactories: testAccProviderFactories, 16 | Steps: []resource.TestStep{ 17 | { 18 | Config: testAccCheckSplitWorkspace_basic(name, "true"), 19 | Check: resource.ComposeTestCheckFunc( 20 | resource.TestCheckResourceAttr( 21 | "split_workspace.foobar", "require_title_comments", "true"), 22 | resource.TestCheckResourceAttr( 23 | "split_workspace.foobar", "name", name), 24 | ), 25 | }, 26 | { 27 | Config: testAccCheckSplitWorkspace_basic(name+"edited", "false"), 28 | Check: resource.ComposeTestCheckFunc( 29 | resource.TestCheckResourceAttr( 30 | "split_workspace.foobar", "require_title_comments", "false"), 31 | resource.TestCheckResourceAttr( 32 | "split_workspace.foobar", "name", name+"edited"), 33 | ), 34 | }, 35 | }, 36 | }) 37 | } 38 | 39 | func testAccCheckSplitWorkspace_basic(name, requireTitleComments string) string { 40 | return fmt.Sprintf(` 41 | provider "split" { 42 | remove_environment_from_state_only = true 43 | } 44 | 45 | resource "split_workspace" "foobar" { 46 | name = "%s" 47 | require_title_comments = %s 48 | } 49 | `, name, requireTitleComments) 50 | } 51 | -------------------------------------------------------------------------------- /split/version.go: -------------------------------------------------------------------------------- 1 | //go:build ignore 2 | // +build ignore 3 | 4 | // This a hack to populate the version in the custom binary file as this provider is not official. 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | "github.com/davidji99/terraform-provider-split/version" 11 | ) 12 | 13 | var ver = version.ProviderVersion 14 | 15 | func main() { 16 | fmt.Println(ver) 17 | } 18 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | //Cribbed from 4 | //https://github.com/terraform-providers/terraform-provider-azurerm/tree/master/version 5 | //This takes advantage of a new build flag populating the binary version of the 6 | //provider, for example: 7 | //-ldflags="-X=github.com/davidji99/terraform-provider-split/version.ProviderVersion=x.x.x" 8 | 9 | var ( 10 | // ProviderVersion is set during the release process to the release version of the binary, and 11 | // set to acc during tests. 12 | ProviderVersion = "dev" 13 | ) 14 | --------------------------------------------------------------------------------