├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE.md ├── SUPPORT.md └── workflows │ ├── acceptance-tests.yml │ ├── golangci-lint.yml │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── .markdownlint.yaml ├── .pre-commit-config.yaml ├── CODEOWNERS ├── LICENSE ├── README.md ├── cloudsmith ├── data_entitlement.go ├── data_entitlement_test.go ├── data_package_deny_policy.go ├── data_source_namespace.go ├── data_source_organization.go ├── data_source_organization_member_details.go ├── data_source_organization_member_details_test.go ├── data_source_organization_members.go ├── data_source_organization_members_test.go ├── data_source_organization_test.go ├── data_source_package.go ├── data_source_package_list.go ├── data_source_package_test.go ├── data_source_repository.go ├── data_source_repository_privileges.go ├── data_source_repository_privileges_test.go ├── data_source_repository_test.go ├── data_source_user_self.go ├── data_source_user_self_test.go ├── provider.go ├── provider_config.go ├── provider_test.go ├── resource_entitlement.go ├── resource_entitlement_control.go ├── resource_entitlement_control_test.go ├── resource_entitlement_test.go ├── resource_license_policy.go ├── resource_license_policy_test.go ├── resource_manage_team.go ├── resource_manage_team_test.go ├── resource_oidc.go ├── resource_oidc_test.go ├── resource_package_deny_policy.go ├── resource_package_deny_policy_test.go ├── resource_repository.go ├── resource_repository_geo_ip_rules.go ├── resource_repository_geo_ip_rules_test.go ├── resource_repository_privileges.go ├── resource_repository_privileges_test.go ├── resource_repository_retention_rule.go ├── resource_repository_retention_rule_test.go ├── resource_repository_test.go ├── resource_repository_upstream.go ├── resource_repository_upstream_test.go ├── resource_saml.go ├── resource_saml_auth.go ├── resource_saml_auth_test.go ├── resource_saml_test.go ├── resource_service.go ├── resource_service_test.go ├── resource_team.go ├── resource_team_test.go ├── resource_vulnerability_policy.go ├── resource_vulnerability_policy_test.go ├── resource_webhook.go ├── resource_webhook_test.go └── utils.go ├── docs ├── data-sources │ ├── entitlement_list.md │ ├── namespace.md │ ├── organization.md │ ├── organization_list_org_members.md │ ├── organization_member_details.md │ ├── package.md │ ├── package_list.md │ ├── repository.md │ ├── repository_privileges.md │ └── user_self.md ├── index.md └── resources │ ├── entitlement.md │ ├── entitlement_control.md │ ├── license_policy.md │ ├── manage_team.md │ ├── oidc.md │ ├── package_deny_policy.md │ ├── repository.md │ ├── repository_geo_ip_rules.md │ ├── repository_privileges.md │ ├── repository_retention.md │ ├── repository_upstream.md │ ├── saml_auth.md │ ├── saml_group_sync.md │ ├── service.md │ ├── team.md │ ├── vulnerability_policy.md │ └── webhook.md ├── examples ├── flat-example │ ├── README.md │ ├── global-variables.tf │ ├── org-deny-policy.tf │ ├── org-license-policy.tf │ ├── org-oidc-config.tf │ ├── org-saml-group.tf │ ├── org-services.tf │ ├── org-teams.tf │ ├── org-vulnerability-policy.tf │ ├── provider.tf │ ├── repo-devops.tf │ ├── repo-oidc-demo.tf │ ├── repo-staging.tf │ └── repo-upstreams.tf └── iterative-example │ ├── README.md │ ├── global-variables.tf │ ├── license-policy.tf │ ├── oidc-config.tf │ ├── provider.tf │ ├── repositories.tf │ ├── services.tf │ ├── teams.tf │ ├── terraform.tfvars │ ├── upstreams.tf │ └── vulnerability-policy.tf ├── go.mod ├── go.sum └── main.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 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Hi there, 2 | 3 | Thank you for opening an issue. Please note that we try to keep the Terraform issue tracker reserved for bug reports and feature requests. For general usage questions, please see: . 4 | 5 | ### Terraform Version 6 | 7 | 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. 8 | 9 | ### Affected Resource(s) 10 | 11 | Please list the resources as a list, for example: 12 | 13 | - opc_instance 14 | - opc_storage_volume 15 | 16 | If this issue appears to affect multiple resources, it may be an issue with Terraform's core, so please mention this. 17 | 18 | ### Terraform Configuration Files 19 | 20 | ```hcl 21 | # Copy-paste your Terraform configurations here - for large Terraform configs, 22 | # please use a service like Dropbox and share a link to the ZIP file. For 23 | # security, you can also encrypt the files using our GPG public key. 24 | ``` 25 | 26 | ### Debug Output 27 | 28 | Please provider a link to a GitHub Gist containing the complete debug output: . Please do NOT paste the debug output in the issue; just paste a link to the Gist. 29 | 30 | ### Panic Output 31 | 32 | If Terraform produced a panic, please provide a link to a GitHub Gist containing the output of the `crash.log`. 33 | 34 | ### Expected Behavior 35 | 36 | What should have happened? 37 | 38 | ### Actual Behavior 39 | 40 | What actually happened? 41 | 42 | ### Steps to Reproduce 43 | 44 | Please list the steps required to reproduce the issue, for example: 45 | 46 | 1. `terraform apply` 47 | 48 | ### Important Factoids 49 | 50 | Are there anything atypical about your accounts that we should know? For example: Running in EC2 Classic? Custom version of OpenStack? Tight ACLs? 51 | 52 | ### References 53 | 54 | Are there any other GitHub issues (open or closed) or Pull Requests that should be linked here? For example: 55 | 56 | - GH-1234 57 | -------------------------------------------------------------------------------- /.github/SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support 2 | 3 | Terraform is a mature project with a growing community. There are active, dedicated people willing to help you through various mediums. 4 | 5 | Take a look at those mediums listed at 6 | -------------------------------------------------------------------------------- /.github/workflows/acceptance-tests.yml: -------------------------------------------------------------------------------- 1 | name: Acceptance 2 | 3 | on: 4 | push: 5 | branches: 6 | - "*" 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | name: Tests 12 | runs-on: ubuntu-latest 13 | concurrency: 14 | group: global 15 | timeout-minutes: 60 16 | steps: 17 | - name: Install Go 1.19 18 | uses: actions/setup-go@v3 19 | with: 20 | go-version: 1.19 21 | id: go 22 | 23 | - uses: actions/checkout@v3 24 | 25 | - name: Run tests 26 | run: | 27 | TF_ACC=1 go test -v ./... -parallel=32 -timeout=30m 28 | env: 29 | CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }} 30 | CLOUDSMITH_NAMESPACE: terraform-provider-testing 31 | 32 | - name: Notify Slack on Success 33 | if: success() 34 | uses: rtCamp/action-slack-notify@v2.0.2 35 | env: 36 | SLACK_ICON: https://avatars0.githubusercontent.com/u/44036562?s=200&v=4 37 | SLACK_TITLE: Workflow Succeeded 38 | SLACK_USERNAME: Github Actions 39 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 40 | 41 | - name: Notify Slack on Failure 42 | if: failure() 43 | uses: rtCamp/action-slack-notify@v2.0.2 44 | env: 45 | SLACK_COLOR: danger 46 | SLACK_ICON: https://avatars0.githubusercontent.com/u/44036562?s=200&v=4 47 | SLACK_TITLE: Workflow Failed 48 | SLACK_USERNAME: Github Actions 49 | SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} 50 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - "*" 8 | pull_request: 9 | permissions: 10 | contents: read 11 | jobs: 12 | golangci: 13 | name: lint 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/setup-go@v3 17 | with: 18 | go-version: 1.19 19 | - uses: actions/checkout@v3 20 | - name: golangci-lint 21 | uses: golangci/golangci-lint-action@v3 22 | with: 23 | version: v1.50.1 24 | args: --timeout=5m --exclude "Error return value of .(d.Set). is not checked" 25 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | tags: 7 | - 'v*' 8 | 9 | jobs: 10 | goreleaser: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: write 14 | pull-requests: write 15 | repository-projects: write 16 | steps: 17 | - 18 | name: Checkout 19 | uses: actions/checkout@v4 20 | - 21 | name: Unshallow 22 | run: git fetch --prune --unshallow 23 | - 24 | name: Set up Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: 1.18 28 | - 29 | name: Import GPG key 30 | id: import_gpg 31 | uses: crazy-max/ghaction-import-gpg@v6 32 | with: 33 | gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} 34 | passphrase: ${{ secrets.PASSPHRASE }} 35 | - 36 | name: Run GoReleaser 37 | uses: goreleaser/goreleaser-action@v6 38 | with: 39 | args: release --clean 40 | env: 41 | GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} 42 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .terraform/ 3 | .terraform.lock.hcl 4 | main.tf 5 | terraform.tfstate 6 | terraform.tfstate.backup 7 | terraform-provider-cloudsmith 8 | dist 9 | 10 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | builds: 4 | - env: 5 | - CGO_ENABLED=0 6 | mod_timestamp: '{{ .CommitTimestamp }}' 7 | flags: 8 | - -trimpath 9 | ldflags: 10 | - '-s -w -X main.version={{.Version}} -X main.commit={{.Commit}}' 11 | goos: 12 | - freebsd 13 | - windows 14 | - linux 15 | - darwin 16 | goarch: 17 | - amd64 18 | - '386' 19 | - arm 20 | - arm64 21 | ignore: 22 | - goos: darwin 23 | goarch: '386' 24 | binary: '{{ .ProjectName }}_v{{ .Version }}' 25 | 26 | archives: 27 | - format: zip 28 | name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' 29 | 30 | checksum: 31 | name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' 32 | algorithm: sha256 33 | 34 | signs: 35 | - artifacts: checksum 36 | args: 37 | - "--batch" 38 | - "--local-user" 39 | - "{{ .Env.GPG_FINGERPRINT }}" 40 | - "--output" 41 | - "${signature}" 42 | - "--detach-sign" 43 | - "${artifact}" 44 | 45 | changelog: 46 | disable: true 47 | -------------------------------------------------------------------------------- /.markdownlint.yaml: -------------------------------------------------------------------------------- 1 | MD010: false 2 | MD013: false 3 | MD033: false 4 | MD003: false 5 | MD045: false 6 | MD041: false 7 | MD025: false 8 | MD040: false 9 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/dnephin/pre-commit-golang 3 | rev: v0.5.1 4 | hooks: 5 | - id: go-fmt 6 | name: Format Go code 7 | - id: go-vet 8 | name: Verify Go code 9 | - id: golangci-lint 10 | name: Run golangci-lint 11 | args: 12 | - --exclude 13 | - "Error return value of .(d.Set). is not checked" 14 | 15 | - repo: https://github.com/antonbabenko/pre-commit-terraform 16 | rev: v1.83.5 17 | hooks: 18 | - id: terraform_fmt 19 | name: Format Terraform code 20 | - id: terraform_validate 21 | name: Validate Terraform code 22 | - id: terraform_tflint 23 | name: Lint Terraform code 24 | 25 | - repo: https://github.com/igorshubovych/markdownlint-cli 26 | rev: v0.37.0 27 | hooks: 28 | - id: markdownlint 29 | name: Lint Markdown files 30 | args: [--config=.markdownlint.yaml] 31 | 32 | - repo: https://github.com/pre-commit/pre-commit-hooks 33 | rev: v4.5.0 34 | hooks: 35 | - id: trailing-whitespace 36 | name: Trim trailing whitespace 37 | - id: end-of-file-fixer 38 | name: Fix end of files 39 | - id: check-yaml 40 | name: Check YAML files 41 | - id: mixed-line-ending 42 | name: Fix line endings 43 | - id: check-merge-conflict 44 | name: Check for merge conflicts 45 | - id: detect-private-key 46 | name: Detect private keys 47 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @cloudsmith-io/engineering 2 | * @cloudsmith-io/customer-engineering 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Terraform Provider for Cloudsmith 2 | ================================= 3 | 4 | ![](https://cloudsmith.com/images/uploads/resources/cloudsmith-logo-master-color.svg) 5 | 6 | Terraform provider for managing your Cloudsmith resources. 7 | 8 | Requirements 9 | ------------ 10 | 11 | - [Terraform](https://www.terraform.io/downloads.html) >= 0.12.x 12 | - [Go](https://golang.org/doc/install) >= 1.13 (to build the provider plugin) 13 | 14 | Building The Provider 15 | --------------------- 16 | 17 | Clone repository: 18 | 19 | ```sh 20 | git clone git@github.com:cloudsmith-io/terraform-provider-cloudsmith 21 | ``` 22 | 23 | Enter the provider directory and build the provider: 24 | 25 | ```sh 26 | cd terraform-provider-cloudsmith 27 | go build 28 | ``` 29 | 30 | Using the provider 31 | ------------------ 32 | 33 | To use a released provider in your Terraform environment, run [`terraform init`](https://www.terraform.io/docs/commands/init.html) and Terraform will automatically install the provider. To specify a particular provider version when installing released providers, see the [Terraform documentation on provider versioning](https://www.terraform.io/docs/configuration/providers.html#version-provider-versions). 34 | 35 | To instead use a custom-built provider in your Terraform environment (e.g. the provider binary from the build instructions above), follow the instructions to [install it as a plugin.](https://www.terraform.io/docs/plugins/basics.html#installing-plugins) After placing the custom-built provider into your plugins directory, run `terraform init` to initialize it. 36 | 37 | ### Examples 38 | 39 | Create a repository with a custom entitlement token 40 | 41 | ``` 42 | provider "cloudsmith" { 43 | api_key = "my-api-key" 44 | } 45 | 46 | data "cloudsmith_namespace" "my_namespace" { 47 | slug = "my-namespace" 48 | } 49 | 50 | resource "cloudsmith_repository" "my_repository" { 51 | description = "A certifiably-awesome private package repository" 52 | name = "My Repository" 53 | namespace = data.cloudsmith_namespace.my_namespace.slug_perm 54 | slug = "my-repository" 55 | } 56 | 57 | resource "cloudsmith_entitlement" "my_entitlement" { 58 | name = "Test Entitlement" 59 | namespace = cloudsmith_repository.test.namespace 60 | repository = cloudsmith_repository.test.slug_perm 61 | } 62 | ``` 63 | 64 | Retrieve a list of packages from a repository 65 | 66 | ``` 67 | provider "cloudsmith" { 68 | api_key = "my-api-key" 69 | } 70 | 71 | data "cloudsmith_namespace" "my_namespace" { 72 | slug = "my-namespace" 73 | } 74 | 75 | data "cloudsmith_repository" "my_repository" { 76 | namespace = data.cloudsmith_namespace.my_namespace.slug 77 | identifier = "my-repository" 78 | } 79 | 80 | data "cloudsmith_package_list" "my_packages" { 81 | namespace = data.cloudsmith_repository.my_repository.namespace 82 | repository = data.cloudsmith_repository.my_repository.slug_perm 83 | filters = ["format:docker", "name:^my-package"] 84 | } 85 | 86 | output "packages" { 87 | value = formatlist("%s-%s", data.cloudsmith_package_list.my_packages.packages.*.name, data.cloudsmith_package_list.my_packages.packages.*.version) 88 | } 89 | ``` 90 | 91 | Testing the Provider 92 | ----------------------- 93 | 94 | In order to test the provider, you can run `go test`. 95 | 96 | ```sh 97 | go test -v ./... 98 | ``` 99 | 100 | In order to run the full suite of Acceptance tests, you'll need a paid Cloudsmith account. 101 | 102 | You'll also need to set a few environment variables: 103 | 104 | - `TF_ACC=1`: Used to enable acceptance tests during `go test`. 105 | - `CLOUDSMITH_API_KEY`: API key used to manage resources during test runs. 106 | - `CLOUDSMITH_NAMESPACE`: Cloudsmith namespace in which to create and destroy resources under test. 107 | 108 | *Note:* Acceptance tests create real resources, and may cost money to run. 109 | 110 | ```sh 111 | export TF_ACC=1 112 | export CLOUDSMITH_API_KEY=mykey 113 | export CLOUDSMITH_NAMESPACE=mynamespace 114 | go test -v ./... 115 | ``` 116 | 117 | If needed, you can also run individual tests with the `-run` flag: 118 | 119 | ```sh 120 | go test -v -run=TestAccEntitlement_basic ./... 121 | ``` 122 | -------------------------------------------------------------------------------- /cloudsmith/data_entitlement_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | ) 11 | 12 | // TestAccEntitlementTokenList_data spins up an entitlement token with all default options, 13 | // verifies it exists, then reads the same entitlement token using a data source and 14 | // verifies that the expected fields are set with default values. 15 | func TestAccDataSourceEntitlementTokenList(t *testing.T) { 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | Providers: testAccProviders, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccDataSourceEntitlementTokenListConfig, 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttrSet("cloudsmith_entitlement.test", "name"), 24 | ), 25 | }, 26 | }, 27 | }) 28 | } 29 | 30 | var testAccDataSourceEntitlementTokenListConfig = fmt.Sprintf(` 31 | resource "cloudsmith_repository" "test" { 32 | name = "terraform-acc-test-ent-list" 33 | namespace = "%s" 34 | } 35 | 36 | resource "cloudsmith_entitlement" "test" { 37 | name = "Test Entitlement" 38 | namespace = "${cloudsmith_repository.test.namespace}" 39 | repository = "${cloudsmith_repository.test.slug_perm}" 40 | } 41 | data "cloudsmith_entitlement_list" "test" { 42 | query = ["name:Test Entitlement"] 43 | repository = "${cloudsmith_repository.test.slug_perm}" 44 | namespace = "%s" 45 | } 46 | `, os.Getenv("CLOUDSMITH_NAMESPACE"), os.Getenv("CLOUDSMITH_NAMESPACE")) 47 | -------------------------------------------------------------------------------- /cloudsmith/data_package_deny_policy.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 6 | ) 7 | 8 | func dataSourcePackageDenyPolicyRead(d *schema.ResourceData, m interface{}) error { 9 | pc := m.(*providerConfig) 10 | slugPerm := requiredString(d, "slug_perm") 11 | namespace := requiredString(d, "namespace") 12 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyRead(pc.Auth, namespace, slugPerm) 13 | packageDenyPolicy, _, err := pc.APIClient.OrgsApi.OrgsDenyPolicyReadExecute(req) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | d.Set("name", packageDenyPolicy.GetName()) 19 | d.Set("description", packageDenyPolicy.GetDescription()) 20 | d.Set("package_query", packageDenyPolicy.GetPackageQueryString()) 21 | d.Set("enabled", packageDenyPolicy.GetEnabled()) 22 | d.SetId(packageDenyPolicy.GetSlugPerm()) 23 | 24 | return nil 25 | } 26 | 27 | //nolint:funlen 28 | func dataSourcePackageDenyPolicy() *schema.Resource { 29 | return &schema.Resource{ 30 | Read: dataSourcePackageDenyPolicyRead, 31 | 32 | Schema: map[string]*schema.Schema{ 33 | "name": { 34 | Type: schema.TypeString, 35 | Description: "A descriptive name for the package deny policy.", 36 | Optional: true, 37 | ValidateFunc: validation.StringIsNotEmpty, 38 | }, 39 | "description": { 40 | Type: schema.TypeString, 41 | Description: "Description of the package deny policy.", 42 | Optional: true, 43 | ValidateFunc: validation.StringIsNotEmpty, 44 | }, 45 | "package_query": { 46 | Type: schema.TypeString, 47 | Description: "The query to match the packages to be blocked.", 48 | Optional: true, 49 | ValidateFunc: validation.StringIsNotEmpty, 50 | }, 51 | "enabled": { 52 | Type: schema.TypeBool, 53 | Description: "Is the package deny policy enabled?.", 54 | Optional: true, 55 | Default: true, 56 | }, 57 | "namespace": { 58 | Type: schema.TypeString, 59 | Description: "Namespace to which this package deny policy belongs.", 60 | Required: true, 61 | ForceNew: true, 62 | ValidateFunc: validation.StringIsNotEmpty, 63 | }, 64 | "slug_perm": { 65 | Type: schema.TypeString, 66 | Description: "Identifier of the package deny policy.", 67 | Required: true, 68 | ForceNew: true, 69 | ValidateFunc: validation.StringIsNotEmpty, 70 | }, 71 | }, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /cloudsmith/data_source_namespace.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 6 | ) 7 | 8 | func dataSourceNamespaceRead(d *schema.ResourceData, m interface{}) error { 9 | pc := m.(*providerConfig) 10 | 11 | slug := requiredString(d, "slug") 12 | 13 | req := pc.APIClient.NamespacesApi.NamespacesRead(pc.Auth, slug) 14 | namespace, _, err := pc.APIClient.NamespacesApi.NamespacesReadExecute(req) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | d.SetId(namespace.GetSlugPerm()) 20 | d.Set("name", namespace.GetName()) 21 | d.Set("slug", namespace.GetSlug()) 22 | d.Set("slug_perm", namespace.GetSlugPerm()) 23 | d.Set("type_name", namespace.GetTypeName()) 24 | 25 | return nil 26 | } 27 | 28 | func dataSourceNamespace() *schema.Resource { 29 | return &schema.Resource{ 30 | DeprecationMessage: "use cloudsmith_organization data source instead", 31 | 32 | Read: dataSourceNamespaceRead, 33 | 34 | Schema: map[string]*schema.Schema{ 35 | "name": { 36 | Type: schema.TypeString, 37 | Description: "A descriptive name for the namespace.", 38 | Computed: true, 39 | }, 40 | "slug": { 41 | Type: schema.TypeString, 42 | Description: "The slug identifies the namespace in URIs.", 43 | Required: true, 44 | ValidateFunc: validation.StringIsNotEmpty, 45 | }, 46 | "slug_perm": { 47 | Type: schema.TypeString, 48 | Description: "The slug_perm immutably identifies the namespace. " + 49 | "It will never change once a namespace has been created.", 50 | Computed: true, 51 | }, 52 | "type_name": { 53 | Type: schema.TypeString, 54 | Description: "Is this a user or an organization namespace?", 55 | Computed: true, 56 | }, 57 | }, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /cloudsmith/data_source_organization.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 6 | ) 7 | 8 | func dataSourceOrganizationRead(d *schema.ResourceData, m interface{}) error { 9 | pc := m.(*providerConfig) 10 | slug := requiredString(d, "slug") 11 | 12 | req := pc.APIClient.OrgsApi.OrgsRead(pc.Auth, slug) 13 | organization, _, err := pc.APIClient.OrgsApi.OrgsReadExecute(req) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | d.Set("country", organization.GetCountry()) 19 | d.Set("created_at", timeToString(organization.GetCreatedAt())) 20 | d.Set("location", organization.GetLocation()) 21 | d.Set("name", organization.GetName()) 22 | d.Set("slug", organization.GetSlug()) 23 | d.Set("slug_perm", organization.GetSlugPerm()) 24 | d.Set("tagline", organization.GetTagline()) 25 | 26 | d.SetId(organization.GetSlugPerm()) 27 | 28 | return nil 29 | } 30 | 31 | //nolint:funlen 32 | func dataSourceOrganization() *schema.Resource { 33 | return &schema.Resource{ 34 | Read: dataSourceOrganizationRead, 35 | 36 | Schema: map[string]*schema.Schema{ 37 | "country": { 38 | Type: schema.TypeString, 39 | Description: "Country in which the organization is based.", 40 | Computed: true, 41 | }, 42 | "created_at": { 43 | Type: schema.TypeString, 44 | Description: "ISO 8601 timestamp at which the organization was created.", 45 | Computed: true, 46 | }, 47 | "location": { 48 | Type: schema.TypeString, 49 | Description: "The city/town/area in which the organization is based.", 50 | Computed: true, 51 | }, 52 | "name": { 53 | Type: schema.TypeString, 54 | Description: "A descriptive name for the organization.", 55 | Computed: true, 56 | }, 57 | "slug": { 58 | Type: schema.TypeString, 59 | Description: "The slug identifies the organization in URIs.", 60 | Required: true, 61 | ValidateFunc: validation.StringIsNotEmpty, 62 | }, 63 | "slug_perm": { 64 | Type: schema.TypeString, 65 | Description: "The slug_perm immutably identifies the organization. " + 66 | "It will never change once an organization has been created.", 67 | Computed: true, 68 | }, 69 | "tagline": { 70 | Type: schema.TypeString, 71 | Description: "A short public description for the organization.", 72 | Computed: true, 73 | }, 74 | }, 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /cloudsmith/data_source_organization_member_details.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | func dataSourceOrganizationMemberDetailsRead(d *schema.ResourceData, m interface{}) error { 10 | pc := m.(*providerConfig) 11 | organization := d.Get("organization").(string) 12 | member := d.Get("member").(string) 13 | 14 | req := pc.APIClient.OrgsApi.OrgsMembersRead(pc.Auth, organization, member) 15 | memberDetails, _, err := pc.APIClient.OrgsApi.OrgsMembersReadExecute(req) 16 | if err != nil { 17 | return err 18 | } 19 | 20 | d.Set("email", memberDetails.GetEmail()) 21 | d.Set("has_two_factor", memberDetails.GetHasTwoFactor()) 22 | d.Set("is_active", memberDetails.GetIsActive()) 23 | d.Set("joined_at", memberDetails.GetJoinedAt().Format(time.RFC3339)) 24 | d.Set("last_login_at", memberDetails.GetLastLoginAt().Format(time.RFC3339)) 25 | d.Set("last_login_method", memberDetails.GetLastLoginMethod()) 26 | d.Set("role", memberDetails.GetRole()) 27 | d.Set("user", memberDetails.GetUser()) 28 | d.Set("user_id", memberDetails.GetUserId()) 29 | d.Set("user_name", memberDetails.GetUserName()) 30 | d.Set("user_url", memberDetails.GetUserUrl()) 31 | d.Set("visibility", memberDetails.GetVisibility()) 32 | 33 | d.SetId(memberDetails.GetUserId()) 34 | 35 | return nil 36 | } 37 | 38 | func dataSourceMemberDetails() *schema.Resource { 39 | return &schema.Resource{ 40 | Read: dataSourceOrganizationMemberDetailsRead, 41 | 42 | Schema: map[string]*schema.Schema{ 43 | "organization": { 44 | Type: schema.TypeString, 45 | Required: true, 46 | }, 47 | "member": { 48 | Type: schema.TypeString, 49 | Required: true, 50 | }, 51 | "email": { 52 | Type: schema.TypeString, 53 | Computed: true, 54 | }, 55 | "has_two_factor": { 56 | Type: schema.TypeBool, 57 | Computed: true, 58 | }, 59 | "is_active": { 60 | Type: schema.TypeBool, 61 | Computed: true, 62 | }, 63 | "joined_at": { 64 | Type: schema.TypeString, 65 | Computed: true, 66 | }, 67 | "last_login_at": { 68 | Type: schema.TypeString, 69 | Computed: true, 70 | }, 71 | "last_login_method": { 72 | Type: schema.TypeString, 73 | Computed: true, 74 | }, 75 | "role": { 76 | Type: schema.TypeString, 77 | Computed: true, 78 | }, 79 | "user": { 80 | Type: schema.TypeString, 81 | Computed: true, 82 | }, 83 | "user_id": { 84 | Type: schema.TypeString, 85 | Computed: true, 86 | }, 87 | "user_name": { 88 | Type: schema.TypeString, 89 | Computed: true, 90 | }, 91 | "user_url": { 92 | Type: schema.TypeString, 93 | Computed: true, 94 | }, 95 | "visibility": { 96 | Type: schema.TypeString, 97 | Computed: true, 98 | }, 99 | }, 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /cloudsmith/data_source_organization_member_details_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpakcage 2 | 3 | package cloudsmith 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | ) 12 | 13 | func TestAccOrganizationMemberDetails_basic(t *testing.T) { 14 | t.Parallel() 15 | 16 | resource.Test(t, resource.TestCase{ 17 | PreCheck: func() { testAccPreCheck(t) }, 18 | Providers: testAccProviders, 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccCheckOrganizationMemberDetailsConfig(), 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttr("data.cloudsmith_org_member_details.test", "email", "bblizniak@cloudsmith.io"), 24 | resource.TestCheckResourceAttr("data.cloudsmith_org_member_details.test", "has_two_factor", "true"), 25 | resource.TestCheckResourceAttr("data.cloudsmith_org_member_details.test", "is_active", "true"), 26 | resource.TestCheckResourceAttr("data.cloudsmith_org_member_details.test", "role", "Owner"), 27 | ), 28 | }, 29 | }, 30 | }) 31 | } 32 | 33 | func testAccCheckOrganizationMemberDetailsConfig() string { 34 | return fmt.Sprintf(` 35 | 36 | data "cloudsmith_org_member_details" "test" { 37 | organization = "%s" 38 | member = "bblizniak" 39 | } 40 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 41 | } 42 | -------------------------------------------------------------------------------- /cloudsmith/data_source_organization_members.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "time" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | 10 | "github.com/cloudsmith-io/cloudsmith-api-go" 11 | ) 12 | 13 | func retrieveOrgMemeberListPage(pc *providerConfig, organization string, isActive bool, pageSize int64, pageCount int64) ([]cloudsmith.OrganizationMembership, int64, error) { 14 | req := pc.APIClient.OrgsApi.OrgsMembersList(pc.Auth, organization) 15 | req = req.Page(pageCount) 16 | req = req.PageSize(pageSize) 17 | req = req.IsActive(isActive) 18 | 19 | membersPage, httpResponse, err := pc.APIClient.OrgsApi.OrgsMembersListExecute(req) 20 | if err != nil { 21 | return nil, 0, err 22 | } 23 | pageTotal, err := strconv.ParseInt(httpResponse.Header.Get("X-Pagination-Pagetotal"), 10, 64) 24 | if err != nil { 25 | return nil, 0, err 26 | } 27 | return membersPage, pageTotal, nil 28 | } 29 | 30 | func retrieveOrgMemeberListPages(pc *providerConfig, organization string, isActive bool, pageSize int64, pageCount int64) ([]cloudsmith.OrganizationMembership, error) { 31 | 32 | var pageCurrentCount int64 = 1 33 | 34 | // A negative or zero count is assumed to mean retrieve the largest size page 35 | membersList := []cloudsmith.OrganizationMembership{} 36 | if pageSize == -1 || pageSize == 0 { 37 | pageSize = 100 38 | } 39 | 40 | // If no count is supplied assmumed to mean retrieve all pages 41 | // we have to retreive a page to get this count 42 | if pageCount == -1 || pageCount == 0 { 43 | var membersPage []cloudsmith.OrganizationMembership 44 | var err error 45 | membersPage, pageCount, err = retrieveOrgMemeberListPage(pc, organization, isActive, pageSize, 1) 46 | if err != nil { 47 | return nil, err 48 | } 49 | membersList = append(membersList, membersPage...) 50 | pageCurrentCount++ 51 | } 52 | 53 | for pageCurrentCount <= pageCount { 54 | membersPage, _, err := retrieveOrgMemeberListPage(pc, organization, isActive, pageSize, pageCount) 55 | if err != nil { 56 | return nil, err 57 | } 58 | membersList = append(membersList, membersPage...) 59 | pageCurrentCount++ 60 | } 61 | 62 | return membersList, nil 63 | } 64 | 65 | // dataSourceOrganizationMembersListRead reads the organization members from the API and filters them based on the provided query. 66 | func dataSourceOrganizationMembersListRead(d *schema.ResourceData, m interface{}) error { 67 | pc := m.(*providerConfig) 68 | namespace := d.Get("namespace").(string) 69 | isActive := d.Get("is_active").(bool) 70 | 71 | // Retrieve all organization members 72 | members, err := retrieveOrgMemeberListPages(pc, namespace, isActive, -1, -1) 73 | if err != nil { 74 | return fmt.Errorf("error retrieving organization members: %s", err) 75 | } 76 | 77 | // Map the filtered members to the schema 78 | if err := d.Set("members", flattenOrganizationMembers(members)); err != nil { 79 | return fmt.Errorf("error setting members: %s", err) 80 | } 81 | 82 | d.SetId(strconv.FormatInt(time.Now().Unix(), 10)) 83 | return nil 84 | } 85 | 86 | // flattenOrganizationMembers maps organization members to a format suitable for the schema. 87 | func flattenOrganizationMembers(members []cloudsmith.OrganizationMembership) []interface{} { 88 | var out []interface{} 89 | for _, member := range members { 90 | m := make(map[string]interface{}) 91 | m["email"] = member.GetEmail() 92 | m["has_two_factor"] = member.GetHasTwoFactor() 93 | m["is_active"] = member.GetIsActive() 94 | m["joined_at"] = member.GetJoinedAt().Format(time.RFC3339) // Assuming time.Time should be formatted as a string 95 | m["last_login_at"] = member.GetLastLoginAt().Format(time.RFC3339) 96 | m["last_login_method"] = member.GetLastLoginMethod() 97 | m["role"] = member.GetRole() 98 | m["user"] = member.GetUser() 99 | m["user_id"] = member.GetUserId() 100 | out = append(out, m) 101 | } 102 | return out 103 | } 104 | 105 | func dataSourceOrganizationMembersList() *schema.Resource { 106 | return &schema.Resource{ 107 | Read: dataSourceOrganizationMembersListRead, 108 | Schema: map[string]*schema.Schema{ 109 | "namespace": { 110 | Type: schema.TypeString, 111 | Required: true, 112 | }, 113 | "is_active": { 114 | Type: schema.TypeBool, 115 | Optional: true, 116 | Default: true, 117 | }, 118 | "members": { 119 | Type: schema.TypeList, 120 | Computed: true, 121 | Elem: &schema.Resource{ 122 | Schema: map[string]*schema.Schema{ 123 | "email": { 124 | Type: schema.TypeString, 125 | Computed: true, 126 | }, 127 | "has_two_factor": { 128 | Type: schema.TypeBool, 129 | Computed: true, 130 | }, 131 | "is_active": { 132 | Type: schema.TypeBool, 133 | Computed: true, 134 | }, 135 | "joined_at": { 136 | Type: schema.TypeString, // Assuming time.Time should be represented as a string 137 | Computed: true, 138 | }, 139 | "last_login_at": { 140 | Type: schema.TypeString, // Assuming time.Time should be represented as a string 141 | Computed: true, 142 | }, 143 | "last_login_method": { 144 | Type: schema.TypeString, 145 | Computed: true, 146 | }, 147 | "role": { 148 | Type: schema.TypeString, 149 | Computed: true, 150 | }, 151 | "user": { 152 | Type: schema.TypeString, 153 | Computed: true, 154 | }, 155 | "user_id": { 156 | Type: schema.TypeString, 157 | Computed: true, 158 | }, 159 | }, 160 | }, 161 | }, 162 | }, 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /cloudsmith/data_source_organization_members_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | ) 11 | 12 | // Test member list function 13 | 14 | func TestAccOrganizationMembersList_basic(t *testing.T) { 15 | t.Parallel() 16 | 17 | resource.Test(t, resource.TestCase{ 18 | PreCheck: func() { testAccPreCheck(t) }, 19 | Providers: testAccProviders, 20 | Steps: []resource.TestStep{ 21 | { 22 | Config: testAccCheckOrganizationMembersListConfig(), 23 | Check: resource.ComposeTestCheckFunc( 24 | resource.TestCheckResourceAttr("data.cloudsmith_list_org_members.test", "is_active", "true"), 25 | resource.TestCheckResourceAttr("data.cloudsmith_list_org_members.test", "members.0.has_two_factor", "true"), 26 | resource.TestCheckResourceAttr("data.cloudsmith_list_org_members.test", "members.0.is_active", "true"), 27 | resource.TestCheckResourceAttr("data.cloudsmith_list_org_members.test", "members.0.user", "bblizniak"), 28 | ), 29 | }, 30 | }, 31 | }) 32 | } 33 | 34 | func testAccCheckOrganizationMembersListConfig() string { 35 | return fmt.Sprintf(` 36 | data "cloudsmith_list_org_members" "test" { 37 | namespace = "%s" 38 | is_active = true 39 | } 40 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 41 | } 42 | -------------------------------------------------------------------------------- /cloudsmith/data_source_organization_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | ) 11 | 12 | // TestAccOrganization_data reads the configured organization using a data source and 13 | // verifies that the expected fields are set with appropriate values. 14 | func TestAccOrganization_data(t *testing.T) { 15 | t.Parallel() 16 | 17 | resource.Test(t, resource.TestCase{ 18 | PreCheck: func() { testAccPreCheck(t) }, 19 | Providers: testAccProviders, 20 | Steps: []resource.TestStep{ 21 | { 22 | Config: testAccOrganizationData, 23 | Check: resource.ComposeTestCheckFunc( 24 | resource.TestCheckResourceAttr("data.cloudsmith_organization.test", "slug", os.Getenv("CLOUDSMITH_NAMESPACE")), 25 | resource.TestCheckResourceAttrSet("data.cloudsmith_organization.test", "country"), 26 | resource.TestCheckResourceAttrSet("data.cloudsmith_organization.test", "created_at"), 27 | resource.TestCheckResourceAttrSet("data.cloudsmith_organization.test", "location"), 28 | resource.TestCheckResourceAttrSet("data.cloudsmith_organization.test", "name"), 29 | resource.TestCheckResourceAttrSet("data.cloudsmith_organization.test", "slug_perm"), 30 | resource.TestCheckResourceAttrSet("data.cloudsmith_organization.test", "tagline"), 31 | ), 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | var testAccOrganizationData = fmt.Sprintf(` 38 | data "cloudsmith_organization" "test" { 39 | slug = "%s" 40 | } 41 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 42 | -------------------------------------------------------------------------------- /cloudsmith/data_source_repository_privileges.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 7 | ) 8 | 9 | // dataSourceRepositoryPrivileges returns the data source schema and read function. 10 | func dataSourceRepositoryPrivileges() *schema.Resource { 11 | return &schema.Resource{ 12 | Read: dataSourceRepositoryPrivilegesRead, 13 | 14 | Schema: map[string]*schema.Schema{ 15 | "organization": { 16 | Type: schema.TypeString, 17 | Description: "Organization to which this repository belongs.", 18 | Required: true, 19 | }, 20 | "repository": { 21 | Type: schema.TypeString, 22 | Description: "Repository to fetch privileges information.", 23 | Required: true, 24 | }, 25 | "service": { 26 | Type: schema.TypeSet, 27 | Computed: true, 28 | Elem: &schema.Resource{ 29 | Schema: map[string]*schema.Schema{ 30 | "privilege": { 31 | Type: schema.TypeString, 32 | Computed: true, 33 | }, 34 | "slug": { 35 | Type: schema.TypeString, 36 | Computed: true, 37 | }, 38 | }, 39 | }, 40 | }, 41 | "team": { 42 | Type: schema.TypeSet, 43 | Computed: true, 44 | Elem: &schema.Resource{ 45 | Schema: map[string]*schema.Schema{ 46 | "privilege": { 47 | Type: schema.TypeString, 48 | Computed: true, 49 | }, 50 | "slug": { 51 | Type: schema.TypeString, 52 | Computed: true, 53 | }, 54 | }, 55 | }, 56 | }, 57 | "user": { 58 | Type: schema.TypeSet, 59 | Computed: true, 60 | Elem: &schema.Resource{ 61 | Schema: map[string]*schema.Schema{ 62 | "privilege": { 63 | Type: schema.TypeString, 64 | Computed: true, 65 | }, 66 | "slug": { 67 | Type: schema.TypeString, 68 | Computed: true, 69 | }, 70 | }, 71 | }, 72 | }, 73 | }, 74 | } 75 | } 76 | 77 | // dataSourceRepositoryPrivilegesRead retrieves privileges information for the specified repository. 78 | func dataSourceRepositoryPrivilegesRead(d *schema.ResourceData, m interface{}) error { 79 | pc := m.(*providerConfig) 80 | 81 | organization := d.Get("organization").(string) 82 | repository := d.Get("repository").(string) 83 | 84 | req := pc.APIClient.ReposApi.ReposPrivilegesList(pc.Auth, organization, repository) 85 | // TODO: add a proper loop here to ensure we always get all privs, 86 | // regardless of how many are configured. 87 | req = req.Page(1) 88 | req = req.PageSize(1000) 89 | privileges, resp, err := pc.APIClient.ReposApi.ReposPrivilegesListExecute(req) 90 | if err != nil { 91 | if is404(resp) { 92 | d.SetId("") 93 | return nil 94 | } 95 | 96 | return err 97 | } 98 | 99 | d.Set("service", flattenRepositoryPrivilegeServices(privileges.GetPrivileges())) 100 | d.Set("team", flattenRepositoryPrivilegeTeams(privileges.GetPrivileges())) 101 | d.Set("user", flattenRepositoryPrivilegeUsers(privileges.GetPrivileges())) 102 | 103 | d.SetId(fmt.Sprintf("%s/%s", organization, repository)) 104 | 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /cloudsmith/data_source_repository_privileges_test.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 9 | ) 10 | 11 | // TestAccDataSourceRepositoryPrivileges_basic tests the basic functionality of the data source. 12 | func TestAccDataSourceRepositoryPrivileges_basic(t *testing.T) { 13 | t.Parallel() 14 | 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | Providers: testAccProviders, 18 | CheckDestroy: testAccRepositoryCheckDestroy("cloudsmith_repository.test"), 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccDataSourceRepositoryPrivilegesConfigBasic, 22 | Check: resource.ComposeTestCheckFunc( 23 | resource.TestCheckResourceAttrSet("data.cloudsmith_repository_privileges.test_data", "service.#"), 24 | ), 25 | }, 26 | }, 27 | }) 28 | } 29 | 30 | var testAccDataSourceRepositoryPrivilegesConfigBasic = fmt.Sprintf(` 31 | resource "cloudsmith_repository" "test" { 32 | name = "terraform-acc-test-read-privs" 33 | namespace = "%s" 34 | } 35 | 36 | resource "cloudsmith_service" "test" { 37 | name = "TF Test Service Data Privs" 38 | organization = cloudsmith_repository.test.namespace 39 | role = "Member" 40 | } 41 | 42 | resource "cloudsmith_repository_privileges" "test" { 43 | organization = cloudsmith_repository.test.namespace 44 | repository = cloudsmith_repository.test.slug 45 | 46 | service { 47 | privilege = "Read" 48 | slug = cloudsmith_service.test.slug 49 | } 50 | } 51 | 52 | data "cloudsmith_repository_privileges" "test_data" { 53 | organization = cloudsmith_repository_privileges.test.organization 54 | repository = cloudsmith_repository_privileges.test.repository 55 | depends_on = [cloudsmith_repository.test] 56 | } 57 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 58 | -------------------------------------------------------------------------------- /cloudsmith/data_source_repository_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | ) 11 | 12 | // TestAccRepository_data spins up a repository with all default options, 13 | // verifies it exists, then reads the same repository using a data source and 14 | // verifies that the expected fields are set with default values. 15 | func TestAccRepository_data(t *testing.T) { 16 | t.Parallel() 17 | 18 | resource.Test(t, resource.TestCase{ 19 | PreCheck: func() { testAccPreCheck(t) }, 20 | Providers: testAccProviders, 21 | CheckDestroy: testAccRepositoryCheckDestroy("cloudsmith_repository.test"), 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: testAccRepositoryData, 25 | Check: resource.ComposeTestCheckFunc( 26 | testAccRepositoryCheckExists("cloudsmith_repository.test"), 27 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "name", "terraform-acc-test-ds"), 28 | resource.TestCheckResourceAttr("data.cloudsmith_repository.test", "name", "terraform-acc-test-ds"), 29 | // testing 5 representative fields, could be exhaustive here but feels like overkill for now 30 | resource.TestCheckResourceAttr("data.cloudsmith_repository.test", "contextual_auth_realm", "true"), 31 | resource.TestCheckResourceAttr("data.cloudsmith_repository.test", "docker_refresh_tokens_enabled", "false"), 32 | resource.TestCheckResourceAttr("data.cloudsmith_repository.test", "resync_own", "true"), 33 | resource.TestCheckResourceAttr("data.cloudsmith_repository.test", "resync_packages", "Admin"), 34 | resource.TestCheckResourceAttr("data.cloudsmith_repository.test", "use_vulnerability_scanning", "true"), 35 | ), 36 | }, 37 | }, 38 | }) 39 | } 40 | 41 | var testAccRepositoryData = fmt.Sprintf(` 42 | resource "cloudsmith_repository" "test" { 43 | name = "terraform-acc-test-ds" 44 | namespace = "%s" 45 | } 46 | 47 | data "cloudsmith_repository" "test" { 48 | identifier = "terraform-acc-test-ds" 49 | namespace = cloudsmith_repository.test.namespace 50 | } 51 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 52 | -------------------------------------------------------------------------------- /cloudsmith/data_source_user_self.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 5 | ) 6 | 7 | func dataSourceUserSelfRead(d *schema.ResourceData, m interface{}) error { 8 | pc := m.(*providerConfig) 9 | 10 | req := pc.APIClient.UserApi.UserSelf(pc.Auth) 11 | userSelf, _, err := pc.APIClient.UserApi.UserSelfExecute(req) 12 | if err != nil { 13 | return err 14 | } 15 | 16 | d.Set("email", userSelf.GetEmail()) 17 | d.Set("name", userSelf.GetName()) 18 | d.Set("slug", userSelf.GetSlug()) 19 | d.Set("slug_perm", userSelf.GetSlugPerm()) 20 | 21 | d.SetId(userSelf.GetSlugPerm()) 22 | 23 | return nil 24 | } 25 | 26 | // dataSourceUserSelf returns the schema and implementation for the data source 27 | // that provides information about the currently authenticated user. 28 | func dataSourceUserSelf() *schema.Resource { 29 | return &schema.Resource{ 30 | Read: dataSourceUserSelfRead, 31 | 32 | Schema: map[string]*schema.Schema{ 33 | "email": { 34 | Type: schema.TypeString, 35 | Description: "The user's email address", 36 | Computed: true, 37 | }, 38 | "name": { 39 | Type: schema.TypeString, 40 | Description: "The user's full name", 41 | Computed: true, 42 | }, 43 | "slug": { 44 | Type: schema.TypeString, 45 | Description: "The slug identifies the user in URIs", 46 | Computed: true, 47 | }, 48 | "slug_perm": { 49 | Type: schema.TypeString, 50 | Description: "The slug_perm immutably identifies the user", 51 | Computed: true, 52 | }, 53 | }, 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /cloudsmith/data_source_user_self_test.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 7 | ) 8 | 9 | func TestAccUserSelf_basic(t *testing.T) { 10 | t.Parallel() 11 | 12 | resource.Test(t, resource.TestCase{ 13 | PreCheck: func() { testAccPreCheck(t) }, 14 | Providers: testAccProviders, 15 | Steps: []resource.TestStep{ 16 | { 17 | Config: testAccUserSelfConfig, 18 | Check: resource.ComposeTestCheckFunc( 19 | resource.TestCheckResourceAttrSet("data.cloudsmith_user_self.test", "email"), 20 | resource.TestCheckResourceAttrSet("data.cloudsmith_user_self.test", "name"), 21 | resource.TestCheckResourceAttrSet("data.cloudsmith_user_self.test", "slug"), 22 | resource.TestCheckResourceAttrSet("data.cloudsmith_user_self.test", "slug_perm"), 23 | ), 24 | }, 25 | }, 26 | }) 27 | } 28 | 29 | const testAccUserSelfConfig = ` 30 | data "cloudsmith_user_self" "test" {} 31 | ` 32 | -------------------------------------------------------------------------------- /cloudsmith/provider.go: -------------------------------------------------------------------------------- 1 | // Package cloudsmith implements a Terraform provider for interacting with Cloudsmith. 2 | package cloudsmith 3 | 4 | import ( 5 | "context" 6 | "fmt" 7 | "runtime" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | ) 12 | 13 | // Provider returns a terraform.ResourceProvider. 14 | func Provider() *schema.Provider { 15 | p := &schema.Provider{ 16 | Schema: map[string]*schema.Schema{ 17 | "api_key": { 18 | Type: schema.TypeString, 19 | Description: "The API key for authenticating with the Cloudsmith API.", 20 | Required: true, 21 | DefaultFunc: schema.EnvDefaultFunc("CLOUDSMITH_API_KEY", nil), 22 | Sensitive: true, 23 | }, 24 | "api_host": { 25 | Type: schema.TypeString, 26 | Description: "The API host to connect to (mostly useful for testing).", 27 | Optional: true, 28 | DefaultFunc: schema.EnvDefaultFunc("CLOUDSMITH_API_HOST", "https://api.cloudsmith.io/v1"), 29 | }, 30 | "headers": { 31 | Type: schema.TypeMap, 32 | Elem: &schema.Schema{Type: schema.TypeString}, 33 | Description: "Additional HTTP headers to include in API requests", 34 | Optional: true, 35 | }, 36 | }, 37 | DataSourcesMap: map[string]*schema.Resource{ 38 | "cloudsmith_namespace": dataSourceNamespace(), 39 | "cloudsmith_organization": dataSourceOrganization(), 40 | "cloudsmith_package": dataSourcePackage(), 41 | "cloudsmith_package_list": dataSourcePackageList(), 42 | "cloudsmith_repository": dataSourceRepository(), 43 | "cloudsmith_repository_privileges": dataSourceRepositoryPrivileges(), 44 | "cloudsmith_package_deny_policy": dataSourcePackageDenyPolicy(), 45 | "cloudsmith_entitlement_list": dataSourceEntitlementList(), 46 | "cloudsmith_list_org_members": dataSourceOrganizationMembersList(), 47 | "cloudsmith_org_member_details": dataSourceMemberDetails(), 48 | "cloudsmith_user_self": dataSourceUserSelf(), 49 | }, 50 | ResourcesMap: map[string]*schema.Resource{ 51 | "cloudsmith_entitlement": resourceEntitlement(), 52 | "cloudsmith_license_policy": resourceLicensePolicy(), 53 | "cloudsmith_repository": resourceRepository(), 54 | "cloudsmith_repository_geo_ip_rules": resourceRepositoryGeoIpRules(), 55 | "cloudsmith_repository_privileges": resourceRepositoryPrivileges(), 56 | "cloudsmith_repository_upstream": resourceRepositoryUpstream(), 57 | "cloudsmith_service": resourceService(), 58 | "cloudsmith_team": resourceTeam(), 59 | "cloudsmith_vulnerability_policy": resourceVulnerabilityPolicy(), 60 | "cloudsmith_webhook": resourceWebhook(), 61 | "cloudsmith_package_deny_policy": packageDenyPolicy(), 62 | "cloudsmith_oidc": resourceOIDC(), 63 | "cloudsmith_manage_team": resourceManageTeam(), 64 | "cloudsmith_saml": resourceSAML(), 65 | "cloudsmith_saml_auth": resourceSAMLAuth(), 66 | "cloudsmith_repository_retention_rule": resourceRepoRetentionRule(), 67 | "cloudsmith_entitlement_control": resourceEntitlementControl(), 68 | }, 69 | } 70 | 71 | p.ConfigureContextFunc = func(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { 72 | terraformVersion := p.TerraformVersion 73 | if terraformVersion == "" { 74 | // Terraform 0.12 introduced this field to the protocol 75 | // We can therefore assume that if it's missing it's 0.10 or 0.11 76 | terraformVersion = "0.11+compatible" 77 | } 78 | 79 | apiHost := requiredString(d, "api_host") 80 | apiKey := requiredString(d, "api_key") 81 | userAgent := fmt.Sprintf("(%s %s) Terraform/%s", runtime.GOOS, runtime.GOARCH, terraformVersion) 82 | headers := d.Get("headers").(map[string]interface{}) 83 | 84 | return newProviderConfig(apiHost, apiKey, headers, userAgent) 85 | } 86 | 87 | return p 88 | } 89 | -------------------------------------------------------------------------------- /cloudsmith/provider_config.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | 9 | "github.com/cloudsmith-io/cloudsmith-api-go" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/diag" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" 12 | ) 13 | 14 | var errMissingCredentials = errors.New("credentials required for Cloudsmith provider") 15 | 16 | type providerConfig struct { 17 | // authentication credentials for the configured user 18 | Auth context.Context 19 | 20 | // initialised Cloudsmith API client 21 | APIClient *cloudsmith.APIClient 22 | } 23 | 24 | func newProviderConfig(apiHost string, apiKey string, headers map[string]interface{}, userAgent string) (*providerConfig, diag.Diagnostics) { 25 | if apiKey == "" { 26 | return nil, diag.FromErr(errMissingCredentials) 27 | } 28 | 29 | httpClient := http.DefaultClient 30 | httpClient.Transport = logging.NewSubsystemLoggingHTTPTransport("Cloudsmith", &headerTransport{ 31 | headers: headers, 32 | rt: http.DefaultTransport, 33 | }) 34 | 35 | config := cloudsmith.NewConfiguration() 36 | config.Debug = logging.IsDebugOrHigher() 37 | config.HTTPClient = httpClient 38 | 39 | config.Servers = cloudsmith.ServerConfigurations{ 40 | {URL: apiHost}, 41 | } 42 | config.UserAgent = userAgent 43 | 44 | apiClient := cloudsmith.NewAPIClient(config) 45 | 46 | auth := context.WithValue( 47 | context.Background(), 48 | cloudsmith.ContextAPIKeys, 49 | map[string]cloudsmith.APIKey{ 50 | "apikey": {Key: apiKey}, 51 | }, 52 | ) 53 | 54 | req := apiClient.UserApi.UserSelf(auth) 55 | if _, _, err := apiClient.UserApi.UserSelfExecute(req); err != nil { 56 | return nil, diag.FromErr(errors.New("invalid API credentials")) 57 | } 58 | 59 | return &providerConfig{Auth: auth, APIClient: apiClient}, nil 60 | } 61 | 62 | func (pc *providerConfig) GetAPIKey() string { 63 | apiKeys, _ := pc.Auth.Value(cloudsmith.ContextAPIKeys).(map[string]cloudsmith.APIKey) 64 | return apiKeys["apikey"].Key 65 | } 66 | 67 | type headerTransport struct { 68 | headers map[string]interface{} 69 | rt http.RoundTripper 70 | } 71 | 72 | func (t *headerTransport) RoundTrip(req *http.Request) (*http.Response, error) { 73 | for k, v := range t.headers { 74 | req.Header.Add(k, fmt.Sprint(v)) 75 | } 76 | return t.rt.RoundTrip(req) 77 | } 78 | -------------------------------------------------------------------------------- /cloudsmith/provider_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "net/http" 7 | "net/http/httptest" 8 | "os" 9 | "regexp" 10 | "testing" 11 | 12 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 13 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 14 | ) 15 | 16 | var ( 17 | testAccProviders map[string]*schema.Provider 18 | testAccProvider *schema.Provider 19 | ) 20 | 21 | //nolint:gochecknoinits 22 | func init() { 23 | testAccProvider = Provider() 24 | testAccProviders = map[string]*schema.Provider{ 25 | "cloudsmith": testAccProvider, 26 | } 27 | } 28 | 29 | func TestProvider(t *testing.T) { 30 | if err := Provider().InternalValidate(); err != nil { 31 | t.Fatalf("err: %s", err) 32 | } 33 | } 34 | 35 | func testAccPreCheck(t *testing.T) { 36 | if v := os.Getenv("CLOUDSMITH_API_KEY"); v == "" { 37 | t.Fatal("CLOUDSMITH_API_KEY must be set for acceptance tests") 38 | } 39 | if v := os.Getenv("CLOUDSMITH_NAMESPACE"); v == "" { 40 | t.Fatal("CLOUDSMITH_NAMESPACE must be set for acceptance tests") 41 | } 42 | } 43 | func TestAccProvider_UserSelfValidation(t *testing.T) { 44 | // Create mock server 45 | server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 46 | if r.URL.Path == "/user/self/" { 47 | w.Header().Set("Content-Type", "application/json") 48 | if r.Header.Get("X-Api-Key") == "valid-token" { 49 | w.WriteHeader(http.StatusOK) 50 | fmt.Fprintln(w, `{"email": "test@example.com", "name": "Test User", "slug": "test-user", "slug_perm": "test-user"}`) 51 | } else { 52 | w.WriteHeader(http.StatusUnauthorized) 53 | fmt.Fprintln(w, `{"error": "invalid API credentials"}`) 54 | } 55 | } 56 | })) 57 | defer server.Close() 58 | 59 | tests := []struct { 60 | name string 61 | apiKey string 62 | }{ 63 | { 64 | name: "ValidToken", 65 | apiKey: "valid-token", 66 | }, 67 | { 68 | name: "InvalidToken", 69 | apiKey: "invalid-token", 70 | }, 71 | } 72 | 73 | for _, tc := range tests { 74 | tc := tc 75 | t.Run(tc.name, func(t *testing.T) { 76 | t.Setenv("CLOUDSMITH_API_HOST", server.URL) 77 | t.Setenv("CLOUDSMITH_API_KEY", tc.apiKey) 78 | resource.Test(t, resource.TestCase{ 79 | PreCheck: func() { testAccPreCheck(t) }, 80 | Providers: testAccProviders, 81 | Steps: []resource.TestStep{ 82 | { 83 | Config: selfConfig, 84 | ExpectError: regexp.MustCompile("invalid API credentials"), 85 | SkipFunc: func() (bool, error) { 86 | // Skip error check for valid token case 87 | return tc.apiKey == "valid-token", nil 88 | }, 89 | }, 90 | }, 91 | }) 92 | }) 93 | } 94 | } 95 | 96 | var selfConfig string = ` 97 | data "cloudsmith_user_self" "this" { 98 | }` 99 | -------------------------------------------------------------------------------- /cloudsmith/resource_entitlement_control.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 10 | ) 11 | 12 | func entitlementControlImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { 13 | idParts := strings.Split(d.Id(), ".") 14 | if len(idParts) != 3 { 15 | return nil, fmt.Errorf( 16 | "invalid import ID, must be of the form .., got: %s", d.Id(), 17 | ) 18 | } 19 | 20 | d.Set("namespace", idParts[0]) 21 | d.Set("repository", idParts[1]) 22 | d.SetId(idParts[2]) 23 | return []*schema.ResourceData{d}, nil 24 | } 25 | 26 | func entitlementControlCreate(d *schema.ResourceData, m interface{}) error { 27 | pc := m.(*providerConfig) 28 | 29 | namespace := requiredString(d, "namespace") 30 | repository := requiredString(d, "repository") 31 | identifier := requiredString(d, "identifier") 32 | enabled := requiredBool(d, "enabled") 33 | 34 | if enabled { 35 | req := pc.APIClient.EntitlementsApi.EntitlementsEnable(pc.Auth, namespace, repository, identifier) 36 | _, err := pc.APIClient.EntitlementsApi.EntitlementsEnableExecute(req) 37 | if err != nil { 38 | return err 39 | } 40 | } else { 41 | req := pc.APIClient.EntitlementsApi.EntitlementsDisable(pc.Auth, namespace, repository, identifier) 42 | _, err := pc.APIClient.EntitlementsApi.EntitlementsDisableExecute(req) 43 | if err != nil { 44 | return err 45 | } 46 | } 47 | 48 | d.SetId(identifier) 49 | return entitlementControlRead(d, m) 50 | } 51 | 52 | func entitlementControlRead(d *schema.ResourceData, m interface{}) error { 53 | pc := m.(*providerConfig) 54 | namespace := requiredString(d, "namespace") 55 | repository := requiredString(d, "repository") 56 | 57 | req := pc.APIClient.EntitlementsApi.EntitlementsRead(pc.Auth, namespace, repository, d.Id()) 58 | entitlement, resp, err := pc.APIClient.EntitlementsApi.EntitlementsReadExecute(req) 59 | 60 | if err != nil { 61 | if is404(resp) { 62 | d.SetId("") 63 | return nil 64 | } 65 | return err 66 | } 67 | 68 | d.Set("enabled", entitlement.GetIsActive()) 69 | return nil 70 | } 71 | 72 | func entitlementControlUpdate(d *schema.ResourceData, m interface{}) error { 73 | pc := m.(*providerConfig) 74 | 75 | namespace := requiredString(d, "namespace") 76 | repository := requiredString(d, "repository") 77 | enabled := requiredBool(d, "enabled") 78 | 79 | if enabled { 80 | req := pc.APIClient.EntitlementsApi.EntitlementsEnable(pc.Auth, namespace, repository, d.Id()) 81 | _, err := pc.APIClient.EntitlementsApi.EntitlementsEnableExecute(req) 82 | if err != nil { 83 | return err 84 | } 85 | } else { 86 | req := pc.APIClient.EntitlementsApi.EntitlementsDisable(pc.Auth, namespace, repository, d.Id()) 87 | _, err := pc.APIClient.EntitlementsApi.EntitlementsDisableExecute(req) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | 93 | return entitlementControlRead(d, m) 94 | } 95 | 96 | func entitlementControlDelete(d *schema.ResourceData, m interface{}) error { 97 | // We don't actually delete the entitlement, just disable it 98 | pc := m.(*providerConfig) 99 | namespace := requiredString(d, "namespace") 100 | repository := requiredString(d, "repository") 101 | 102 | req := pc.APIClient.EntitlementsApi.EntitlementsDisable(pc.Auth, namespace, repository, d.Id()) 103 | _, err := pc.APIClient.EntitlementsApi.EntitlementsDisableExecute(req) 104 | if err != nil { 105 | return err 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func resourceEntitlementControl() *schema.Resource { 112 | return &schema.Resource{ 113 | Create: entitlementControlCreate, 114 | Read: entitlementControlRead, 115 | Update: entitlementControlUpdate, 116 | Delete: entitlementControlDelete, 117 | 118 | Importer: &schema.ResourceImporter{ 119 | StateContext: entitlementControlImport, 120 | }, 121 | 122 | Schema: map[string]*schema.Schema{ 123 | "namespace": { 124 | Type: schema.TypeString, 125 | Description: "Namespace to which this entitlement belongs.", 126 | Required: true, 127 | ForceNew: true, 128 | ValidateFunc: validation.StringIsNotEmpty, 129 | }, 130 | "repository": { 131 | Type: schema.TypeString, 132 | Description: "Repository to which this entitlement belongs.", 133 | Required: true, 134 | ForceNew: true, 135 | ValidateFunc: validation.StringIsNotEmpty, 136 | }, 137 | "identifier": { 138 | Type: schema.TypeString, 139 | Description: "The identifier (slug_perm) of the entitlement token.", 140 | Required: true, 141 | ForceNew: true, 142 | ValidateFunc: validation.StringIsNotEmpty, 143 | }, 144 | "enabled": { 145 | Type: schema.TypeBool, 146 | Description: "Whether the entitlement token is enabled or disabled.", 147 | Required: true, 148 | }, 149 | }, 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /cloudsmith/resource_entitlement_control_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 11 | ) 12 | 13 | // TestAccEntitlementControl_basic spins up a repository and uses its default entitlement token, 14 | // creates an entitlement control with the token disabled, verifies it exists and checks 15 | // the enabled state is set correctly. Then it changes the enabled state to true, 16 | // and verifies it's been set correctly before tearing down the resources and 17 | // verifying deletion. 18 | func TestAccEntitlementControl_basic(t *testing.T) { 19 | t.Parallel() 20 | 21 | resource.Test(t, resource.TestCase{ 22 | PreCheck: func() { testAccPreCheck(t) }, 23 | Providers: testAccProviders, 24 | CheckDestroy: testAccEntitlementControlCheckDestroy("cloudsmith_entitlement_control.test"), 25 | Steps: []resource.TestStep{ 26 | { 27 | Config: testAccEntitlementControlConfigBasic, 28 | Check: resource.ComposeTestCheckFunc( 29 | testAccEntitlementControlCheckExists("cloudsmith_entitlement_control.test"), 30 | resource.TestCheckResourceAttr("cloudsmith_entitlement_control.test", "namespace", os.Getenv("CLOUDSMITH_NAMESPACE")), 31 | resource.TestCheckResourceAttr("cloudsmith_entitlement_control.test", "enabled", "true"), 32 | ), 33 | }, 34 | { 35 | Config: testAccEntitlementControlConfigBasicUpdate, 36 | Check: resource.ComposeTestCheckFunc( 37 | testAccEntitlementControlCheckExists("cloudsmith_entitlement_control.test"), 38 | resource.TestCheckResourceAttr("cloudsmith_entitlement_control.test", "namespace", os.Getenv("CLOUDSMITH_NAMESPACE")), 39 | resource.TestCheckResourceAttr("cloudsmith_entitlement_control.test", "enabled", "false"), 40 | ), 41 | }, 42 | { 43 | ResourceName: "cloudsmith_entitlement_control.test", 44 | ImportState: true, 45 | ImportStateVerify: true, 46 | ImportStateIdFunc: func(s *terraform.State) (string, error) { 47 | resourceState := s.RootModule().Resources["cloudsmith_entitlement_control.test"] 48 | return fmt.Sprintf( 49 | "%s.%s.%s", 50 | resourceState.Primary.Attributes["namespace"], 51 | resourceState.Primary.Attributes["repository"], 52 | resourceState.Primary.ID, 53 | ), nil 54 | }, 55 | ImportStateVerifyIgnore: []string{ 56 | "identifier", // Ignore identifier as it's used for creation but not returned in read 57 | }, 58 | }, 59 | }, 60 | }) 61 | } 62 | 63 | //nolint:goerr113 64 | func testAccEntitlementControlCheckDestroy(resourceName string) resource.TestCheckFunc { 65 | return func(s *terraform.State) error { 66 | resourceState, ok := s.RootModule().Resources[resourceName] 67 | if !ok { 68 | return fmt.Errorf("resource not found: %s", resourceName) 69 | } 70 | 71 | if resourceState.Primary.ID == "" { 72 | return fmt.Errorf("resource id not set") 73 | } 74 | 75 | pc := testAccProvider.Meta().(*providerConfig) 76 | 77 | namespace := os.Getenv("CLOUDSMITH_NAMESPACE") 78 | repository := resourceState.Primary.Attributes["repository"] 79 | identifier := resourceState.Primary.ID 80 | 81 | req := pc.APIClient.EntitlementsApi.EntitlementsRead(pc.Auth, namespace, repository, identifier) 82 | entitlement, resp, err := pc.APIClient.EntitlementsApi.EntitlementsReadExecute(req) 83 | if err != nil && !is404(resp) { 84 | return fmt.Errorf("unable to verify entitlement control state: %w", err) 85 | } else if is200(resp) && entitlement.GetIsActive() { 86 | return fmt.Errorf("unable to verify entitlement control state: still enabled: %s/%s/%s", namespace, repository, identifier) 87 | } 88 | defer resp.Body.Close() 89 | 90 | return nil 91 | } 92 | } 93 | 94 | //nolint:goerr113 95 | func testAccEntitlementControlCheckExists(resourceName string) resource.TestCheckFunc { 96 | return func(s *terraform.State) error { 97 | resourceState, ok := s.RootModule().Resources[resourceName] 98 | if !ok { 99 | return fmt.Errorf("resource not found: %s", resourceName) 100 | } 101 | 102 | if resourceState.Primary.ID == "" { 103 | return fmt.Errorf("resource id not set") 104 | } 105 | 106 | pc := testAccProvider.Meta().(*providerConfig) 107 | 108 | namespace := os.Getenv("CLOUDSMITH_NAMESPACE") 109 | repository := resourceState.Primary.Attributes["repository"] 110 | identifier := resourceState.Primary.ID 111 | 112 | req := pc.APIClient.EntitlementsApi.EntitlementsRead(pc.Auth, namespace, repository, identifier) 113 | _, resp, err := pc.APIClient.EntitlementsApi.EntitlementsReadExecute(req) 114 | if err != nil { 115 | return fmt.Errorf("unable to verify entitlement control existence: %w", err) 116 | } 117 | defer resp.Body.Close() 118 | 119 | return nil 120 | } 121 | } 122 | 123 | var testAccEntitlementControlConfigBasic = fmt.Sprintf(` 124 | resource "cloudsmith_repository" "test" { 125 | name = "terraform-acc-test-ent-ctrl" 126 | namespace = "%s" 127 | } 128 | 129 | data "cloudsmith_entitlement_list" "test" { 130 | namespace = resource.cloudsmith_repository.test.namespace 131 | repository = resource.cloudsmith_repository.test.slug_perm 132 | query = ["name:Default"] 133 | } 134 | 135 | resource "cloudsmith_entitlement_control" "test" { 136 | namespace = resource.cloudsmith_repository.test.namespace 137 | repository = resource.cloudsmith_repository.test.slug_perm 138 | identifier = data.cloudsmith_entitlement_list.test.entitlement_tokens[0].slug_perm 139 | enabled = true 140 | } 141 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 142 | 143 | var testAccEntitlementControlConfigBasicUpdate = fmt.Sprintf(` 144 | resource "cloudsmith_repository" "test" { 145 | name = "terraform-acc-test-ent-ctrl" 146 | namespace = "%s" 147 | } 148 | 149 | data "cloudsmith_entitlement_list" "test" { 150 | namespace = resource.cloudsmith_repository.test.namespace 151 | repository = resource.cloudsmith_repository.test.slug_perm 152 | query = ["name:Default"] 153 | } 154 | 155 | resource "cloudsmith_entitlement_control" "test" { 156 | namespace = resource.cloudsmith_repository.test.namespace 157 | repository = resource.cloudsmith_repository.test.slug_perm 158 | identifier = data.cloudsmith_entitlement_list.test.entitlement_tokens[0].slug_perm 159 | enabled = false 160 | } 161 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 162 | -------------------------------------------------------------------------------- /cloudsmith/resource_entitlement_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 11 | ) 12 | 13 | // TestAccEntitlement_basic spins up a repository with all default options, 14 | // creates an entitlement with default options and verifies it exists and checks 15 | // the name is set correctly. Then it changes the name and some of the limit 16 | // variables, and verifies they've been set correctly before tearing down the 17 | // resources and verifying deletion. 18 | func TestAccEntitlement_basic(t *testing.T) { 19 | t.Parallel() 20 | 21 | resource.Test(t, resource.TestCase{ 22 | PreCheck: func() { testAccPreCheck(t) }, 23 | Providers: testAccProviders, 24 | CheckDestroy: testAccEntitlementCheckDestroy("cloudsmith_entitlement.test"), 25 | Steps: []resource.TestStep{ 26 | { 27 | Config: testAccEntitlementConfigBasic, 28 | Check: resource.ComposeTestCheckFunc( 29 | testAccEntitlementCheckExists("cloudsmith_entitlement.test"), 30 | resource.TestCheckResourceAttr("cloudsmith_entitlement.test", "name", "Test Entitlement"), 31 | resource.TestCheckResourceAttr("cloudsmith_entitlement.test", "limit_num_downloads", "0"), 32 | ), 33 | }, 34 | { 35 | Config: testAccEntitlementConfigBasicUpdate, 36 | Check: resource.ComposeTestCheckFunc( 37 | testAccEntitlementCheckExists("cloudsmith_entitlement.test"), 38 | resource.TestCheckResourceAttr("cloudsmith_entitlement.test", "name", "Test Entitlement Update"), 39 | resource.TestCheckResourceAttr("cloudsmith_entitlement.test", "limit_num_downloads", "100"), 40 | ), 41 | }, 42 | { 43 | ResourceName: "cloudsmith_entitlement.test", 44 | ImportState: true, 45 | ImportStateIdFunc: func(s *terraform.State) (string, error) { 46 | resourceState := s.RootModule().Resources["cloudsmith_entitlement.test"] 47 | return fmt.Sprintf( 48 | "%s.%s.%s", 49 | resourceState.Primary.Attributes["namespace"], 50 | resourceState.Primary.Attributes["repository"], 51 | resourceState.Primary.ID, 52 | ), nil 53 | }, 54 | ImportStateVerify: true, 55 | }, 56 | }, 57 | }) 58 | } 59 | 60 | //nolint:goerr113 61 | func testAccEntitlementCheckDestroy(resourceName string) resource.TestCheckFunc { 62 | return func(s *terraform.State) error { 63 | resourceState, ok := s.RootModule().Resources[resourceName] 64 | if !ok { 65 | return fmt.Errorf("resource not found: %s", resourceName) 66 | } 67 | 68 | if resourceState.Primary.ID == "" { 69 | return fmt.Errorf("resource id not set") 70 | } 71 | 72 | pc := testAccProvider.Meta().(*providerConfig) 73 | 74 | namespace := os.Getenv("CLOUDSMITH_NAMESPACE") 75 | repository := resourceState.Primary.Attributes["repository"] 76 | entitlement := resourceState.Primary.ID 77 | 78 | req := pc.APIClient.EntitlementsApi.EntitlementsRead(pc.Auth, namespace, repository, entitlement) 79 | _, resp, err := pc.APIClient.EntitlementsApi.EntitlementsReadExecute(req) 80 | if err != nil && !is404(resp) { 81 | return fmt.Errorf("unable to verify entitlement deletion: %w", err) 82 | } else if is200(resp) { 83 | return fmt.Errorf("unable to verify entitlement deletion: still exists: %s/%s/%s", namespace, repository, entitlement) 84 | } 85 | defer resp.Body.Close() 86 | 87 | rreq := pc.APIClient.ReposApi.ReposRead(pc.Auth, namespace, repository) 88 | _, resp, err = pc.APIClient.ReposApi.ReposReadExecute(rreq) 89 | if err != nil && !is404(resp) { 90 | return fmt.Errorf("unable to verify repository deletion: %w", err) 91 | } else if is200(resp) { 92 | return fmt.Errorf("unable to verify repository deletion: still exists: %s/%s", namespace, repository) 93 | } 94 | defer resp.Body.Close() 95 | 96 | return nil 97 | } 98 | } 99 | 100 | //nolint:goerr113 101 | func testAccEntitlementCheckExists(resourceName string) resource.TestCheckFunc { 102 | return func(s *terraform.State) error { 103 | resourceState, ok := s.RootModule().Resources[resourceName] 104 | if !ok { 105 | return fmt.Errorf("resource not found: %s", resourceName) 106 | } 107 | 108 | if resourceState.Primary.ID == "" { 109 | return fmt.Errorf("resource id not set") 110 | } 111 | 112 | pc := testAccProvider.Meta().(*providerConfig) 113 | 114 | namespace := os.Getenv("CLOUDSMITH_NAMESPACE") 115 | repository := resourceState.Primary.Attributes["repository"] 116 | entitlement := resourceState.Primary.ID 117 | 118 | req := pc.APIClient.EntitlementsApi.EntitlementsRead(pc.Auth, namespace, repository, entitlement) 119 | _, resp, err := pc.APIClient.EntitlementsApi.EntitlementsReadExecute(req) 120 | if err != nil { 121 | return fmt.Errorf("unable to verify entitlement existence: %w", err) 122 | } 123 | defer resp.Body.Close() 124 | 125 | return nil 126 | } 127 | } 128 | 129 | var testAccEntitlementConfigBasic = fmt.Sprintf(` 130 | resource "cloudsmith_repository" "test" { 131 | name = "terraform-acc-test-ent" 132 | namespace = "%s" 133 | } 134 | 135 | resource "cloudsmith_entitlement" "test" { 136 | name = "Test Entitlement" 137 | namespace = "${cloudsmith_repository.test.namespace}" 138 | repository = "${cloudsmith_repository.test.slug_perm}" 139 | } 140 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 141 | 142 | var testAccEntitlementConfigBasicUpdate = fmt.Sprintf(` 143 | resource "cloudsmith_repository" "test" { 144 | name = "terraform-acc-test-ent" 145 | namespace = "%s" 146 | } 147 | 148 | resource "cloudsmith_entitlement" "test" { 149 | name = "Test Entitlement Update" 150 | limit_num_downloads = 100 151 | namespace = "${cloudsmith_repository.test.namespace}" 152 | repository = "${cloudsmith_repository.test.slug_perm}" 153 | } 154 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 155 | -------------------------------------------------------------------------------- /cloudsmith/resource_license_policy_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 12 | ) 13 | 14 | func TestAccOrgLicensePolicy_basic(t *testing.T) { 15 | t.Parallel() 16 | 17 | resource.Test(t, resource.TestCase{ 18 | PreCheck: func() { testAccPreCheck(t) }, 19 | Providers: testAccProviders, 20 | CheckDestroy: testOrgLicensePolicyCheckDestroy("cloudsmith_license_policy.test"), 21 | Steps: []resource.TestStep{ 22 | { 23 | Config: testOrgLicensePolicyBasic, 24 | Check: resource.ComposeTestCheckFunc( 25 | testOrgLicensePolicyCheckExists("cloudsmith_license_policy.test"), 26 | // check computed properties have been set correctly 27 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "allow_unknown_licenses"), 28 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "on_violation_quarantine"), 29 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "created_at"), 30 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "updated_at"), 31 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "slug_perm"), 32 | ), 33 | }, 34 | { 35 | Config: testOrgLicensePolicyBasicInvalidSpdx, 36 | ExpectError: regexp.MustCompile("invalid spdx_identifiers:"), 37 | }, 38 | { 39 | Config: testOrgLicensePolicyBasicUpdate, 40 | Check: resource.ComposeTestCheckFunc( 41 | testOrgLicensePolicyCheckExists("cloudsmith_license_policy.test"), 42 | // check computed properties have been set correctly 43 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "created_at"), 44 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "updated_at"), 45 | resource.TestCheckResourceAttrSet("cloudsmith_license_policy.test", "slug_perm"), 46 | ), 47 | }, 48 | { 49 | ResourceName: "cloudsmith_license_policy.test", 50 | ImportState: true, 51 | ImportStateIdFunc: func(s *terraform.State) (string, error) { 52 | resourceState := s.RootModule().Resources["cloudsmith_license_policy.test"] 53 | return fmt.Sprintf( 54 | "%s.%s", 55 | resourceState.Primary.Attributes["organization"], 56 | resourceState.Primary.Attributes["slug_perm"], 57 | ), nil 58 | }, 59 | ImportStateVerify: true, 60 | }, 61 | }, 62 | }) 63 | } 64 | 65 | //nolint:goerr113 66 | func testOrgLicensePolicyCheckDestroy(resourceName string) resource.TestCheckFunc { 67 | return func(s *terraform.State) error { 68 | resourceState, ok := s.RootModule().Resources[resourceName] 69 | if !ok { 70 | return fmt.Errorf("resource not found: %s", resourceName) 71 | } 72 | 73 | if resourceState.Primary.ID == "" { 74 | return fmt.Errorf("resource id not set") 75 | } 76 | 77 | pc := testAccProvider.Meta().(*providerConfig) 78 | 79 | req := pc.APIClient.OrgsApi.OrgsLicensePolicyRead(pc.Auth, os.Getenv("CLOUDSMITH_NAMESPACE"), resourceState.Primary.ID) 80 | _, resp, err := pc.APIClient.OrgsApi.OrgsLicensePolicyReadExecute(req) 81 | if err != nil && !is404(resp) { 82 | return fmt.Errorf("unable to verify license policy deletion: %w", err) 83 | } else if is200(resp) { 84 | return fmt.Errorf("unable to verify license policy deletion: still exists: %s/%s", os.Getenv("CLOUDSMITH_NAMESPACE"), resourceState.Primary.ID) 85 | } 86 | defer resp.Body.Close() 87 | 88 | return nil 89 | } 90 | } 91 | 92 | //nolint:goerr113 93 | func testOrgLicensePolicyCheckExists(resourceName string) resource.TestCheckFunc { 94 | return func(s *terraform.State) error { 95 | resourceState, ok := s.RootModule().Resources[resourceName] 96 | if !ok { 97 | return fmt.Errorf("resource not found: %s", resourceName) 98 | } 99 | 100 | if resourceState.Primary.ID == "" { 101 | return fmt.Errorf("resource id not set") 102 | } 103 | 104 | pc := testAccProvider.Meta().(*providerConfig) 105 | 106 | req := pc.APIClient.OrgsApi.OrgsLicensePolicyRead(pc.Auth, os.Getenv("CLOUDSMITH_NAMESPACE"), resourceState.Primary.ID) 107 | _, resp, err := pc.APIClient.OrgsApi.OrgsLicensePolicyReadExecute(req) 108 | if err != nil { 109 | return err 110 | } 111 | defer resp.Body.Close() 112 | 113 | return nil 114 | } 115 | } 116 | 117 | var testOrgLicensePolicyBasic = fmt.Sprintf(` 118 | resource "cloudsmith_license_policy" "test" { 119 | name = "TF Test Policy" 120 | description = "TF Test Policy Description" 121 | spdx_identifiers = ["Apache-1.0"] 122 | organization = "%s" 123 | } 124 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 125 | 126 | var testOrgLicensePolicyBasicInvalidSpdx = fmt.Sprintf(` 127 | resource "cloudsmith_license_policy" "test" { 128 | name = "TF Test Policy" 129 | description = "TF Test Policy Description" 130 | spdx_identifiers = ["Not a spdx"] 131 | organization = "%s" 132 | } 133 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 134 | 135 | var testOrgLicensePolicyBasicUpdate = fmt.Sprintf(` 136 | resource "cloudsmith_license_policy" "test" { 137 | name = "TF Test Policy Updated" 138 | description = "TF Test Policy Description Updated" 139 | spdx_identifiers = ["Apache-2.0"] 140 | on_violation_quarantine = true 141 | allow_unknown_licenses = true 142 | package_query_string = "format:python AND downloads:>50" 143 | organization = "%s" 144 | } 145 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 146 | -------------------------------------------------------------------------------- /cloudsmith/resource_manage_team.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/cloudsmith-io/cloudsmith-api-go" 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 10 | ) 11 | 12 | // The purpose of this resource is to add/remove users from a team in Cloudsmith 13 | 14 | func importManageTeam(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { 15 | idParts := strings.Split(d.Id(), ".") 16 | if len(idParts) != 2 { 17 | return nil, fmt.Errorf( 18 | "invalid import ID, must be of the form ., got: %s", d.Id(), 19 | ) 20 | } 21 | 22 | d.Set("organization", idParts[0]) 23 | d.Set("team_name", idParts[1]) 24 | return []*schema.ResourceData{d}, nil 25 | } 26 | 27 | func resourceManageTeamAdd(d *schema.ResourceData, m interface{}) error { 28 | // this function will add users to an existing team 29 | pc := m.(*providerConfig) 30 | organization := requiredString(d, "organization") 31 | teamName := requiredString(d, "team_name") 32 | 33 | // Fetching members from the Set, converting to a list 34 | teamMembersSet := d.Get("members").(*schema.Set).List() 35 | teamMembersList := make([]cloudsmith.OrganizationTeamMembership, len(teamMembersSet)) 36 | 37 | for i, v := range teamMembersSet { 38 | teamMember := v.(map[string]interface{}) 39 | teamMembersList[i] = cloudsmith.OrganizationTeamMembership{ 40 | Role: teamMember["role"].(string), 41 | User: teamMember["user"].(string), 42 | } 43 | } 44 | 45 | teamMembersData := cloudsmith.OrganizationTeamMembers{ 46 | Members: teamMembersList, 47 | } 48 | 49 | req := pc.APIClient.OrgsApi.OrgsTeamsMembersCreate(pc.Auth, organization, teamName) 50 | req = req.Data(teamMembersData) 51 | 52 | _, _, err := pc.APIClient.OrgsApi.OrgsTeamsMembersCreateExecute(req) 53 | if err != nil { 54 | return err 55 | } 56 | 57 | d.SetId(fmt.Sprintf("%s.%s", organization, teamName)) 58 | 59 | return nil 60 | } 61 | 62 | // We're using the replace members endpoint here so we need to compare the existing members with the new members and adjust the delta 63 | func resourceManageTeamUpdateRemove(d *schema.ResourceData, m interface{}) error { 64 | pc := m.(*providerConfig) 65 | organization := requiredString(d, "organization") 66 | teamName := requiredString(d, "team_name") 67 | 68 | // Fetching members from the Set, converting to a list 69 | teamMembersSet := d.Get("members").(*schema.Set).List() 70 | teamMembersList := make([]cloudsmith.OrganizationTeamMembership, len(teamMembersSet)) 71 | 72 | for i, v := range teamMembersSet { 73 | teamMember := v.(map[string]interface{}) 74 | teamMembersList[i] = cloudsmith.OrganizationTeamMembership{ 75 | Role: teamMember["role"].(string), 76 | User: teamMember["user"].(string), 77 | } 78 | } 79 | 80 | teamMembersData := cloudsmith.OrganizationTeamMembers{ 81 | Members: teamMembersList, 82 | } 83 | 84 | req := pc.APIClient.OrgsApi.OrgsTeamsMembersUpdate(pc.Auth, organization, teamName) 85 | req = req.Data(teamMembersData) 86 | 87 | _, _, err := pc.APIClient.OrgsApi.OrgsTeamsMembersUpdateExecute(req) 88 | if err != nil { 89 | return err 90 | } 91 | 92 | d.SetId(fmt.Sprintf("%s.%s", organization, teamName)) 93 | 94 | return nil 95 | } 96 | 97 | func resourceManageTeamRead(d *schema.ResourceData, m interface{}) error { 98 | // This function will read the team 99 | pc := m.(*providerConfig) 100 | 101 | organization := requiredString(d, "organization") 102 | teamName := requiredString(d, "team_name") 103 | 104 | req := pc.APIClient.OrgsApi.OrgsTeamsMembersList(pc.Auth, organization, teamName) 105 | 106 | teamMembers, resp, err := pc.APIClient.OrgsApi.OrgsTeamsMembersListExecute(req) 107 | if err != nil { 108 | if is404(resp) { 109 | d.SetId("") 110 | return nil 111 | } 112 | return err 113 | } 114 | 115 | // Map the members correctly 116 | members := make([]map[string]interface{}, len(teamMembers.GetMembers())) 117 | for i, member := range teamMembers.GetMembers() { 118 | members[i] = map[string]interface{}{ 119 | "role": member.Role, 120 | "user": member.User, 121 | } 122 | } 123 | 124 | // Setting the values into the resource data 125 | d.Set("organization", organization) 126 | d.Set("team_name", teamName) 127 | d.Set("members", members) 128 | 129 | // Set the ID to the organization and team name, no slug returned from the API 130 | d.SetId(fmt.Sprintf("%s.%s", organization, teamName)) 131 | 132 | return nil 133 | } 134 | 135 | func resourceManageTeam() *schema.Resource { 136 | return &schema.Resource{ 137 | Create: resourceManageTeamAdd, 138 | Read: resourceManageTeamRead, 139 | Update: resourceManageTeamUpdateRemove, 140 | Delete: resourceManageTeamUpdateRemove, 141 | Importer: &schema.ResourceImporter{ 142 | StateContext: importManageTeam, 143 | }, 144 | 145 | Schema: map[string]*schema.Schema{ 146 | "organization": { 147 | Type: schema.TypeString, 148 | Required: true, 149 | ForceNew: true, 150 | }, 151 | "team_name": { 152 | Type: schema.TypeString, 153 | Required: true, 154 | }, 155 | "members": { 156 | Type: schema.TypeSet, 157 | Elem: &schema.Resource{ 158 | Schema: map[string]*schema.Schema{ 159 | "role": { 160 | Type: schema.TypeString, 161 | Required: true, 162 | }, 163 | "user": { 164 | Type: schema.TypeString, 165 | Required: true, 166 | }, 167 | }, 168 | }, 169 | Required: true, 170 | }, 171 | }, 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /cloudsmith/resource_manage_team_test.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "testing" 7 | 8 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 9 | ) 10 | 11 | // create basic manage team test function 12 | 13 | func TestAccManageTeam_basic(t *testing.T) { 14 | t.Parallel() 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | Providers: testAccProviders, 18 | CheckDestroy: testAccTeamCheckDestroy("cloudsmith_team.test"), 19 | Steps: []resource.TestStep{ 20 | { 21 | Config: testAccManageTeamConfigBasic, 22 | Check: resource.ComposeTestCheckFunc( 23 | testAccTeamCheckExists("cloudsmith_team.test"), 24 | resource.TestCheckResourceAttr("cloudsmith_manage_team.test", "team_name", "tf-test-manage-team-members"), 25 | resource.TestCheckResourceAttr("cloudsmith_manage_team.test", "members.0.role", "Member"), 26 | resource.TestCheckResourceAttr("cloudsmith_manage_team.test", "members.0.user", "bblizniak"), 27 | ), 28 | // This is required as when creating a team, the creator gets automatically added which causes a 422 error 29 | ExpectNonEmptyPlan: true, 30 | }, 31 | }, 32 | }) 33 | } 34 | 35 | var testAccManageTeamConfigBasic = fmt.Sprintf(` 36 | resource "cloudsmith_team" "test" { 37 | organization = "%s" 38 | name = "tf-test-manage-team-members" 39 | } 40 | 41 | resource "cloudsmith_manage_team" "test" { 42 | depends_on = [cloudsmith_team.test] 43 | organization = cloudsmith_team.test.organization 44 | team_name = cloudsmith_team.test.name 45 | members { 46 | role = "Member" 47 | user = "bblizniak" 48 | } 49 | } 50 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 51 | -------------------------------------------------------------------------------- /cloudsmith/resource_package_deny_policy.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "strings" 7 | "time" 8 | 9 | "github.com/cloudsmith-io/cloudsmith-api-go" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" 12 | ) 13 | 14 | func packageDenyPolicyImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { 15 | idParts := strings.Split(d.Id(), ".") 16 | if len(idParts) != 2 { 17 | return nil, fmt.Errorf( 18 | "invalid import ID, must be of the form ., got: %s", d.Id(), 19 | ) 20 | } 21 | 22 | d.Set("namespace", idParts[0]) 23 | d.SetId(idParts[1]) 24 | return []*schema.ResourceData{d}, nil 25 | } 26 | 27 | func packageDenyPolicyCreate(d *schema.ResourceData, m interface{}) error { 28 | pc := m.(*providerConfig) 29 | 30 | namespace := requiredString(d, "namespace") 31 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyCreate(pc.Auth, namespace) 32 | req = req.Data(cloudsmith.PackageDenyPolicyRequest{ 33 | Name: nullableString(d, "name"), 34 | Enabled: optionalBool(d, "enabled"), 35 | Description: nullableString(d, "description"), 36 | PackageQueryString: *optionalString(d, "package_query"), 37 | }) 38 | packageDenyPolicy, _, err := pc.APIClient.OrgsApi.OrgsDenyPolicyCreateExecute(req) 39 | if err != nil { 40 | return err 41 | } 42 | d.SetId(packageDenyPolicy.GetSlugPerm()) 43 | checkerFunc := func() error { 44 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyRead(pc.Auth, namespace, d.Id()) 45 | if _, resp, err := pc.APIClient.OrgsApi.OrgsDenyPolicyReadExecute(req); err != nil { 46 | if is404(resp) { 47 | return errKeepWaiting 48 | } 49 | return err 50 | } 51 | return nil 52 | } 53 | if err := waiter(checkerFunc, defaultCreationTimeout, defaultCreationInterval); err != nil { 54 | return fmt.Errorf("error waiting for package deny policy (%s) to be created: %w", d.Id(), err) 55 | } 56 | return packageDenyPolicyRead(d, m) 57 | } 58 | 59 | func packageDenyPolicyRead(d *schema.ResourceData, m interface{}) error { 60 | pc := m.(*providerConfig) 61 | 62 | namespace := requiredString(d, "namespace") 63 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyRead(pc.Auth, namespace, d.Id()) 64 | packageDenyPolicy, resp, err := pc.APIClient.OrgsApi.OrgsDenyPolicyReadExecute(req) 65 | 66 | if err != nil { 67 | if is404(resp) { 68 | d.SetId("") 69 | return nil 70 | } 71 | return err 72 | 73 | } 74 | 75 | d.Set("name", packageDenyPolicy.GetName()) 76 | d.Set("description", packageDenyPolicy.GetDescription()) 77 | d.Set("package_query", packageDenyPolicy.GetPackageQueryString()) 78 | d.Set("enabled", packageDenyPolicy.GetEnabled()) 79 | 80 | return nil 81 | } 82 | 83 | func packageDenyPolicyUpdate(d *schema.ResourceData, m interface{}) error { 84 | pc := m.(*providerConfig) 85 | namespace := requiredString(d, "namespace") 86 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyPartialUpdate(pc.Auth, namespace, d.Id()) 87 | req = req.Data(cloudsmith.PackageDenyPolicyRequestPatch{ 88 | Name: nullableString(d, "name"), 89 | Enabled: optionalBool(d, "enabled"), 90 | Description: nullableString(d, "description"), 91 | PackageQueryString: optionalString(d, "package_query"), 92 | }) 93 | packageDenyPolicy, _, err := pc.APIClient.OrgsApi.OrgsDenyPolicyPartialUpdateExecute(req) 94 | if err != nil { 95 | return err 96 | } 97 | d.SetId(packageDenyPolicy.GetSlugPerm()) 98 | checkerFunc := func() error { 99 | // this is somewhat of a hack until we have a better way to poll for a 100 | // deny policy being updated 101 | time.Sleep(time.Second * 5) 102 | return nil 103 | } 104 | if err := waiter(checkerFunc, defaultUpdateTimeout, defaultUpdateInterval); err != nil { 105 | return fmt.Errorf("error waiting for deny policy (%s) to be updated: %w", d.Id(), err) 106 | } 107 | return packageDenyPolicyRead(d, m) 108 | } 109 | 110 | func packageDenyPolicyDelete(d *schema.ResourceData, m interface{}) error { 111 | pc := m.(*providerConfig) 112 | 113 | namespace := requiredString(d, "namespace") 114 | 115 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyDelete(pc.Auth, namespace, d.Id()) 116 | _, err := pc.APIClient.OrgsApi.OrgsDenyPolicyDeleteExecute(req) 117 | if err != nil { 118 | return err 119 | } 120 | 121 | checkerFunc := func() error { 122 | req := pc.APIClient.OrgsApi.OrgsDenyPolicyRead(pc.Auth, namespace, d.Id()) 123 | if _, resp, err := pc.APIClient.OrgsApi.OrgsDenyPolicyReadExecute(req); err != nil { 124 | if is404(resp) { 125 | return nil 126 | } 127 | return err 128 | } 129 | return errKeepWaiting 130 | } 131 | 132 | if err := waiter(checkerFunc, defaultDeletionTimeout, defaultDeletionInterval); err != nil { 133 | return fmt.Errorf("error waiting for deny policy (%s) to be deleted: %w", d.Id(), err) 134 | } 135 | return nil 136 | } 137 | 138 | //nolint:funlen 139 | func packageDenyPolicy() *schema.Resource { 140 | return &schema.Resource{ 141 | Create: packageDenyPolicyCreate, 142 | Read: packageDenyPolicyRead, 143 | Update: packageDenyPolicyUpdate, 144 | Delete: packageDenyPolicyDelete, 145 | Description: "Package deny policies control which packages can be downloaded within their repositories.", 146 | 147 | Importer: &schema.ResourceImporter{ 148 | StateContext: packageDenyPolicyImport, 149 | }, 150 | 151 | Schema: map[string]*schema.Schema{ 152 | "name": { 153 | Type: schema.TypeString, 154 | Description: "A descriptive name for the package deny policy.", 155 | Optional: true, 156 | Default: nil, 157 | }, 158 | "description": { 159 | Type: schema.TypeString, 160 | Description: "Description of the package deny policy.", 161 | Optional: true, 162 | Default: nil, 163 | }, 164 | "package_query": { 165 | Type: schema.TypeString, 166 | Description: "The query to match the packages to be blocked.", 167 | Required: true, 168 | ValidateFunc: validation.StringIsNotEmpty, 169 | }, 170 | "enabled": { 171 | Type: schema.TypeBool, 172 | Description: "Is the package deny policy enabled?.", 173 | Optional: true, 174 | Default: true, 175 | }, 176 | "namespace": { 177 | Type: schema.TypeString, 178 | Description: "Namespace to which this package deny policy belongs.", 179 | Required: true, 180 | ForceNew: true, 181 | ValidateFunc: validation.StringIsNotEmpty, 182 | }, 183 | }, 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /cloudsmith/resource_package_deny_policy_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 11 | ) 12 | 13 | // create a baisc package deny policy function 14 | 15 | func TestAccPackageDenyPolicy_basic(t *testing.T) { 16 | t.Parallel() 17 | 18 | resource.Test(t, resource.TestCase{ 19 | PreCheck: func() { testAccPreCheck(t) }, 20 | Providers: testAccProviders, 21 | CheckDestroy: testAccPackageDenyPolicyCheckDestroy("cloudsmith_package_deny_policy.test"), 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: testAccPackageDenyPolicyConfigBasic, 25 | Check: resource.ComposeTestCheckFunc( 26 | testAccPackageDenyPolicyCheckExists("cloudsmith_package_deny_policy.test"), 27 | resource.TestCheckResourceAttr("cloudsmith_package_deny_policy.test", "namespace", os.Getenv("CLOUDSMITH_NAMESPACE")), 28 | resource.TestCheckResourceAttr("cloudsmith_package_deny_policy.test", "enabled", "true"), 29 | resource.TestCheckResourceAttr("cloudsmith_package_deny_policy.test", "name", "test-package-deny-policy-terraform-provider"), 30 | resource.TestCheckResourceAttr("cloudsmith_package_deny_policy.test", "package_query", "name:example"), 31 | ), 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | // create a basic package deny policy config 38 | 39 | var testAccPackageDenyPolicyConfigBasic = fmt.Sprintf(` 40 | resource "cloudsmith_package_deny_policy" "test" { 41 | namespace = "%s" 42 | enabled = true 43 | name = "test-package-deny-policy-terraform-provider" 44 | package_query = "name:example" 45 | } 46 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 47 | 48 | // create a package deny policy check destroy function 49 | 50 | func testAccPackageDenyPolicyCheckDestroy(name string) resource.TestCheckFunc { 51 | return func(s *terraform.State) error { 52 | client := testAccProvider.Meta().(*providerConfig).APIClient 53 | for _, rs := range s.RootModule().Resources { 54 | if rs.Type != "cloudsmith_package_deny_policy" { 55 | continue 56 | } 57 | 58 | _, _, err := client.OrgsApi.OrgsDenyPolicyRead(testAccProvider.Meta().(*providerConfig).Auth, rs.Primary.Attributes["namespace"], rs.Primary.ID).Execute() 59 | if err == nil { 60 | return fmt.Errorf("Package deny policy still exists") 61 | } 62 | } 63 | return nil 64 | } 65 | } 66 | 67 | // create a package deny policy check exists function 68 | 69 | func testAccPackageDenyPolicyCheckExists(name string) resource.TestCheckFunc { 70 | return func(s *terraform.State) error { 71 | client := testAccProvider.Meta().(*providerConfig).APIClient 72 | for _, rs := range s.RootModule().Resources { 73 | if rs.Type != "cloudsmith_package_deny_policy" { 74 | continue 75 | } 76 | 77 | _, _, err := client.OrgsApi.OrgsDenyPolicyRead(testAccProvider.Meta().(*providerConfig).Auth, rs.Primary.Attributes["namespace"], rs.Primary.ID).Execute() 78 | if err != nil { 79 | return fmt.Errorf("Package deny policy does not exist") 80 | } 81 | } 82 | return nil 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /cloudsmith/resource_repository_privileges_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 11 | ) 12 | 13 | // TestAccRepositoryPrivileges_basic spins up a repository with default options, 14 | // creates a service account and a couple of teams, assigning and modifying 15 | // their permissions before tearing down and verifying deletion. 16 | func TestAccRepositoryPrivileges_basic(t *testing.T) { 17 | t.Parallel() 18 | 19 | resource.Test(t, resource.TestCase{ 20 | PreCheck: func() { testAccPreCheck(t) }, 21 | Providers: testAccProviders, 22 | CheckDestroy: testAccRepositoryCheckDestroy("cloudsmith_repository.test"), 23 | Steps: []resource.TestStep{ 24 | { 25 | Config: testAccRepositoryPrivilegesConfigBasic, 26 | Check: resource.ComposeTestCheckFunc( 27 | resource.TestCheckResourceAttr("cloudsmith_repository_privileges.test", "service.0.privilege", "Read"), 28 | ), 29 | }, 30 | { 31 | Config: testAccRepositoryPrivilegesConfigBasicUpdatePrivilege, 32 | Check: resource.ComposeTestCheckFunc( 33 | resource.TestCheckResourceAttr("cloudsmith_repository_privileges.test", "service.0.privilege", "Write"), 34 | ), 35 | }, 36 | { 37 | Config: testAccRepositoryPrivilegesConfigBasicAddTeam, 38 | Check: resource.ComposeTestCheckFunc( 39 | resource.TestCheckResourceAttr("cloudsmith_repository_privileges.test", "service.0.privilege", "Write"), 40 | resource.TestCheckResourceAttr("cloudsmith_repository_privileges.test", "team.0.privilege", "Write"), 41 | ), 42 | }, 43 | { 44 | Config: testAccRepositoryPrivilegesConfigBasicAddAnotherTeam, 45 | Check: resource.ComposeTestCheckFunc( 46 | resource.TestCheckResourceAttr("cloudsmith_repository_privileges.test", "service.0.privilege", "Write"), 47 | resource.TestCheckTypeSetElemNestedAttrs("cloudsmith_repository_privileges.test", "team.*", map[string]string{ 48 | "privilege": "Write", 49 | "slug": "tf-test-team-privs-2", 50 | }), 51 | resource.TestCheckTypeSetElemNestedAttrs("cloudsmith_repository_privileges.test", "team.*", map[string]string{ 52 | "privilege": "Read", 53 | "slug": "tf-test-team-privs-1", 54 | }), 55 | ), 56 | }, 57 | { 58 | ResourceName: "cloudsmith_repository_privileges.test", 59 | ImportState: true, 60 | ImportStateIdFunc: func(s *terraform.State) (string, error) { 61 | resourceState := s.RootModule().Resources["cloudsmith_repository_privileges.test"] 62 | return fmt.Sprintf( 63 | "%s.%s", 64 | resourceState.Primary.Attributes["organization"], 65 | resourceState.Primary.Attributes["repository"], 66 | ), nil 67 | }, 68 | ImportStateVerify: true, 69 | }, 70 | }, 71 | }) 72 | } 73 | 74 | var testAccRepositoryPrivilegesConfigBasic = fmt.Sprintf(` 75 | resource "cloudsmith_repository" "test" { 76 | name = "terraform-acc-test-privs" 77 | namespace = "%s" 78 | } 79 | 80 | resource "cloudsmith_service" "test" { 81 | name = "TF Test Service Privs" 82 | organization = cloudsmith_repository.test.namespace 83 | role = "Member" 84 | } 85 | 86 | resource "cloudsmith_repository_privileges" "test" { 87 | organization = cloudsmith_repository.test.namespace 88 | repository = cloudsmith_repository.test.slug 89 | 90 | service { 91 | privilege = "Read" 92 | slug = cloudsmith_service.test.slug 93 | } 94 | } 95 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 96 | 97 | var testAccRepositoryPrivilegesConfigBasicUpdatePrivilege = fmt.Sprintf(` 98 | resource "cloudsmith_repository" "test" { 99 | name = "terraform-acc-test-privs" 100 | namespace = "%s" 101 | } 102 | 103 | resource "cloudsmith_service" "test" { 104 | name = "TF Test Service Privs" 105 | organization = cloudsmith_repository.test.namespace 106 | role = "Member" 107 | } 108 | 109 | resource "cloudsmith_repository_privileges" "test" { 110 | organization = cloudsmith_repository.test.namespace 111 | repository = cloudsmith_repository.test.slug 112 | 113 | service { 114 | privilege = "Write" 115 | slug = cloudsmith_service.test.slug 116 | } 117 | } 118 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 119 | 120 | var testAccRepositoryPrivilegesConfigBasicAddTeam = fmt.Sprintf(` 121 | resource "cloudsmith_repository" "test" { 122 | name = "terraform-acc-test-privs" 123 | namespace = "%s" 124 | } 125 | 126 | resource "cloudsmith_service" "test" { 127 | name = "TF Test Service Privs" 128 | organization = cloudsmith_repository.test.namespace 129 | role = "Member" 130 | } 131 | 132 | resource "cloudsmith_team" "test_1" { 133 | name = "TF Test Team Privs 1" 134 | organization = cloudsmith_repository.test.namespace 135 | } 136 | 137 | resource "cloudsmith_repository_privileges" "test" { 138 | organization = cloudsmith_repository.test.namespace 139 | repository = cloudsmith_repository.test.slug 140 | 141 | service { 142 | privilege = "Write" 143 | slug = cloudsmith_service.test.slug 144 | } 145 | 146 | team { 147 | privilege = "Write" 148 | slug = cloudsmith_team.test_1.slug 149 | } 150 | } 151 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 152 | 153 | var testAccRepositoryPrivilegesConfigBasicAddAnotherTeam = fmt.Sprintf(` 154 | resource "cloudsmith_repository" "test" { 155 | name = "terraform-acc-test-privs" 156 | namespace = "%s" 157 | } 158 | 159 | resource "cloudsmith_service" "test" { 160 | name = "TF Test Service Privs" 161 | organization = cloudsmith_repository.test.namespace 162 | role = "Member" 163 | } 164 | 165 | resource "cloudsmith_team" "test_1" { 166 | name = "TF Test Team Privs 1" 167 | organization = cloudsmith_repository.test.namespace 168 | } 169 | 170 | resource "cloudsmith_team" "test_2" { 171 | name = "TF Test Team Privs 2" 172 | organization = cloudsmith_repository.test.namespace 173 | } 174 | 175 | resource "cloudsmith_repository_privileges" "test" { 176 | organization = cloudsmith_repository.test.namespace 177 | repository = cloudsmith_repository.test.slug 178 | 179 | service { 180 | privilege = "Write" 181 | slug = cloudsmith_service.test.slug 182 | } 183 | 184 | team { 185 | privilege = "Write" 186 | slug = cloudsmith_team.test_2.slug 187 | } 188 | 189 | team { 190 | privilege = "Read" 191 | slug = cloudsmith_team.test_1.slug 192 | } 193 | } 194 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 195 | -------------------------------------------------------------------------------- /cloudsmith/resource_repository_retention_rule_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | ) 11 | 12 | func TestAccRepositoryRetentionRule_basic(t *testing.T) { 13 | t.Parallel() 14 | 15 | resource.Test(t, resource.TestCase{ 16 | PreCheck: func() { testAccPreCheck(t) }, 17 | Providers: testAccProviders, 18 | Steps: []resource.TestStep{ 19 | { 20 | Config: testAccRepositoryConfig, 21 | Check: resource.ComposeTestCheckFunc( 22 | testAccRepositoryCheckExists("cloudsmith_repository.test-retention"), 23 | ), 24 | }, 25 | { 26 | Config: testAccRepositoryRetentionRuleConfigBasic, 27 | Check: resource.ComposeTestCheckFunc( 28 | testAccRepositoryCheckExists("cloudsmith_repository.test-retention"), 29 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_count_limit", "100"), 30 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_days_limit", "28"), 31 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_enabled", "false"), 32 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_group_by_name", "false"), 33 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_group_by_format", "false"), 34 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_group_by_package_type", "false"), 35 | resource.TestCheckResourceAttr("cloudsmith_repository_retention_rule.test", "retention_size_limit", "0"), 36 | ), 37 | }, 38 | }, 39 | CheckDestroy: testAccRepositoryCheckDestroy("cloudsmith_repository.test-retention"), 40 | }) 41 | } 42 | 43 | var testAccRepositoryConfig = fmt.Sprintf(` 44 | resource "cloudsmith_repository" "test-retention" { 45 | name = "terraform-acc-repo-retention-rule" 46 | namespace = "%s" 47 | } 48 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 49 | 50 | var testAccRepositoryRetentionRuleConfigBasic = fmt.Sprintf(` 51 | resource "cloudsmith_repository" "test-retention" { 52 | name = "terraform-acc-repo-retention-rule" 53 | namespace = "%s" 54 | } 55 | 56 | resource "cloudsmith_repository_retention_rule" "test" { 57 | namespace = "%s" 58 | repository = cloudsmith_repository.test-retention.name 59 | retention_enabled = false 60 | retention_count_limit = 100 61 | retention_days_limit = 28 62 | retention_group_by_name = false 63 | retention_group_by_format = false 64 | retention_group_by_package_type = false 65 | retention_size_limit = 0 66 | } 67 | `, os.Getenv("CLOUDSMITH_NAMESPACE"), os.Getenv("CLOUDSMITH_NAMESPACE")) 68 | -------------------------------------------------------------------------------- /cloudsmith/resource_repository_test.go: -------------------------------------------------------------------------------- 1 | //nolint:testpackage 2 | package cloudsmith 3 | 4 | import ( 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "testing" 9 | 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 11 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 12 | ) 13 | 14 | // TestAccRepository_basic spins up a repository with all default options, 15 | // verifies it exists and checks the name is set correctly. Then it changes the 16 | // name, and verifies it's been set correctly before tearing down the resource 17 | // and verifying deletion. 18 | // 19 | // NOTE: It is not necessary to check properties that have been explicitly set 20 | // as Terraform performs a drift/plan check after every step anyway. Only 21 | // computed properties need explicitly checked. 22 | func TestAccRepository_basic(t *testing.T) { 23 | t.Parallel() 24 | 25 | resource.Test(t, resource.TestCase{ 26 | PreCheck: func() { testAccPreCheck(t) }, 27 | Providers: testAccProviders, 28 | CheckDestroy: testAccRepositoryCheckDestroy("cloudsmith_repository.test"), 29 | Steps: []resource.TestStep{ 30 | { 31 | Config: testAccRepositoryConfigBasic, 32 | Check: resource.ComposeTestCheckFunc( 33 | testAccRepositoryCheckExists("cloudsmith_repository.test"), 34 | // check a sample of computed properties have been set correctly 35 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "contextual_auth_realm", "true"), 36 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "copy_own", "true"), 37 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "copy_packages", "Read"), 38 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "docker_refresh_tokens_enabled", "false"), 39 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "is_private", "true"), 40 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "is_public", "false"), 41 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "replace_packages_by_default", "false"), 42 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "use_vulnerability_scanning", "true"), 43 | ), 44 | }, 45 | { 46 | Config: testAccRepositoryConfigBasicUpdateName, 47 | Check: resource.ComposeTestCheckFunc( 48 | testAccRepositoryCheckExists("cloudsmith_repository.test"), 49 | ), 50 | }, 51 | { 52 | Config: testAccRepositoryConfigBasicInvalidProp, 53 | ExpectError: regexp.MustCompile("expected copy_packages to be one of"), 54 | }, 55 | { 56 | Config: testAccRepositoryConfigBasicUpdateProps, 57 | Check: resource.ComposeTestCheckFunc( 58 | testAccRepositoryCheckExists("cloudsmith_repository.test"), 59 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "tag_pre_releases_as_latest", "true"), 60 | resource.TestCheckResourceAttr("cloudsmith_repository.test", "use_entitlements_privilege", "Admin"), 61 | ), 62 | }, 63 | { 64 | ResourceName: "cloudsmith_repository.test", 65 | ImportState: true, 66 | ImportStateIdFunc: func(s *terraform.State) (string, error) { 67 | resourceState := s.RootModule().Resources["cloudsmith_repository.test"] 68 | return fmt.Sprintf( 69 | "%s.%s", 70 | resourceState.Primary.Attributes["namespace"], 71 | resourceState.Primary.Attributes["slug"], 72 | ), nil 73 | }, 74 | ImportStateVerify: true, 75 | ImportStateVerifyIgnore: []string{"wait_for_deletion"}, 76 | }, 77 | }, 78 | }) 79 | } 80 | 81 | //nolint:goerr113 82 | func testAccRepositoryCheckDestroy(resourceName string) resource.TestCheckFunc { 83 | return func(s *terraform.State) error { 84 | resourceState, ok := s.RootModule().Resources[resourceName] 85 | if !ok { 86 | return fmt.Errorf("resource not found: %s", resourceName) 87 | } 88 | 89 | if resourceState.Primary.ID == "" { 90 | return fmt.Errorf("resource id not set") 91 | } 92 | 93 | pc := testAccProvider.Meta().(*providerConfig) 94 | 95 | req := pc.APIClient.ReposApi.ReposRead(pc.Auth, os.Getenv("CLOUDSMITH_NAMESPACE"), resourceState.Primary.ID) 96 | _, resp, err := pc.APIClient.ReposApi.ReposReadExecute(req) 97 | if err != nil && !is404(resp) { 98 | return fmt.Errorf("unable to verify repository deletion: %w", err) 99 | } else if is200(resp) { 100 | return fmt.Errorf("unable to verify repository deletion: still exists: %s/%s", os.Getenv("CLOUDSMITH_NAMESPACE"), resourceState.Primary.ID) 101 | } 102 | defer resp.Body.Close() 103 | 104 | return nil 105 | } 106 | } 107 | 108 | //nolint:goerr113 109 | func testAccRepositoryCheckExists(resourceName string) resource.TestCheckFunc { 110 | return func(s *terraform.State) error { 111 | resourceState, ok := s.RootModule().Resources[resourceName] 112 | if !ok { 113 | return fmt.Errorf("resource not found: %s", resourceName) 114 | } 115 | 116 | if resourceState.Primary.ID == "" { 117 | return fmt.Errorf("resource id not set") 118 | } 119 | 120 | pc := testAccProvider.Meta().(*providerConfig) 121 | 122 | req := pc.APIClient.ReposApi.ReposRead(pc.Auth, os.Getenv("CLOUDSMITH_NAMESPACE"), resourceState.Primary.ID) 123 | _, resp, err := pc.APIClient.ReposApi.ReposReadExecute(req) 124 | if err != nil { 125 | return err 126 | } 127 | defer resp.Body.Close() 128 | 129 | return nil 130 | } 131 | } 132 | 133 | var testAccRepositoryConfigBasic = fmt.Sprintf(` 134 | resource "cloudsmith_repository" "test" { 135 | name = "terraform-acc-test" 136 | namespace = "%s" 137 | } 138 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 139 | 140 | var testAccRepositoryConfigBasicUpdateName = fmt.Sprintf(` 141 | resource "cloudsmith_repository" "test" { 142 | name = "terraform-acc-test-update" 143 | namespace = "%s" 144 | } 145 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 146 | 147 | var testAccRepositoryConfigBasicInvalidProp = fmt.Sprintf(` 148 | resource "cloudsmith_repository" "test" { 149 | name = "terraform-acc-test-update" 150 | namespace = "%s" 151 | 152 | copy_packages = "Sudo" 153 | } 154 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 155 | 156 | var testAccRepositoryConfigBasicUpdateProps = fmt.Sprintf(` 157 | resource "cloudsmith_repository" "test" { 158 | name = "terraform-acc-test-update" 159 | namespace = "%s" 160 | 161 | contextual_auth_realm = false 162 | copy_packages = "Write" 163 | docker_refresh_tokens_enabled = true 164 | replace_packages_by_default = true 165 | use_vulnerability_scanning = false 166 | tag_pre_releases_as_latest = true 167 | use_entitlements_privilege = "Admin" 168 | } 169 | `, os.Getenv("CLOUDSMITH_NAMESPACE")) 170 | -------------------------------------------------------------------------------- /cloudsmith/resource_saml_auth_test.go: -------------------------------------------------------------------------------- 1 | package cloudsmith 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" 10 | "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" 11 | ) 12 | 13 | func TestAccSAMLAuth_basic(t *testing.T) { 14 | resource.Test(t, resource.TestCase{ 15 | PreCheck: func() { testAccPreCheck(t) }, 16 | Providers: testAccProviders, 17 | CheckDestroy: testAccSAMLAuthCheckDestroy("cloudsmith_saml_auth.test"), 18 | Steps: []resource.TestStep{ 19 | { 20 | // Basic configuration with URL-based metadata 21 | Config: testAccSAMLAuthConfigBasic, 22 | Check: resource.ComposeTestCheckFunc( 23 | testAccSAMLAuthCheckExists("cloudsmith_saml_auth.test"), 24 | resource.TestCheckResourceAttr("cloudsmith_saml_auth.test", "saml_auth_enabled", "true"), 25 | resource.TestCheckResourceAttr("cloudsmith_saml_auth.test", "saml_auth_enforced", "false"), 26 | resource.TestCheckResourceAttr("cloudsmith_saml_auth.test", "saml_metadata_url", "https://test.idp.example.com/metadata.xml"), 27 | resource.TestCheckNoResourceAttr("cloudsmith_saml_auth.test", "saml_metadata_inline"), 28 | ), 29 | }, 30 | { 31 | // Update to use inline metadata 32 | Config: testAccSAMLAuthConfigInlineMetadata, 33 | Check: resource.ComposeTestCheckFunc( 34 | testAccSAMLAuthCheckExists("cloudsmith_saml_auth.test"), 35 | resource.TestCheckResourceAttr("cloudsmith_saml_auth.test", "saml_metadata_inline", testSAMLMetadata), 36 | resource.TestCheckResourceAttr("cloudsmith_saml_auth.test", "saml_metadata_url", ""), 37 | ), 38 | }, 39 | { 40 | // Enable enforcement 41 | Config: testAccSAMLAuthConfigEnforced, 42 | Check: resource.ComposeTestCheckFunc( 43 | testAccSAMLAuthCheckExists("cloudsmith_saml_auth.test"), 44 | resource.TestCheckResourceAttr("cloudsmith_saml_auth.test", "saml_auth_enforced", "true"), 45 | ), 46 | }, 47 | { 48 | ResourceName: "cloudsmith_saml_auth.test", 49 | ImportState: true, 50 | ImportStateIdFunc: func(s *terraform.State) (string, error) { 51 | return os.Getenv("CLOUDSMITH_NAMESPACE"), nil 52 | }, 53 | ImportStateVerify: true, 54 | }, 55 | }, 56 | }) 57 | } 58 | 59 | func testAccSAMLAuthCheckDestroy(resourceName string) resource.TestCheckFunc { 60 | return func(s *terraform.State) error { 61 | resourceState, ok := s.RootModule().Resources[resourceName] 62 | if !ok { 63 | return fmt.Errorf("resource not found: %s", resourceName) 64 | } 65 | 66 | if resourceState.Primary.ID == "" { 67 | return fmt.Errorf("resource id not set") 68 | } 69 | 70 | pc := testAccProvider.Meta().(*providerConfig) 71 | organization := resourceState.Primary.Attributes["organization"] 72 | 73 | req := pc.APIClient.OrgsApi.OrgsSamlAuthenticationRead(pc.Auth, organization) 74 | samlAuth, resp, err := pc.APIClient.OrgsApi.OrgsSamlAuthenticationReadExecute(req) 75 | if err != nil && !is404(resp) { 76 | return fmt.Errorf("unable to verify SAML auth deletion: %w", err) 77 | } 78 | defer resp.Body.Close() 79 | 80 | // Resource is considered destroyed if SAML auth is disabled 81 | if samlAuth != nil && samlAuth.GetSamlAuthEnabled() { 82 | return fmt.Errorf("SAML authentication still enabled for organization: %s", organization) 83 | } 84 | 85 | return nil 86 | } 87 | } 88 | 89 | func testAccSAMLAuthCheckExists(resourceName string) resource.TestCheckFunc { 90 | return func(s *terraform.State) error { 91 | resourceState, ok := s.RootModule().Resources[resourceName] 92 | if !ok { 93 | return fmt.Errorf("resource not found: %s", resourceName) 94 | } 95 | 96 | if resourceState.Primary.ID == "" { 97 | return fmt.Errorf("resource id not set") 98 | } 99 | 100 | pc := testAccProvider.Meta().(*providerConfig) 101 | organization := resourceState.Primary.Attributes["organization"] 102 | 103 | req := pc.APIClient.OrgsApi.OrgsSamlAuthenticationRead(pc.Auth, organization) 104 | _, resp, err := pc.APIClient.OrgsApi.OrgsSamlAuthenticationReadExecute(req) 105 | if err != nil { 106 | return fmt.Errorf("error checking SAML auth existence: %w", err) 107 | } 108 | defer resp.Body.Close() 109 | 110 | return nil 111 | } 112 | } 113 | 114 | // Sample SAML metadata for testing 115 | var testSAMLMetadata = strings.TrimSpace(` 116 | 117 | 118 | 120 | 121 | `) 122 | 123 | var testAccSAMLAuthConfigBasic = strings.TrimSpace(fmt.Sprintf(` 124 | resource "cloudsmith_saml_auth" "test" { 125 | organization = "%s" 126 | saml_auth_enabled = true 127 | saml_auth_enforced = false 128 | saml_metadata_url = "https://test.idp.example.com/metadata.xml" 129 | } 130 | `, os.Getenv("CLOUDSMITH_NAMESPACE"))) 131 | 132 | var testAccSAMLAuthConfigInlineMetadata = strings.TrimSpace(fmt.Sprintf(` 133 | resource "cloudsmith_saml_auth" "test" { 134 | organization = "%s" 135 | saml_auth_enabled = true 136 | saml_auth_enforced = false 137 | saml_metadata_inline = < **WARNING:** This data source is deprecated and will be removed in future. Use `cloudsmith_organization` instead. 4 | 5 | The `namespace` data source allows fetching of metadata about a given Cloudsmith namespace. The fetched data can be used to resolve permanent identifiers from a namespace's user-facing name. These identifiers can then be passed to other resources to allow more consistent identification as user-facing names can change. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_namespace" "my_namespace" { 15 | slug = "my-namespace" 16 | } 17 | ``` 18 | 19 | ## Argument Reference 20 | 21 | * `slug` - (Required) The slug identifies the namespace in URIs. 22 | 23 | ## Attribute Reference 24 | 25 | * `name` - A descriptive name for the namespace. 26 | * `slug` - The slug identifies the namespace in URIs. 27 | * `slug_perm` - The slug_perm immutably identifies the namespace. It will never change once a namespace has been created. 28 | * `type_name` - Is this a user or an organization namespace?. 29 | -------------------------------------------------------------------------------- /docs/data-sources/organization.md: -------------------------------------------------------------------------------- 1 | # Organization Data Source 2 | 3 | The `organization` data source allows fetching of metadata about a given Cloudsmith organization. The fetched data can be used to resolve permanent identifiers from an organization's user-facing name. These identifiers can then be passed to other resources to allow more consistent identification as user-facing names can change. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | data "cloudsmith_organization" "my_organization" { 13 | slug = "my-organization" 14 | } 15 | ``` 16 | 17 | ## Argument Reference 18 | 19 | * `slug` - (Required) The slug identifies the organization in URIs. 20 | 21 | ## Attribute Reference 22 | 23 | * `country` - Country in which the organization is based. 24 | * `created_at` - ISO 8601 timestamp at which the organization was created. 25 | * `location` - The city/town/area in which the organization is based. 26 | * `name` - A descriptive name for the organization. 27 | * `slug` - The slug identifies the organization in URIs. 28 | * `slug_perm` - The slug_perm immutably identifies the organization. It will never change once a organization has been created. 29 | * `tagline` - A short public description for the organization. 30 | -------------------------------------------------------------------------------- /docs/data-sources/organization_list_org_members.md: -------------------------------------------------------------------------------- 1 | # Organization Member List Data Source 2 | 3 | Get the details for all organization members. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | 9 | provider "cloudsmith" { 10 | api_key = "my-api-key" 11 | } 12 | 13 | data "cloudsmith_organization" "my_organization" { 14 | slug = "my-organization" 15 | } 16 | 17 | data "cloudsmith_list_org_members" "test" { 18 | namespace = my_organization.slug_perm 19 | is_active = true 20 | } 21 | ``` 22 | 23 | ## Argument Reference 24 | 25 | * `namespace` - (Required) Namespace to which the org members belong to. 26 | * `is_active` - (Optional) Filter for active/inactive users. Default is `true`. 27 | 28 | All of the argument attributes are also exported as result attributes. 29 | 30 | The following attribute is additionally exported: 31 | 32 | * `members` - (Computed) A list of organization members. Each member has the following attributes: 33 | * `email` - The email address of the member. 34 | * `has_two_factor` - Indicates if the member has two-factor authentication enabled. 35 | * `is_active` - Indicates if the member is active. 36 | * `joined_at` - The date and time when the member joined the organization. 37 | * `last_login_at` - The date and time when the member last logged in. 38 | * `last_login_method` - The method used by the member for the last login. 39 | * `role` - The role of the member within the organization. 40 | * `user` - The username of the member. 41 | * `user_id` - The unique identifier of the member. 42 | -------------------------------------------------------------------------------- /docs/data-sources/organization_member_details.md: -------------------------------------------------------------------------------- 1 | # Organization Member Details Data Resource 2 | 3 | Get the details for a specific organization member. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | data "cloudsmith_organization" "my_organization" { 13 | slug = "my-organization" 14 | } 15 | 16 | data "cloudsmith_org_member_details" "my_org_member_details" { 17 | organization = data.cloudsmith_organization.my_organization.slug 18 | member = "username-or-email" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | * `organization` - (Required) Organization to which the org member belongs to. 25 | * `member` - (Required) The username, slug or email of the member. 26 | 27 | All of the argument attributes are also exported as result attributes. 28 | 29 | The following attribute is additionally exported: 30 | 31 | * `email` - The email address of the organization member (string). 32 | * `has_two_factor` - Indicates whether the member has two-factor authentication enabled (boolean). 33 | * `is_active` - Indicates whether the member is active (boolean). 34 | * `joined_at` - The date and time when the member joined the organization (date-time). 35 | * `last_login_at` - The date and time of the member's last login (date-time | null). 36 | * `last_login_method` - The method used for the member's last login. Defaults to "Unknown" and can be one of the following: "Unknown", "Password", "Social", "SAML" (string). 37 | * `role` - The role of the member within the organization. Defaults to "Owner" and can be one of the following: "Owner", "Manager", "Member", "Collaborator" (string). 38 | * `user` - Information about the user associated with the member. 39 | * `user_id` - The ID of the user (string). 40 | * `user_name` - The username of the user (string). 41 | * `user_url` - The URL of the user's profile (uri). 42 | * `visibility` - The visibility of the member's profile. Defaults to "Public" and can be one of the following: "Public", "Private" (string). 43 | -------------------------------------------------------------------------------- /docs/data-sources/package.md: -------------------------------------------------------------------------------- 1 | # Package Data Source 2 | 3 | The `cloudsmith_package` data source allows you to list details and download a specific package from a given repository. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | resource "cloudsmith_repository" "test" { 13 | name = "terraform-acc-test-package" 14 | namespace = "" 15 | } 16 | 17 | data "cloudsmith_package_list" "test" { 18 | repository = cloudsmith_repository.test.name 19 | namespace = cloudsmith_repository.test.namespace 20 | filters = [ 21 | "name:dummy-package", 22 | "version:1.0.48", 23 | ] 24 | } 25 | 26 | data "cloudsmith_package" "test" { 27 | repository = cloudsmith_repository.test.name 28 | namespace = cloudsmith_repository.test.namespace 29 | identifier = data.cloudsmith_package_list.test.packages[0].slug_perm 30 | download = true 31 | download_dir = "/path/to/your/directory" 32 | } 33 | ``` 34 | 35 | ## Argument Reference 36 | 37 | - `namespace` (Required): The namespace of the package. 38 | - `repository` (Required): The repository of the package. 39 | - `identifier` (Required): The identifier for the package. 40 | - `download` (Optional): If set to true, the package will be downloaded. Defaults to false. If set to false, the CDN URL will be available in the `output_path`. 41 | - `download_dir` (Optional): The directory where the file will be downloaded to. If not set and `download` is set to `true`, it will default to the operating system's default temporary directory and save the file there. 42 | - `ignore_checksums` (Optional): If set to `true`, any mismatched checksum from our API and local check will be ignored and download the package if `download` is set to `true`. 43 | 44 | ## Attribute Reference 45 | 46 | - `cdn_url`: The URL of the package to download. This attribute is computed and available only when the `download` argument is set to `false`. 47 | - `checksum_md5`: MD5 hash of the downloaded package. If `download` is set to `false`, the checksum is returned from the package API instead. 48 | - `checksum_sha1`: SHA1 hash of the downloaded package.If `download` is set to `false`, the checksum is returned from the package API instead. 49 | - `checksum_sha256`: SHA256 hash of the downloaded package.If `download` is set to `false`, the checksum is returned from the package API instead. 50 | - `checksum_sha512`: SHA512 hash of the downloaded package.If `download` is set to `false`, the checksum is returned from the package API instead. 51 | - `format`: The format of the package. 52 | - `is_sync_awaiting`: Indicates whether the package is awaiting synchronization. 53 | - `is_sync_completed`: Indicates whether the package synchronization has completed. 54 | - `is_sync_failed`: Indicates whether the package synchronization has failed. 55 | - `is_sync_in_flight`: Indicates whether the package synchronization is currently in-flight. 56 | - `is_sync_in_progress`: Indicates whether the package synchronization is currently in-progress. 57 | - `name`: The name of the package. 58 | - `output_path`: The location of the package. If the `download` argument is set to `true`, this will provide the path where the package is downloaded. 59 | - `output_directory`: The directory where the package is downloaded. 60 | - `slug`: The public unique identifier for the package. 61 | - `slug_perm`: The slug_perm that immutably identifies the package. 62 | - `version`: The version of the package. 63 | -------------------------------------------------------------------------------- /docs/data-sources/package_list.md: -------------------------------------------------------------------------------- 1 | # Package List Data Source 2 | 3 | The `package_list` data source allows for retrieval of a list of packages within a given repository. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | data "cloudsmith_namespace" "my_namespace" { 13 | slug = "my-namespace" 14 | } 15 | 16 | data "cloudsmith_repository" "my_repository" { 17 | namespace = data.cloudsmith_namespace.my_namespace.slug_perm 18 | identifier = "my-repository" 19 | } 20 | 21 | data "cloudsmith_package_list" "my_packages" { 22 | namespace = data.cloudsmith_repository.my_repository.namespace 23 | repository = data.cloudsmith_repository.my_repository.slug_perm 24 | 25 | package_group = "my-package" 26 | filters = ["format:docker"] 27 | } 28 | 29 | output "packages" { 30 | value = formatlist("%s-%s", data.cloudsmith_package_list.my_packages.packages.*.name, data.cloudsmith_package_List.my_packages.*.version) 31 | } 32 | ``` 33 | 34 | ## Argument Reference 35 | 36 | * `namespace` - (Required) Namespace to which the packages belong. 37 | * `repository` - (Required) Repository `slug_perm` to which the packages belong. 38 | * `filters` - (Optional) A list of Cloudsmith search filters (e.g `format:docker`, `name:^foo`). 39 | * `most_recent` - (Optional) When `true`, only the most recent package resolved will be returned. 40 | 41 | ## Attribute Reference 42 | 43 | All of the argument attributes are also exported as result attributes. 44 | 45 | The following attribute is additionally exported: 46 | 47 | * `packages` - A list of `package` entries as discovered by the data source. 48 | -------------------------------------------------------------------------------- /docs/data-sources/repository_privileges.md: -------------------------------------------------------------------------------- 1 | # Repository Privileges Data Source 2 | 3 | The `cloudsmith_repository_privileges` data source allows you to retrieve information about repository privileges, including service accounts, teams, and users, for a specific repository. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | resource "cloudsmith_repository" "test" { 13 | name = "terraform-acc-test-privileges" 14 | namespace = "" 15 | } 16 | 17 | data "cloudsmith_repository_privileges" "test_data" { 18 | organization = cloudsmith_repository.test.namespace 19 | repository = cloudsmith_repository.test.slug 20 | } 21 | ``` 22 | 23 | ## Argument Reference 24 | 25 | * organization (Required): The organization to which the repository belongs. 26 | * repository (Required): The repository for which privileges information is retrieved. 27 | 28 | ## Attribute Reference 29 | 30 | The following attributes are available: 31 | 32 | * service: A set containing privileges information for service accounts. 33 | * privilege: The privilege level (Admin, Write, Read). 34 | * slug: The unique identifier for the service account. 35 | 36 | * team: A set containing privileges information for teams. 37 | * privilege: The privilege level (Admin, Write, Read). 38 | * slug: The unique identifier for the team. 39 | 40 | * user: A set containing privileges information for users. 41 | * privilege: The privilege level (Admin, Write, Read). 42 | * slug: The unique identifier for the user. 43 | -------------------------------------------------------------------------------- /docs/data-sources/user_self.md: -------------------------------------------------------------------------------- 1 | # User Self Data Source 2 | 3 | The `cloudsmith_user_self` data source provides information about the currently authenticated user. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | data "cloudsmith_user_self" "current" {} 9 | 10 | # Reference user attributes 11 | output "current_user_email" { 12 | value = data.cloudsmith_user_self.current.email 13 | } 14 | 15 | output "current_user_slug" { 16 | value = data.cloudsmith_user_self.current.slug 17 | } 18 | ``` 19 | 20 | ## Attribute Reference 21 | 22 | * `email` - (String) The email address associated with the authenticated user account. 23 | * `name` - (String) The full name of the authenticated user as configured in their profile. 24 | * `slug` - (String) The URL-friendly identifier used in URIs. This may change if the user's name is updated. 25 | * `slug_perm` - (String) The permanent, immutable identifier that uniquely identifies the user. This value remains constant even if other user properties change. 26 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Cloudsmith Provider 2 | 3 | This provider allows Cloudsmith users to automate the provisioning of resources using Terraform. Users can create and manage repositories, along with entitlement tokens to grant access to repository contents. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/) for full documentation (including an API reference). 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_namespace" "my_namespace" { 15 | slug = "my-namespace" 16 | } 17 | 18 | resource "cloudsmith_repository" "my_repository" { 19 | description = "A certifiably-awesome private package repository" 20 | name = "My Repository" 21 | namespace = "${data.cloudsmith_namespace.my_namespace.slug_perm}" 22 | slug = "my-repository" 23 | } 24 | 25 | resource "cloudsmith_entitlement" "my_entitlement" { 26 | name = "Test Entitlement" 27 | namespace = "${cloudsmith_repository.test.namespace}" 28 | repository = "${cloudsmith_repository.test.slug_perm}" 29 | } 30 | ``` 31 | 32 | ## Argument Reference 33 | 34 | * `api_key` - (Required) The API key for authenticating with the Cloudsmith API. 35 | * `api_host` - (Optional) The API host to connect to (used to connect to a non-production Cloudsmith instance, mostly useful for testing). 36 | -------------------------------------------------------------------------------- /docs/resources/entitlement.md: -------------------------------------------------------------------------------- 1 | # Entitlement Resource 2 | 3 | The entitlement resource allows the creation and management of Entitlement tokens for a given Cloudsmith repository. Entitlement tokens grant read-only access to a repository and can be configured with a number of custom restrictions if necessary. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/entitlements) for full entitlement documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_organization" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_repository" "my_repository" { 19 | description = "A certifiably-awesome private package repository" 20 | name = "My Repository" 21 | namespace = "${data.cloudsmith_organization.my_organization.slug_perm}" 22 | slug = "my-repository" 23 | } 24 | 25 | resource "cloudsmith_entitlement" "my_entitlement" { 26 | name = "Test Entitlement" 27 | namespace = "${cloudsmith_repository.test.namespace}" 28 | repository = "${cloudsmith_repository.test.slug_perm}" 29 | } 30 | ``` 31 | 32 | ## Argument Reference 33 | 34 | * `is_active` - (Optional) If enabled, the token will allow downloads based on configured restrictions (if any). 35 | * `limit_date_range_from` - (Optional) The starting date/time the token is allowed to be used from. 36 | * `limit_date_range_to` - (Optional) The ending date/time the token is allowed to be used until. 37 | * `limit_num_clients` - (Optional) The maximum number of unique clients allowed for the token. Please note that since clients are calculated asynchronously (after the download happens), the limit may not be imposed immediately but at a later point. 38 | * `limit_num_downloads` - (Optional) The maximum number of downloads allowed for the token. Please note that since downloads are calculated asynchronously (after the download happens), the limit may not be imposed immediately but at a later point. 39 | * `limit_package_query` - (Optional) The package-based search query to apply to restrict downloads to. This uses the same syntax as the standard search used for repositories, and also supports boolean logic operators such as OR/AND/NOT and parentheses for grouping. This will still allow access to non-package files, such as metadata. 40 | * `limit_path_query` - (Optional) The path-based search query to apply to restrict downloads to. This supports boolean logic operators such as OR/AND/NOT and parentheses for grouping. The path evaluated does not include the domain name, the namespace, the entitlement code used, the package format, etc. and it always starts with a forward slash. 41 | * `name` - (Required) A descriptive name for the entitlement. 42 | * `namespace` - (Required) Namespace (or organization) to which this entitlement belongs. 43 | * `repository` - (Required) Repository to which this entitlement belongs. 44 | * `token` - (Optional) The literal value of the token to be created. 45 | 46 | ## Attribute Reference 47 | 48 | * `is_active` - If enabled, the token will allow downloads based on configured restrictions (if any). 49 | * `limit_date_range_from` - The starting date/time the token is allowed to be used from. 50 | * `limit_date_range_to` - The ending date/time the token is allowed to be used until. 51 | * `limit_num_clients` - The maximum number of unique clients allowed for the token. Please note that since clients are calculated asynchronously (after the download happens), the limit may not be imposed immediately but at a later point. 52 | * `limit_num_downloads` - The maximum number of downloads allowed for the token. Please note that since downloads are calculated asynchronously (after the download happens), the limit may not be imposed immediately but at a later point. 53 | * `limit_package_query` - The package-based search query to apply to restrict downloads to. This uses the same syntax as the standard search used for repositories, and also supports boolean logic operators such as OR/AND/NOT and parentheses for grouping. This will still allow access to non-package files, such as metadata. 54 | * `limit_path_query` - The path-based search query to apply to restrict downloads to. This supports boolean logic operators such as OR/AND/NOT and parentheses for grouping. The path evaluated does not include the domain name, the namespace, the entitlement code used, the package format, etc. and it always starts with a forward slash. 55 | * `name` - A descriptive name for the entitlement. 56 | * `namespace` - Namespace to which this entitlement belongs. 57 | * `repository` - Repository to which this entitlement belongs. 58 | * `token` - The literal value of the token to be created. 59 | 60 | ## Import 61 | 62 | This resource can be imported using the organization slug, the repository slug, and the entitlement slug: 63 | 64 | ```shell 65 | terraform import cloudsmith_entitlement.my_entitlement my-organization.my-repository.3nt1lem3nT 66 | ``` 67 | -------------------------------------------------------------------------------- /docs/resources/entitlement_control.md: -------------------------------------------------------------------------------- 1 | # Entitlement Control Resource 2 | 3 | The entitlement control resource allows enabling and disabling of existing Entitlement tokens for a given Cloudsmith repository. This provides a way to manage the active state of entitlement tokens without modifying their other properties. 4 | 5 | > ⚠️ **We highly recommend controlling the entitlement token with the [`cloudsmith_entitlement` resource](../resources/entitlement.md) and the `is_active` flag which controls the same setting. The purpose of this resource is to manage the "Default" entitlement token which is created by default for all repositories.** 6 | 7 | 8 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/entitlements) for full entitlement documentation. 9 | 10 | ## Example Usage 11 | 12 | > **Note:** Ensure `entitlement_tokens` array is returning entitlement tokens (and not an empty array) before using the control resource. By default the example should work, but if there were any changes made to repo settings, the expected behaviour might be different. 13 | 14 | Disable repository "Default" entitlement token: 15 | 16 | ```hcl 17 | provider "cloudsmith" { 18 | api_key = "my-api-key" 19 | } 20 | 21 | data "cloudsmith_organization" "my_organization" { 22 | slug = "my-organization" 23 | } 24 | 25 | resource "cloudsmith_repository" "my_repository" { 26 | description = "A certifiably-awesome private package repository" 27 | name = "My Repository" 28 | namespace = "${data.cloudsmith_organization.my_organization.slug_perm}" 29 | slug = "my-repository" 30 | } 31 | 32 | data "cloudsmith_entitlement_list" "my_tokens" { 33 | namespace = resource.cloudsmith_repository.my_repository.namespace 34 | repository = resource.cloudsmith_repository.my_repository.slug_perm 35 | 36 | query = ["name:Default"] 37 | } 38 | 39 | resource "cloudsmith_entitlement_control" "my_entitlement_control" { 40 | namespace = resource.cloudsmith_repository.my_repository.namespace 41 | repository = resource.cloudsmith_repository.my_repository.slug_perm 42 | identifier = data.cloudsmith_entitlement_list.my_tokens.entitlement_tokens[0].slug_perm 43 | enabled = false 44 | } 45 | ``` 46 | 47 | ## Argument Reference 48 | 49 | * `namespace` - (Required) Namespace (or organization) to which this entitlement belongs. 50 | * `repository` - (Required) Repository to which this entitlement belongs. 51 | * `identifier` - (Required) The identifier (slug_perm) of the entitlement token to control. 52 | * `enabled` - (Required) Whether the entitlement token should be enabled or disabled. 53 | 54 | ## Attribute Reference 55 | 56 | * `namespace` - Namespace to which this entitlement belongs. 57 | * `repository` - Repository to which this entitlement belongs. 58 | * `identifier` - The identifier (slug_perm) of the entitlement token. 59 | * `enabled` - Whether the entitlement token is enabled or disabled. 60 | 61 | ## Import 62 | 63 | This resource can be imported using the organization slug, the repository slug, and the entitlement slug: 64 | 65 | ```shell 66 | terraform import cloudsmith_entitlement_control.my_entitlement_control my-organization.my-repository.3nt1lem3nT 67 | ``` 68 | -------------------------------------------------------------------------------- /docs/resources/license_policy.md: -------------------------------------------------------------------------------- 1 | # License Policy Resource 2 | 3 | The license policy resource allows for creation and management of license policies within a Cloudsmith organization. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/license-policies) for the full license policies documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_organization" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_license_policy" "my_license_policy" { 19 | name = "My Policy" 20 | description = "My license policy" 21 | spdx_identifiers = ["Apache-2.0"] 22 | on_violation_quarantine = true 23 | package_query_string = "format:python AND downloads:>50" 24 | organization = "my-organization" 25 | } 26 | ``` 27 | 28 | ## Argument Reference 29 | 30 | The following arguments are supported: 31 | 32 | * `organization` - (Required) Organization to which the policy belongs. 33 | * `name` - (Required) The name of the license policy. 34 | * `description` - (Required) The description of the license policy. 35 | * `spdx_identifiers` - (Required) The licenses to deny. 36 | * `on_violation_quarantine` - (Optional) On violation of the license policy, quarantine violating packages. 37 | * `allow_unknown_licenses` - (Optional) Allow unknown licenses within the policy. 38 | * `package_query_string` - (Optional) A search / filter string of packages to include in the policy. 39 | 40 | ## Import 41 | 42 | This resource can be imported using the organization slug. 43 | 44 | ```shell 45 | terraform import cloudsmith_license_policy.my_policy my-organization 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/resources/manage_team.md: -------------------------------------------------------------------------------- 1 | # Team Manage Resource 2 | 3 | This resource is used to manage teams in Cloudsmith. It allows you to add, update, and remove team members. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | resource "cloudsmith_manage_team" "example" { 9 | organization = "example_org" 10 | team_name = "example_team" 11 | members { 12 | role = "Manager" 13 | user = "user1" 14 | } 15 | members { 16 | role = "Member" 17 | user = "user2" 18 | } 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | > :warning: **Warning**: When running this resource with a **USER API** token (Not applicable to users running this with Service Account API Key) on **newly created teams**, the user will automatically get added to the team causing a `422 error` when specified in the resource as well. We highly advise using a service account for this purpose to avoid the error. Alternatively, the plan needs to be run without the user in the resource and re-added after the first apply happens. 25 | 26 | The following arguments are supported: 27 | 28 | - `organization` - (Required) The slug of the organization. 29 | - `team_name` - (Required) The name of the team. 30 | - `members` - (Required) A list of members to be added to the team. Each member is a map containing `role` and `user`. The role can only be set to "Manager" or "Member". 31 | 32 | ## Attribute Reference 33 | 34 | The following attributes are exported: 35 | 36 | - `organization` - The slug of the organization. 37 | - `team_name` - The name of the team. 38 | - `members` - A list of team members. Each member is a map containing `role` and `user`. 39 | 40 | ## Import 41 | 42 | Existing teams can be imported using the organization slug and team name, separated by a dot. For example: 43 | 44 | ```hcl 45 | terraform import cloudsmith_manage_team.example example_org.example_team 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/resources/oidc.md: -------------------------------------------------------------------------------- 1 | # OIDC Resource 2 | 3 | The OIDC resource allows the creation and management of OpenID Connect (OIDC) configurations for a given Cloudsmith organization. OIDC configurations allow integration with external systems by providing OpenID Connect authentication. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | data "cloudsmith_organization" "my_organization" { 13 | slug = "my-organization" 14 | } 15 | 16 | resource "cloudsmith_oidc" "my_oidc" { 17 | namespace = data.cloudsmith_organization.my_organization.slug_perm 18 | name = "My OIDC" 19 | enabled = true 20 | provider_url = "https://example.com" 21 | service_accounts = ["account1", "account2"] 22 | claims = { 23 | "claim1" = "value1" 24 | "claim2" = "value2" 25 | } 26 | } 27 | ``` 28 | 29 | ## Argument Reference 30 | 31 | * `claims` - (Required) The claims associated with these provider settings. 32 | * `enabled` - (Required) Whether the provider settings should be used for incoming OIDC requests. Default is `true`. 33 | * `name` - (Required) The name of the provider settings are being configured for. 34 | * `namespace` - (Required) Namespace (or organization) to which this OIDC config belongs. 35 | * `provider_url` - (Required) The URL from the provider that serves as the base for the OpenID configuration. 36 | * `service_accounts` - (Required) The service accounts associated with these provider settings. 37 | * `slug` - (Computed) The slug identifies the OIDC. 38 | * `slug_perm` - (Computed) The slug_perm identifies the OIDC. 39 | 40 | ## Attribute Reference 41 | 42 | * `slug` - The slug identifies the OIDC. 43 | * `slug_perm` - The slug_perm identifies the OIDC. 44 | 45 | ## Import 46 | 47 | This resource can be imported using the organization slug and the OIDC slug_perm: 48 | 49 | ```shell 50 | terraform import cloudsmith_oidc.my_oidc my-organization.my-oidc-slug-perm 51 | ``` 52 | -------------------------------------------------------------------------------- /docs/resources/package_deny_policy.md: -------------------------------------------------------------------------------- 1 | # Package Deny Policy Resource 2 | 3 | Create a package deny policy resource. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | data "cloudsmith_organization" "my_organization" { 13 | slug = "my-organization" 14 | } 15 | 16 | data "cloudsmith_package_deny_policy" "test" { 17 | namespace = my_organization.slug_perm 18 | enabled = true 19 | name = "test-package-deny-policy-terraform-provider" 20 | package_query = "name:example" 21 | } 22 | ``` 23 | 24 | ## Argument Reference 25 | 26 | The following arguments are supported: 27 | 28 | - `name` (Optional) - A descriptive name for the package deny policy. 29 | - `description` (Optional) - Description of the package deny policy. 30 | - `package_query` (Required) - The query to match the packages to be blocked. 31 | - `enabled` (Optional) - Is the package deny policy enabled? Defaults to `true` 32 | - `namespace` - The namespace where package deny policy is managed 33 | 34 | ## Attribute Reference 35 | 36 | The following attributes are exported: 37 | 38 | - `id` - The ID of the package deny policy. 39 | - `name` - The name of the package deny policy. 40 | - `description` - The description of the package deny policy. 41 | - `package_query` - The query used to match the packages to be blocked. 42 | - `enabled` - Whether the package deny policy is enabled. 43 | - `namespace` - The namespace where package deny policy is managed 44 | -------------------------------------------------------------------------------- /docs/resources/repository_geo_ip_rules.md: -------------------------------------------------------------------------------- 1 | # Respository Geo/IP Rules Resource 2 | 3 | The repository geo/ip rules resource allows the management of geo/ip rules for a given Cloudsmith repository. Using this resource it is possible to allow and/or deny access to a repository using CIDR notation, two-character ISO 3166-1 country codes or a combination thereof. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/geoip-restriction) for full geo/ip rules documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_organization" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_repository" "my_repository" { 19 | description = "A certifiably-awesome private package repository" 20 | name = "My Repository" 21 | namespace = "${data.cloudsmith_organization.my_organization.slug_perm}" 22 | slug = "my-repository" 23 | } 24 | 25 | resource "cloudsmith_repository_geo_ip_rules" "my_rules" { 26 | namespace = "${data.cloudsmith_organization.my_organization.slug_perm}" 27 | repository = "${resource.cloudsmith_repository.my_repository.slug_perm}" 28 | cidr_allow = [ 29 | "10.0.0.0/24", 30 | "6cc2:ab98:2143:7e6e:8827:e81a:1527:9645/128", 31 | "140.59.25.1/32", 32 | ] 33 | cidr_deny = [ 34 | "83.154.136.12/32", 35 | "203.0.0.0/10", 36 | ] 37 | country_code_allow = [ 38 | "ST", 39 | "CM", 40 | ] 41 | country_code_deny = [ 42 | "CA", 43 | "WF", 44 | ] 45 | } 46 | ``` 47 | 48 | ## Argument Reference 49 | 50 | The following arguments are supported: 51 | 52 | * `namespace` - (Required) Organization to which the Repository belongs. 53 | * `repository` - (Required) Repository to which these Geo/IP rules apply. 54 | * `cidr_allow` - (Optional) The list of IP Addresses for which to allow access to the Repository, expressed in CIDR notation. 55 | * `cidr_deny` - (Optional) The list of IP Addresses for which to deny access to the Repository, expressed in CIDR notation. 56 | * `country_code_allow` - (Optional) The list of countries for which to allow access to the Repository, expressed in ISO 3166-1 country codes. 57 | * `country_code_deny` - (Optional) The list of countries for which to deny access to the Repository, expressed in ISO 3166-1 country codes. 58 | 59 | ## Import 60 | 61 | This resource can be imported using the organization slug, and the repository slug: 62 | 63 | ```shell 64 | terraform import cloudsmith_repository_geo_ip_rules.my_rules my-organization.my-repository 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/resources/repository_privileges.md: -------------------------------------------------------------------------------- 1 | # Respository Privileges Resource 2 | 3 | The repository privileges resource allows the management of privileges for a given Cloudsmith repository. Using this resource it is possible to assign users, teams, or service accounts to a repository, and define the appropriate permission level for each. 4 | 5 | Note that while users can be added to repositories in this manner, since Terraform does not (and cannot currently) manage those user accounts, you may encounter issues if the users change or are deleted outside of Terraform. 6 | 7 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/permissions#repository-permissions) for full permissions documentation. 8 | 9 | ## Example Usage 10 | 11 | ```hcl 12 | provider "cloudsmith" { 13 | api_key = "my-api-key" 14 | } 15 | 16 | data "cloudsmith_organization" "my_organization" { 17 | slug = "my-organization" 18 | } 19 | 20 | resource "cloudsmith_repository" "my_repository" { 21 | description = "A certifiably-awesome private package repository" 22 | name = "My Repository" 23 | namespace = data.cloudsmith_organization.my_organization.slug_perm 24 | slug = "my-repository" 25 | } 26 | 27 | resource "cloudsmith_team" "my_team" { 28 | organization = data.cloudsmith_organization.my_organization.slug_perm 29 | name = "My Team" 30 | } 31 | 32 | resource "cloudsmith_team" "my_other_team" { 33 | organization = data.cloudsmith_organization.my_organization.slug_perm 34 | name = "My Other Team" 35 | } 36 | 37 | resource "cloudsmith_service" "my_service" { 38 | name = "My Service" 39 | organization = data.cloudsmith_organization.my_organization.slug_perm 40 | } 41 | 42 | resource "cloudsmith_repository_privileges" "privs" { 43 | organization = data.cloudsmith_organization.my_organization.slug 44 | repository = cloudsmith_repository.my_repository.slug 45 | 46 | service { 47 | privilege = "Write" 48 | slug = cloudsmith_service.my_service.slug 49 | } 50 | 51 | team { 52 | privilege = "Write" 53 | slug = cloudsmith_team.my_team.slug 54 | } 55 | 56 | team { 57 | privilege = "Read" 58 | slug = cloudsmith_team.my_other_team.slug 59 | } 60 | 61 | user { 62 | privilege = "Read" 63 | slug = "some-user-slug" 64 | } 65 | } 66 | ``` 67 | 68 | ## Argument Reference 69 | 70 | The following arguments are supported: 71 | 72 | * `organization` - (Required) Organization to which this repository belongs. 73 | * `repository` - (Required) Repository to which these privileges apply. 74 | * `service` - (Optional) Variable number of blocks containing service accounts that should have repository privileges. 75 | * `privilege` - (Required) The service's privilege level in the repository. Must be one of `Admin`, `Write`, or `Read`. 76 | * `slug` - (Required) The slug/identifier of the service. 77 | * `team` - (Optional) Variable number of blocks containing teams that should have repository privileges. 78 | * `privilege` - (Required) The team's privilege level in the repository. Must be one of `Admin`, `Write`, or `Read`. 79 | * `slug` - (Required) The slug/identifier of the team. 80 | * `user` - (Optional) Variable number of blocks containing users that should have repository privileges. 81 | * `privilege` - (Required) The user's privilege level in the repository. Must be one of `Admin`, `Write`, or `Read`. 82 | * `slug` - (Required) The slug/identifier of the user. 83 | 84 | ## Import 85 | 86 | This resource can be imported using the organization slug, and the repository slug: 87 | 88 | ```shell 89 | terraform import cloudsmith_repository_privileges.privs my-organization.my-repository 90 | ``` 91 | -------------------------------------------------------------------------------- /docs/resources/repository_retention.md: -------------------------------------------------------------------------------- 1 | # Repository Retention Rules Resource 2 | 3 | The repository retention rules resource allows the management of retention rules for a given Cloudsmith repository. Using this resource, it is possible to define rules that control the retention of packages based on various criteria such as count, days, size, and grouping. 4 | 5 | Note that while retention rules can be managed in this manner, changes made outside of Terraform may not be reflected in the Terraform state. 6 | 7 | **Note: Retention rule settings are only applied once retention is enabled for the repository.** 8 | 9 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/retention-lifecycle#:~:text=Retention%20rules%20only%20activate%20when,1000%20day%20package%20be%20deleted.) for full retention rules documentation. 10 | 11 | ## Example Usage 12 | 13 | ```hcl 14 | provider "cloudsmith" { 15 | api_key = "my-api-key" 16 | } 17 | 18 | data "cloudsmith_organization" "my_organization" { 19 | slug = "my-organization" 20 | } 21 | 22 | resource "cloudsmith_repository" "my_repository" { 23 | name = "retention-rules" 24 | namespace = data.cloudsmith_organization.my_organization.slug_perm 25 | } 26 | 27 | resource "cloudsmith_repository_retention_rule" "retention_rule" { 28 | namespace = data.cloudsmith_organization.my_organization.slug 29 | repository = cloudsmith_repository.my_repository.slug 30 | retention_enabled = true 31 | retention_count_limit = 100 32 | retention_days_limit = 28 33 | retention_group_by_name = false 34 | retention_group_by_format = false 35 | retention_group_by_package_type = false 36 | retention_size_limit = 200000 37 | } 38 | ``` 39 | 40 | ## Argument Reference 41 | 42 | The following arguments are supported: 43 | 44 | * `namespace` - (Required) The namespace of the repository. 45 | * `repository` - (Required) If true, the retention lifecycle rules will be activated for the repository and settings will be updated. 46 | * `retention_enabled` - (Required) If true, the retention lifecycle rules will be activated for the repository and settings will be updated. 47 | * `retention_count_limit` - (Optional) The maximum number of packages to retain. Must be between 0 and 10000. 48 | * `retention_days_limit` - (Optional) The number of days of packages to retain. Must be between `0` and `180`. 49 | * `retention_group_by_name` - (Optional) If true, retention will apply to groups of packages by name rather than all packages. 50 | * `retention_group_by_format` - (Optional) If true, retention will apply to packages by package formats rather than across all package formats. 51 | * `retention_group_by_package_type` - (Optional) If true, retention will apply to packages by package type rather than across all package types for one or more formats. 52 | * `retention_size_limit` - (Optional) The maximum total size (in bytes) of packages to retain. Must be between `0` and `21474836480` (21.47 GB / 21474.83 MB). 53 | 54 | ## Import 55 | 56 | This resource can be imported using the namespace and repository slug: 57 | 58 | ```shell 59 | terraform import cloudsmith_repository_retention_rule.retention_rule my-namespace-slug.my-repository 60 | ``` 61 | -------------------------------------------------------------------------------- /docs/resources/saml_auth.md: -------------------------------------------------------------------------------- 1 | # SAML Authentication Resource 2 | 3 | The SAML Authentication resource allows the configuration of SAML-based authentication for a Cloudsmith organization. This enables organizations to integrate with SAML identity providers for user authentication. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | provider "cloudsmith" { 9 | api_key = "my-api-key" 10 | } 11 | 12 | resource "cloudsmith_saml_auth" "example" { 13 | organization = "my-organization" 14 | saml_auth_enabled = true 15 | saml_auth_enforced = false 16 | 17 | # Use either saml_metadata_url OR saml_metadata_inline 18 | saml_metadata_url = "https://idp.example.com/metadata.xml" 19 | 20 | # Alternative: Use inline metadata 21 | # saml_metadata_inline = < 23 | # 24 | # 25 | # 27 | # 28 | # 29 | # EOF 30 | } 31 | ``` 32 | 33 | ## Argument Reference 34 | 35 | The following arguments are supported: 36 | 37 | * `organization` - (Required) Organization slug for SAML authentication. This value cannot be changed after creation. 38 | * `saml_auth_enabled` - (Required) Enable or disable SAML authentication for the organization. 39 | * `saml_auth_enforced` - (Required) Whether to enforce SAML authentication for the organization. 40 | * `saml_metadata_url` - (Optional) URL to fetch SAML metadata from the identity provider. Exactly one of `saml_metadata_url` or `saml_metadata_inline` must be specified. 41 | * `saml_metadata_inline` - (Optional) Inline SAML metadata XML from the identity provider. Exactly one of `saml_metadata_url` or `saml_metadata_inline` must be specified. 42 | 43 | ## Import 44 | 45 | SAML authentication configuration can be imported using the organization slug: 46 | 47 | ```shell 48 | terraform import cloudsmith_saml_auth.example my-organization 49 | ``` 50 | -------------------------------------------------------------------------------- /docs/resources/saml_group_sync.md: -------------------------------------------------------------------------------- 1 | # SAML Group Sync Resource 2 | 3 | The SAML resource allows the creation and management of SAML Group Sync configurations for a given Cloudsmith organization. SAML Group sync configuration allows for easy mapping of your current AD groups and assign these groups into Cloudsmith teams. 4 | 5 | ## Example Usage 6 | 7 | ```hcl 8 | 9 | provider "cloudsmith" { 10 | api_key = "my-api-key" 11 | } 12 | 13 | resource "cloudsmith_saml" "my_saml" { 14 | organization = "org-name" 15 | idp_key = "role" 16 | idp_value = "example" 17 | role = "Member" 18 | team = "owners" 19 | } 20 | ``` 21 | 22 | ## Argument Reference 23 | 24 | * `organization` - (Required) Organization (namespace) to which this SAML Group Sync configuration belongs 25 | * `idp_key` - (Required) The attribute key from your provider 26 | * `idp_value` - (Required) The attribute value from your provider 27 | * `role` - (Optional) (Default to Member) The role assigned for the team (Member or Manager) 28 | * `team` - (Required) The team associated with the configuration (The team must exist prior to creating SAML Group sync config) 29 | 30 | ## Attribute Reference 31 | 32 | * `slug_perm` - The slug identifier 33 | 34 | ## Import 35 | 36 | This resource can be imported using the organization slug and the SAML slug_perm: 37 | 38 | ```shell 39 | terraform import cloudsmith_saml.my_saml my-organization.my-saml-slug-perm 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/resources/service.md: -------------------------------------------------------------------------------- 1 | # Service Resource 2 | 3 | The service resource allows the creation and management of services for a given Cloudsmith organization. Services allow users to create API keys that can be used for machine-to-machine or other programmatic access without requiring a real user account. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/service-accounts) for full service documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_org" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_team" "my_team" { 19 | organization = data.cloudsmith_organization.my_org.slug 20 | name = "My Team" 21 | } 22 | 23 | resource "cloudsmith_service" "my_service" { 24 | name = "My Service" 25 | organization = data.cloudsmith_organization.my_org.slug 26 | 27 | team { 28 | slug = cloudsmith_team.my_team.slug 29 | } 30 | } 31 | ``` 32 | 33 | ## Argument Reference 34 | 35 | The following arguments are supported: 36 | 37 | * `description` - (Optional) A description of the service's purpose. 38 | * `name` - (Required) A descriptive name for the service. 39 | * `organization` - (Required) Organization to which this service belongs. 40 | * `role` - (Optional) The service's role in the organization. If defined, must be one of `Member` or `Manager`. 41 | * `team` - (Optional) Variable number of blocks containing team assignments for this service. 42 | * `role` - (Optional) The service's role in the team. If defined, must be one of `Member` or `Manager`. 43 | * `slug` - (Required) The team the service should be added to. 44 | * `store_api_key` - (Optional) The service's API key to be returned in state. Defaults to `true`. If set to `false`, the "key" value is replaced with `**redacted**`. **NOTE:** This will only be applied to newly created service accounts, **this won't take effect for existing service accounts**. 45 | 46 | ## Attribute Reference 47 | 48 | In addition to all arguments above, the following attributes are exported: 49 | 50 | * `key` - The service's API key. If `store_api_key` is set to false, the value returned will equal to `**redacted**` 51 | * `slug` - The slug identifies the service in URIs or where a username is required. 52 | 53 | ## Import 54 | 55 | This resource can be imported using the organization slug, and the service slug: 56 | 57 | ```shell 58 | terraform import cloudsmith_service.my_service my-organization.my-service 59 | ``` 60 | 61 | NOTE: It's not possible to retrieve a service's API key via the Cloudsmith API after creation, so when we import a service the key is unavailable. If the API key is needed for use within Terraform (to be passed to other resources) then the resource needs to be tainted and recreated (or otherwise created fresh within Terraform). 62 | -------------------------------------------------------------------------------- /docs/resources/team.md: -------------------------------------------------------------------------------- 1 | # Team Resource 2 | 3 | The teams resource allows creation and management of teams within a Cloudsmith organization. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/teams) for full team documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_org" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_team" "my_team" { 19 | organization = data.cloudsmith_organization.my_org.slug_perm 20 | name = "My Team" 21 | } 22 | ``` 23 | 24 | ## Argument Reference 25 | 26 | The following arguments are supported: 27 | 28 | * `description` - (Optional) A description of the team's purpose. 29 | * `name` - (Required) A descriptive name for the team. 30 | * `organization` - (Required) Organization to which this team belongs. 31 | * `slug` - (Optional) The slug identifies the team in URIs. 32 | * `visibility` - (Optional) Controls if the team is visible or hidden from non-members. 33 | 34 | ## Attribute Reference 35 | 36 | In addition to all arguments above, the following attributes are exported: 37 | 38 | * `slug_perm` - The slug_perm immutably identifies the team. It will never change once a team has been created. 39 | 40 | ## Import 41 | 42 | This resource can be imported using the organization slug, and the team slug: 43 | 44 | ```shell 45 | terraform import cloudsmith_team.my_team my-organization.my-team 46 | ``` 47 | -------------------------------------------------------------------------------- /docs/resources/vulnerability_policy.md: -------------------------------------------------------------------------------- 1 | # Vulnerability Policy Resource 2 | 3 | The vulnerability policy resource allows for creation and management of vulnerability policies within a Cloudsmith organization. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/vulnerability-policy) for the full vulnerability policies documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_organization" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_vulnerability_policy" "my_vulnerability_policy" { 19 | name = "My Policy" 20 | description = "My vulnerability policy" 21 | min_severity = "Medium" 22 | on_violation_quarantine = true 23 | allow_unknown_severity = false 24 | package_query_string = "format:python AND downloads:>50" 25 | organization = "my-organization" 26 | } 27 | ``` 28 | 29 | ## Argument Reference 30 | 31 | The following arguments are supported: 32 | 33 | * `organization` - (Required) Organization to which the policy belongs. 34 | * `name` - (Required) The name of the vulnerability policy. 35 | * `description` - (Optional) The description of the vulnerability policy. 36 | * `min_severity` - (Optional) The minimum severity level where a policy violation will be flagged. 37 | * `on_violation_quarantine` - (Optional) On violation of the vulnerability policy, quarantine violating packages. 38 | * `allow_unknown_severity` - (Optional) Allow an unknown severity level. 39 | * `package_query_string` - (Optional) A search / filter string of packages to include in the policy. 40 | 41 | ## Import 42 | 43 | This resource can be imported using the organization slug and the vulnerability policy slug_perm. 44 | 45 | ```shell 46 | terraform import cloudsmith_vulnerability_policy.my_policy my-organization 47 | ``` 48 | -------------------------------------------------------------------------------- /docs/resources/webhook.md: -------------------------------------------------------------------------------- 1 | # Webhook Resource 2 | 3 | The webhook resource allows the creation and management of webhooks for a given Cloudsmith repository. Webhooks allow integration with external systems by emitting events via HTTP POST request. 4 | 5 | See [help.cloudsmith.io](https://help.cloudsmith.io/docs/webhooks) for full webhook documentation. 6 | 7 | ## Example Usage 8 | 9 | ```hcl 10 | provider "cloudsmith" { 11 | api_key = "my-api-key" 12 | } 13 | 14 | data "cloudsmith_organization" "my_organization" { 15 | slug = "my-organization" 16 | } 17 | 18 | resource "cloudsmith_repository" "my_repository" { 19 | description = "A certifiably-awesome private package repository" 20 | name = "My Repository" 21 | namespace = "${data.cloudsmith_organization.my_organization.slug_perm}" 22 | slug = "my-repository" 23 | } 24 | 25 | resource "cloudsmith_webhook" "my_webhook" { 26 | namespace = cloudsmith_repository.my_repository.namespace 27 | repository = cloudsmith_repository.my_repository.slug_perm 28 | 29 | events = ["package.created", "package.deleted"] 30 | request_body_format = "Handlebars Template" 31 | target_url = "https://example.com" 32 | 33 | template { 34 | event = "package.created" 35 | template = "created: {{data.name}}: {{data.version}}" 36 | } 37 | 38 | template { 39 | event = "package.deleted" 40 | template = "deleted: {{data.name}}: {{data.version}}" 41 | } 42 | } 43 | ``` 44 | 45 | ## Argument Reference 46 | 47 | * `events` - (Required) List of events for which this webhook will be fired. Supported events include: 48 | + `*` - Catch-all for all events. 49 | + `package.created` - Fired when a package is created. 50 | + `package.deleted` - Fired when a package is deleted. 51 | + `package.downloaded` - Fired when a package is downloaded. 52 | + `package.failed` - Fired when a package fails. 53 | + `package.security_scanned` - Fired when a package security scan is completed. 54 | + `package.synced` - Fired when a package is synced. 55 | + `package.syncing` - Fired when a package is syncing. 56 | + `package.tags_updated` - Fired when package tags are updated. 57 | + `package.released` - Fired when a package is released. 58 | + `package.restored` - Fired when a package is restored. 59 | + `package.quarantined` - Fired when a package is quarantined. 60 | * `is_active` - (Optional) If enabled, the webhook will trigger on subscribed events and send payloads to the configured target URL. 61 | * `namespace` - (Required) Namespace (or organization) to which this webhook belongs. 62 | * `package_query` - (Optional) The package-based search query for webhooks to fire. This uses the same syntax as the standard search used for repositories, and also supports boolean logic operators such as OR/AND/NOT and parentheses for grouping. If a package does not match, the webhook will not fire. 63 | * `repository` - (Required) Repository to which this webhook belongs. 64 | * `request_body_format` - (Optional) The format of the payloads for webhook requests. 65 | * `request_body_template_format` - (Optional) The format of the payloads for webhook requests. 66 | * `request_content_type` - (Optional) The value that will be sent for the 'Content Type' header. 67 | * `secret_header` - (Optional) The header to send the predefined secret in. This must be unique from existing headers or it won't be sent. You can use this as a form of authentication on the endpoint side. 68 | * `secret_value` - (Optional) The value for the predefined secret (note: this is treated as a passphrase and is encrypted when we store it). You can use this as a form of authentication on the endpoint side. 69 | * `signature_key` - (Optional) The value for the signature key - This is used to generate an HMAC-based hex digest of the request body, which we send as the X-Cloudsmith-Signature header so that you can ensure that the request wasn't modified by a malicious party (note: this is treated as a passphrase and is encrypted when we store it). 70 | * `target_url` - (Required) The destination URL that webhook payloads will be POST'ed to. 71 | * `template` - (Optional) Variable number of blocks containing templates used to render webhook content before sending. 72 | * `event` - (Required) The event for which this template will be applied. 73 | * `template` - (Required) The contents of the template to be rendered. 74 | * `is_active` - (Optional) If enabled, SSL certificates is verified when webhooks are sent. It's recommended to leave this enabled as not verifying the integrity of SSL certificates leaves you susceptible to Man-in-the-Middle (MITM) attacks. 75 | 76 | ## Attribute Reference 77 | 78 | * `created_at` - ISO 8601 timestamp at which the webhook was created. 79 | * `created_by` - The user/account that created the webhook. 80 | * `disable_reason` - Why this webhook has been disabled. 81 | * `slug_perm` - The slug_perm immutably identifies the webhook. It will never change once a webhook has been created. 82 | * `updated_at` - ISO 8601 timestamp at which the webhook was updated. 83 | * `updated_by` - The user/account that updated the webhook. 84 | 85 | ## Import 86 | 87 | This resource can be imported using the organization slug, the repository slug, and the webhook slug: 88 | 89 | ```shell 90 | terraform import cloudsmith_webhook.my_webhook my-organization.my-repository.w3bh0okS1uG 91 | ``` 92 | -------------------------------------------------------------------------------- /examples/flat-example/README.md: -------------------------------------------------------------------------------- 1 | # Terraform flat structure example 2 | 3 | This terraform example project will setup 4 repositories, each with different settings/functionality that can be configured within Cloudsmith. 4 | 5 | The example will also configures basic org-level settings like license policy, SAML group sync, Service accounts etc. 6 | 7 | ## Usage 8 | 9 | In order to get started, supply your api key and org name in `global-variables.tf` file and run `terraform apply` against it. 10 | -------------------------------------------------------------------------------- /examples/flat-example/global-variables.tf: -------------------------------------------------------------------------------- 1 | 2 | data "cloudsmith_organization" "org-demo" { 3 | slug = "YOUR-ORG-NAME" 4 | } 5 | 6 | variable "api_key" { 7 | type = string 8 | default = "YOUR-API-KEY" 9 | } 10 | 11 | variable "default_storage_region" { 12 | type = string 13 | default = "us-ohio" 14 | } 15 | 16 | variable "main_entitlement_token" { 17 | type = string 18 | default = "Main Entitlement" 19 | } 20 | 21 | variable "main_entitlement_token_limit_num_downloads" { 22 | type = string 23 | default = 1000 24 | } 25 | 26 | variable "geopip_allow_countries" { 27 | type = list(string) 28 | default = ["US", "GB", "DE"] 29 | } 30 | -------------------------------------------------------------------------------- /examples/flat-example/org-deny-policy.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_package_deny_policy" "left_pad_policy" { 2 | name = "Deny left-pad" 3 | description = "Deny left-pad versions greater than 1.1.2" 4 | package_query = "format:npm AND name:left-pad AND version:>1.1.2" 5 | namespace = data.cloudsmith_organization.org-demo.slug 6 | } 7 | -------------------------------------------------------------------------------- /examples/flat-example/org-license-policy.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_license_policy" "apache-python-policy" { 2 | name = "Example License Policy" 3 | description = "Apache 2 License Policy for Python packages" 4 | spdx_identifiers = ["Apache-2.0"] 5 | on_violation_quarantine = true 6 | package_query_string = "format:python AND downloads:>50" 7 | organization = data.cloudsmith_organization.org-demo.slug 8 | } 9 | 10 | resource "cloudsmith_license_policy" "mit-npm-policy" { 11 | name = "Example License Policy" 12 | description = "MIT License Policy for Python packages" 13 | spdx_identifiers = ["MIT"] 14 | on_violation_quarantine = true 15 | package_query_string = "format:npm" 16 | organization = data.cloudsmith_organization.org-demo.slug 17 | } 18 | -------------------------------------------------------------------------------- /examples/flat-example/org-oidc-config.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_oidc" "devops-oidc" { 2 | namespace = data.cloudsmith_organization.org-demo.slug 3 | name = "OIDC-DEMO" 4 | enabled = true 5 | provider_url = "https://token.actions.githubusercontent.com" 6 | service_accounts = [cloudsmith_service.production-service.slug] 7 | claims = { 8 | "repository" = "Owner/GitHubRepoName" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/flat-example/org-saml-group.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_saml" "owners_mapping" { 2 | organization = data.cloudsmith_organization.org-demo.slug 3 | idp_key = "administrators" 4 | idp_value = "administrators" 5 | role = "Manager" 6 | team = "owners" 7 | } 8 | 9 | resource "cloudsmith_saml" "developers_mapping" { 10 | organization = data.cloudsmith_organization.org-demo.slug 11 | idp_key = "interns" 12 | idp_value = "interns" 13 | role = "Member" 14 | team = resource.cloudsmith_team.interns.slug 15 | } 16 | -------------------------------------------------------------------------------- /examples/flat-example/org-services.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_service" "devops-service" { 2 | name = "devops-service" 3 | organization = data.cloudsmith_organization.org-demo.slug 4 | } 5 | 6 | resource "cloudsmith_service" "production-service" { 7 | name = "production-service" 8 | organization = data.cloudsmith_organization.org-demo.slug 9 | } 10 | 11 | resource "cloudsmith_service" "qa-service" { 12 | name = "qa-service" 13 | organization = data.cloudsmith_organization.org-demo.slug 14 | } 15 | 16 | resource "cloudsmith_service" "developer-service" { 17 | name = "developer-service" 18 | organization = data.cloudsmith_organization.org-demo.slug 19 | } 20 | -------------------------------------------------------------------------------- /examples/flat-example/org-teams.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_team" "developers" { 2 | organization = data.cloudsmith_organization.org-demo.slug_perm 3 | name = "Developers" 4 | } 5 | 6 | resource "cloudsmith_team" "interns" { 7 | organization = data.cloudsmith_organization.org-demo.slug_perm 8 | name = "Interns" 9 | } 10 | -------------------------------------------------------------------------------- /examples/flat-example/org-vulnerability-policy.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_vulnerability_policy" "python_policy" { 2 | name = "Python policy" 3 | description = "Python block policy" 4 | min_severity = "High" 5 | on_violation_quarantine = true 6 | allow_unknown_severity = false 7 | package_query_string = "format:python" 8 | organization = data.cloudsmith_organization.org-demo.slug 9 | } 10 | 11 | resource "cloudsmith_vulnerability_policy" "container-policy-test" { 12 | name = "Container policy test" 13 | description = "Container block policy test" 14 | min_severity = "High" 15 | on_violation_quarantine = true 16 | allow_unknown_severity = true 17 | package_query_string = "format:docker and repository:shared-proxy-test" 18 | organization = data.cloudsmith_organization.org-demo.slug 19 | } 20 | 21 | 22 | resource "cloudsmith_vulnerability_policy" "maven-policy" { 23 | name = "Maven policy" 24 | description = "Maven block policy" 25 | min_severity = "High" 26 | on_violation_quarantine = true 27 | allow_unknown_severity = true 28 | package_query_string = "format:maven" 29 | organization = data.cloudsmith_organization.org-demo.slug 30 | } 31 | 32 | 33 | resource "cloudsmith_vulnerability_policy" "nuget-policy" { 34 | name = "Nuget policy" 35 | description = "Nuget block policy" 36 | min_severity = "High" 37 | on_violation_quarantine = true 38 | allow_unknown_severity = true 39 | package_query_string = "format:nuget" 40 | organization = data.cloudsmith_organization.org-demo.slug 41 | } 42 | 43 | 44 | resource "cloudsmith_vulnerability_policy" "npm-policy" { 45 | name = "npm policy" 46 | description = "npm block policy" 47 | min_severity = "High" 48 | on_violation_quarantine = true 49 | allow_unknown_severity = true 50 | package_query_string = "format:npm" 51 | organization = data.cloudsmith_organization.org-demo.slug 52 | } 53 | -------------------------------------------------------------------------------- /examples/flat-example/provider.tf: -------------------------------------------------------------------------------- 1 | # tflint-ignore-file: terraform_required_version, terraform_required_providers 2 | 3 | terraform { 4 | required_providers { 5 | cloudsmith = { 6 | source = "cloudsmith-io/cloudsmith" 7 | } 8 | } 9 | } 10 | 11 | provider "cloudsmith" { 12 | api_key = var.api_key 13 | } 14 | -------------------------------------------------------------------------------- /examples/flat-example/repo-devops.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_repository" "devops" { 2 | description = "DevOps repository" 3 | name = "devops" 4 | namespace = data.cloudsmith_organization.org-demo.slug_perm 5 | slug = "devops" 6 | repository_type = "Private" 7 | storage_region = var.default_storage_region 8 | proxy_npmjs = false 9 | proxy_pypi = false 10 | use_default_cargo_upstream = false 11 | } 12 | 13 | 14 | resource "cloudsmith_repository_privileges" "devops-privs" { 15 | organization = data.cloudsmith_organization.org-demo.slug 16 | repository = cloudsmith_repository.devops.slug 17 | 18 | service { 19 | privilege = "Write" 20 | slug = cloudsmith_service.qa-service.slug 21 | } 22 | 23 | service { 24 | privilege = "Read" 25 | slug = cloudsmith_service.developer-service.slug 26 | } 27 | } 28 | 29 | resource "cloudsmith_repository_geo_ip_rules" "devops-geoip" { 30 | repository = cloudsmith_repository.devops.slug 31 | namespace = data.cloudsmith_organization.org-demo.slug 32 | country_code_allow = var.geopip_allow_countries 33 | } 34 | -------------------------------------------------------------------------------- /examples/flat-example/repo-oidc-demo.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_repository" "oidc_demo" { 2 | description = "OIDC repository" 3 | name = "oidc-demo" 4 | namespace = data.cloudsmith_organization.org-demo.slug_perm 5 | slug = "oidc" 6 | repository_type = "Private" 7 | storage_region = var.default_storage_region 8 | proxy_npmjs = false 9 | proxy_pypi = false 10 | use_default_cargo_upstream = false 11 | } 12 | 13 | 14 | resource "cloudsmith_repository_privileges" "oidc_demo-privs" { 15 | organization = data.cloudsmith_organization.org-demo.slug 16 | repository = cloudsmith_repository.oidc_demo.slug 17 | 18 | service { 19 | privilege = "Write" 20 | slug = cloudsmith_service.developer-service.slug 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/flat-example/repo-staging.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_repository" "staging" { 2 | description = "Staging repository" 3 | name = "staging" 4 | namespace = data.cloudsmith_organization.org-demo.slug_perm 5 | slug = "staging" 6 | repository_type = "Private" 7 | storage_region = var.default_storage_region 8 | proxy_npmjs = false 9 | proxy_pypi = false 10 | use_default_cargo_upstream = false 11 | } 12 | 13 | resource "cloudsmith_entitlement" "staging-main_entitlement" { 14 | namespace = data.cloudsmith_organization.org-demo.slug 15 | name = var.main_entitlement_token 16 | repository = cloudsmith_repository.staging.slug 17 | limit_num_downloads = var.main_entitlement_token_limit_num_downloads 18 | } 19 | 20 | resource "cloudsmith_repository_privileges" "staging-privs" { 21 | organization = data.cloudsmith_organization.org-demo.slug 22 | repository = cloudsmith_repository.staging.slug 23 | 24 | service { 25 | privilege = "Write" 26 | slug = cloudsmith_service.devops-service.slug 27 | } 28 | 29 | service { 30 | privilege = "Write" 31 | slug = cloudsmith_service.qa-service.slug 32 | } 33 | 34 | team { 35 | privilege = "Read" 36 | slug = cloudsmith_team.developers.slug 37 | } 38 | 39 | # user { 40 | # privilege = "Read" 41 | # username = "example-user" 42 | # } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /examples/flat-example/repo-upstreams.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_repository" "upstream" { 2 | description = "Global upstream proxy repository for docker, nuget, python, npm and maven" 3 | name = "upstreams-demo" 4 | namespace = data.cloudsmith_organization.org-demo.slug_perm 5 | slug = "upstreams-demo" 6 | repository_type = "Private" 7 | storage_region = var.default_storage_region 8 | default_privilege = "Read" 9 | } 10 | 11 | resource "cloudsmith_repository_upstream" "pypi" { 12 | name = "pypi" 13 | namespace = data.cloudsmith_organization.org-demo.slug_perm 14 | repository = cloudsmith_repository.upstream.slug_perm 15 | upstream_type = "python" 16 | upstream_url = "https://pypi.org" 17 | mode = "Cache and Proxy" 18 | } 19 | 20 | resource "cloudsmith_repository_upstream" "npm" { 21 | name = "npm" 22 | namespace = data.cloudsmith_organization.org-demo.slug_perm 23 | repository = cloudsmith_repository.upstream.slug_perm 24 | upstream_type = "npm" 25 | upstream_url = "https://registry.npmjs.org" 26 | mode = "Cache and Proxy" 27 | } 28 | 29 | resource "cloudsmith_repository_upstream" "nuget" { 30 | name = "nuget.org" 31 | namespace = data.cloudsmith_organization.org-demo.slug_perm 32 | repository = cloudsmith_repository.upstream.slug_perm 33 | upstream_type = "nuget" 34 | upstream_url = "https://api.nuget.org/v3/index.json" 35 | mode = "Cache and Proxy" 36 | } 37 | 38 | resource "cloudsmith_repository_upstream" "dockerhub" { 39 | name = "dockerhub" 40 | namespace = data.cloudsmith_organization.org-demo.slug_perm 41 | repository = cloudsmith_repository.upstream.slug_perm 42 | upstream_type = "docker" 43 | upstream_url = "https://index.docker.io" 44 | mode = "Cache and Proxy" 45 | } 46 | 47 | resource "cloudsmith_repository_upstream" "mcr-microsoft" { 48 | name = "mcr.microsoft.com" 49 | namespace = data.cloudsmith_organization.org-demo.slug_perm 50 | repository = cloudsmith_repository.upstream.slug_perm 51 | upstream_type = "docker" 52 | upstream_url = "https://mcr.microsoft.com" 53 | mode = "Cache and Proxy" 54 | } 55 | 56 | resource "cloudsmith_repository_upstream" "maven" { 57 | name = "Maven" 58 | namespace = data.cloudsmith_organization.org-demo.slug_perm 59 | repository = cloudsmith_repository.upstream.slug_perm 60 | upstream_type = "maven" 61 | upstream_url = "https://repo1.maven.org/maven2" 62 | mode = "Cache and Proxy" 63 | } 64 | -------------------------------------------------------------------------------- /examples/iterative-example/README.md: -------------------------------------------------------------------------------- 1 | # Terraform iterative structure example 2 | 3 | This terraform example project will setup 3 repositories using a loop-based approach for instances where each repository 4 | shares many attributes with the others, but changes slightly based on their individual needs. 5 | 6 | * Creates a Development, Staging and Production repository 7 | * Disables User entitlements. The default entitlement token can then be disabled so that authentication can only be done via API keys! 8 | * Creates an individual CI service account for each repository which gets write access 9 | * Configures Github OIDC for authenticating as the service accounts (see our documentation [here](https://help.cloudsmith.io/docs/setup-cloudsmith-to-authenticate-with-oidc-in-github-actions) for how to configure that on the Github side) 10 | * Creates a Developers team which gets write permission on the Development repository 11 | * Configures DockerHub and Chainguard upstreams on each repository 12 | * Configures a Vulnerablity policy that blocks high severity vulnerabilities. 13 | * Configures a license policy that blocks AGPL licensed packages. 14 | 15 | ## Usage 16 | 17 | To get started, supply your API key and org name in `global-variables.tf` file. 18 | Run `terraform init` and then run `terraform apply` to execute the plan. 19 | 20 | Configuration can be done in the `terraform.tfvars` file. 21 | -------------------------------------------------------------------------------- /examples/iterative-example/global-variables.tf: -------------------------------------------------------------------------------- 1 | data "cloudsmith_organization" "cloudsmith-org" { 2 | slug = "YOUR-ORG-NAME" 3 | } 4 | 5 | variable "api_key" { 6 | type = string 7 | default = "YOUR-API-KEY" 8 | } 9 | 10 | variable "default_storage_region" { 11 | type = string 12 | default = "us-ohio" 13 | } 14 | 15 | variable "chainguard_api_user" { 16 | type = string 17 | default = "YOUR-CHAINGUARD-API-USER" 18 | } 19 | 20 | variable "chainguard_api_secret" { 21 | type = string 22 | default = "YOUR-CHAINGUARD-API-SECRET" 23 | } 24 | 25 | variable "repositories" { 26 | type = map(object({ 27 | add_developers = optional(bool) 28 | oidc_claims = optional(map(string)) 29 | })) 30 | description = "A map of repositories with their configurations." 31 | default = { 32 | "staging" = { 33 | add_developers = false 34 | }, 35 | "production" = { 36 | add_developers = false 37 | } 38 | } 39 | } 40 | 41 | variable "oidc_claims" { 42 | type = map(string) 43 | default = { 44 | "repository" = "Owner/GitHubRepoName" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /examples/iterative-example/license-policy.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_license_policy" "agpl-policy" { 2 | name = "Block AGPL" 3 | description = "Block AGPL licensed packages" 4 | spdx_identifiers = ["AGPL-1.0", "AGPL-1.0-only", "AGPL-1.0-or-later", "AGPL-3.0", "AGPL-3.0-only", "AGPL-3.0-or-later"] 5 | on_violation_quarantine = true 6 | organization = data.cloudsmith_organization.cloudsmith-org.slug 7 | } 8 | -------------------------------------------------------------------------------- /examples/iterative-example/oidc-config.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_oidc" "org-oidc" { 2 | for_each = var.repositories 3 | namespace = data.cloudsmith_organization.cloudsmith-org.slug 4 | name = "Github OIDC - ${each.key}" 5 | enabled = true 6 | provider_url = "https://token.actions.githubusercontent.com" 7 | service_accounts = [cloudsmith_service.ci-service[each.key].slug] 8 | claims = (each.value.oidc_claims != null) ? each.value.oidc_claims : var.oidc_claims 9 | } 10 | -------------------------------------------------------------------------------- /examples/iterative-example/provider.tf: -------------------------------------------------------------------------------- 1 | # tflint-ignore-file: terraform_required_version, terraform_required_providers 2 | 3 | terraform { 4 | required_providers { 5 | cloudsmith = { 6 | source = "cloudsmith-io/cloudsmith" 7 | } 8 | } 9 | } 10 | 11 | provider "cloudsmith" { 12 | api_key = var.api_key 13 | } 14 | -------------------------------------------------------------------------------- /examples/iterative-example/repositories.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_repository" "repositories" { 2 | for_each = var.repositories 3 | description = "${title(each.key)} repository" 4 | name = each.key 5 | namespace = data.cloudsmith_organization.cloudsmith-org.slug_perm 6 | slug = each.key 7 | repository_type = "Private" 8 | storage_region = var.default_storage_region 9 | user_entitlements_enabled = false 10 | 11 | } 12 | 13 | resource "cloudsmith_repository_privileges" "repo-privs" { 14 | for_each = var.repositories 15 | organization = data.cloudsmith_organization.cloudsmith-org.slug 16 | repository = cloudsmith_repository.repositories[each.key].slug 17 | 18 | # if you're using a service account to provision, be sure to include it as an Admin here! 19 | service { 20 | privilege = "Write" 21 | slug = cloudsmith_service.ci-service[each.key].slug 22 | } 23 | 24 | dynamic "team" { 25 | for_each = each.value.add_developers == true ? [1] : [] 26 | content { 27 | privilege = "Write" 28 | slug = cloudsmith_team.developers.slug 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/iterative-example/services.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_service" "ci-service" { 2 | for_each = var.repositories 3 | name = "${lower(each.key)}-ci-service" 4 | organization = data.cloudsmith_organization.cloudsmith-org.slug 5 | } 6 | -------------------------------------------------------------------------------- /examples/iterative-example/teams.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_team" "developers" { 2 | organization = data.cloudsmith_organization.cloudsmith-org.slug_perm 3 | name = "Developers" 4 | } 5 | -------------------------------------------------------------------------------- /examples/iterative-example/terraform.tfvars: -------------------------------------------------------------------------------- 1 | repositories = { 2 | "development" : { 3 | "add_developers" : true 4 | }, 5 | "staging" : { 6 | "add_developers" : false 7 | }, 8 | "production" : { 9 | "add_developers" : false 10 | "oidc_claims" : { 11 | "repository" = "Owner/ProductionGithubRepoName" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/iterative-example/upstreams.tf: -------------------------------------------------------------------------------- 1 | # Chainguard public repositories, no authentication, affix /chainguard/ to URL when pulling. 2 | resource "cloudsmith_repository_upstream" "cgr-public" { 3 | for_each = var.repositories 4 | name = "cgr-public" 5 | namespace = data.cloudsmith_organization.cloudsmith-org.slug_perm 6 | repository = cloudsmith_repository.repositories[each.key].slug_perm 7 | is_active = true 8 | upstream_type = "docker" 9 | upstream_url = "https://cgr.dev" 10 | mode = "Cache and Proxy" 11 | priority = 2 12 | } 13 | 14 | # Chainguard private repositories, uses authentication, affix // to URL when pulling. 15 | resource "cloudsmith_repository_upstream" "cgr-private" { 16 | for_each = var.repositories 17 | name = "cgr-private" 18 | namespace = data.cloudsmith_organization.cloudsmith-org.slug_perm 19 | repository = cloudsmith_repository.repositories[each.key].slug_perm 20 | is_active = true 21 | upstream_type = "docker" 22 | upstream_url = "https://cgr.dev" 23 | mode = "Cache and Proxy" 24 | auth_mode = "Username and Password" 25 | auth_username = var.chainguard_api_user 26 | auth_secret = var.chainguard_api_secret 27 | priority = 2 28 | } 29 | 30 | resource "cloudsmith_repository_upstream" "dockerhub" { 31 | for_each = var.repositories 32 | name = "dockerhub" 33 | namespace = data.cloudsmith_organization.cloudsmith-org.slug_perm 34 | repository = cloudsmith_repository.repositories[each.key].slug_perm 35 | upstream_type = "docker" 36 | upstream_url = "https://index.docker.io" 37 | mode = "Cache and Proxy" 38 | priority = 1 39 | } 40 | -------------------------------------------------------------------------------- /examples/iterative-example/vulnerability-policy.tf: -------------------------------------------------------------------------------- 1 | resource "cloudsmith_vulnerability_policy" "container-policy-test" { 2 | name = "Container policy" 3 | description = "Block high severity issues in docker images" 4 | min_severity = "High" 5 | on_violation_quarantine = true 6 | allow_unknown_severity = true 7 | package_query_string = "format:docker" 8 | organization = data.cloudsmith_organization.cloudsmith-org.slug 9 | } 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudsmith-io/terraform-provider-cloudsmith 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/cloudsmith-io/cloudsmith-api-go v0.0.46 7 | github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 8 | github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1 9 | github.com/samber/lo v1.36.0 10 | ) 11 | 12 | require ( 13 | github.com/agext/levenshtein v1.2.2 // indirect 14 | github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect 15 | github.com/davecgh/go-spew v1.1.1 // indirect 16 | github.com/fatih/color v1.13.0 // indirect 17 | github.com/golang/protobuf v1.5.2 // indirect 18 | github.com/google/go-cmp v0.5.9 // indirect 19 | github.com/hashicorp/errwrap v1.0.0 // indirect 20 | github.com/hashicorp/go-checkpoint v0.5.0 // indirect 21 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 22 | github.com/hashicorp/go-hclog v1.2.1 // indirect 23 | github.com/hashicorp/go-multierror v1.1.1 // indirect 24 | github.com/hashicorp/go-plugin v1.4.6 // indirect 25 | github.com/hashicorp/go-uuid v1.0.3 // indirect 26 | github.com/hashicorp/go-version v1.6.0 // indirect 27 | github.com/hashicorp/hc-install v0.4.0 // indirect 28 | github.com/hashicorp/hcl/v2 v2.15.0 // indirect 29 | github.com/hashicorp/logutils v1.0.0 // indirect 30 | github.com/hashicorp/terraform-exec v0.17.3 // indirect 31 | github.com/hashicorp/terraform-json v0.14.0 // indirect 32 | github.com/hashicorp/terraform-plugin-go v0.14.1 // indirect 33 | github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect 34 | github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect 35 | github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect 36 | github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect 37 | github.com/mattn/go-colorable v0.1.12 // indirect 38 | github.com/mattn/go-isatty v0.0.14 // indirect 39 | github.com/mitchellh/copystructure v1.2.0 // indirect 40 | github.com/mitchellh/go-testing-interface v1.14.1 // indirect 41 | github.com/mitchellh/go-wordwrap v1.0.0 // indirect 42 | github.com/mitchellh/mapstructure v1.5.0 // indirect 43 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 44 | github.com/oklog/run v1.0.0 // indirect 45 | github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect 46 | github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect 47 | github.com/vmihailenco/tagparser v0.1.1 // indirect 48 | github.com/zclconf/go-cty v1.12.1 // indirect 49 | golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect 50 | golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect 51 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect 52 | golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect 53 | golang.org/x/text v0.3.7 // indirect 54 | google.golang.org/appengine v1.6.6 // indirect 55 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect 56 | google.golang.org/grpc v1.50.1 // indirect 57 | google.golang.org/protobuf v1.28.1 // indirect 58 | ) 59 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/cloudsmith-io/terraform-provider-cloudsmith/cloudsmith" 5 | "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" 6 | ) 7 | 8 | func main() { 9 | plugin.Serve(&plugin.ServeOpts{ 10 | ProviderFunc: cloudsmith.Provider, 11 | }) 12 | } 13 | --------------------------------------------------------------------------------