├── main.go ├── drone ├── utils.go ├── utils_test.go ├── provider_test.go ├── resource_user_test.go ├── provider.go ├── resource_user.go ├── resource_registry_test.go ├── resource_repo_test.go ├── resource_secret_test.go ├── resource_registry.go ├── resource_secret.go └── resource_repo.go ├── LICENSE ├── .drone.yml └── README.md /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/artisanofcode/terraform-provider-drone/drone" 5 | "github.com/hashicorp/terraform/plugin" 6 | "github.com/hashicorp/terraform/terraform" 7 | ) 8 | 9 | func main() { 10 | plugin.Serve(&plugin.ServeOpts{ 11 | ProviderFunc: func() terraform.ResourceProvider { 12 | return drone.Provider() 13 | }, 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /drone/utils.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | func parseRepo(str string) (user, repo string, err error) { 9 | parts := strings.Split(str, "/") 10 | 11 | if len(parts) != 2 { 12 | err = fmt.Errorf("Error: Invalid repository (e.g. octocat/hello-world).") 13 | return 14 | } 15 | 16 | user = parts[0] 17 | repo = parts[1] 18 | return 19 | } 20 | 21 | func parseId(str, example string) (user, repo, id string, err error) { 22 | parts := strings.Split(str, "/") 23 | 24 | if len(parts) < 3 { 25 | err = fmt.Errorf( 26 | "Error: Invalid identity (e.g. octocat/hello-world/%s).", 27 | example, 28 | ) 29 | return 30 | } 31 | 32 | user = parts[0] 33 | repo = parts[1] 34 | 35 | id = strings.Join(parts[2:], "/") 36 | 37 | return 38 | } 39 | -------------------------------------------------------------------------------- /drone/utils_test.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestParseRepo(t *testing.T) { 8 | for _, test := range []struct { 9 | name, str, user, repo string 10 | is_error bool 11 | }{ 12 | {"Test valid repository", "octocat/hello-world", "octocat", "hello-world", false}, 13 | {"Test another valid repository", "drone/drone", "drone", "drone", false}, 14 | {"Test invalid repository without slash", "foobar", "", "", true}, 15 | {"Test invalid repository with too many slashes", "foo/bar/baz", "", "", true}, 16 | } { 17 | t.Run(test.name, func(t *testing.T) { 18 | user, repo, err := parseRepo(test.str) 19 | 20 | if (test.is_error == true) && (err == nil) { 21 | t.Errorf("expected error") 22 | } 23 | 24 | if (test.is_error == false) && (err != nil) { 25 | t.Errorf("unexpected error") 26 | } 27 | 28 | if test.user != user { 29 | t.Errorf("unexpected user") 30 | } 31 | 32 | if test.repo != repo { 33 | t.Errorf("unexpected repo") 34 | } 35 | }) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Daniel Knell, http://danielknell.co.uk 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the “Software”), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /drone/provider_test.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/hashicorp/terraform/helper/schema" 8 | "github.com/hashicorp/terraform/terraform" 9 | ) 10 | 11 | var ( 12 | testDroneUser string = os.Getenv("DRONE_USER") 13 | testProviders map[string]terraform.ResourceProvider 14 | testProvider *schema.Provider 15 | ) 16 | 17 | func init() { 18 | testProvider = Provider() 19 | testProviders = map[string]terraform.ResourceProvider{ 20 | "drone": testProvider, 21 | } 22 | } 23 | 24 | func TestProvider(t *testing.T) { 25 | if err := Provider().InternalValidate(); err != nil { 26 | t.Fatalf("err: %s", err) 27 | } 28 | } 29 | 30 | func TestProvider_impl(t *testing.T) { 31 | var _ terraform.ResourceProvider = Provider() 32 | } 33 | 34 | func testAccPreCheck(t *testing.T) { 35 | if v := os.Getenv("DRONE_SERVER"); v == "" { 36 | t.Fatal("DRONE_SERVER must be set for acceptance tests") 37 | } 38 | if v := os.Getenv("DRONE_TOKEN"); v == "" { 39 | t.Fatal("DRONE_TOKEN must be set for acceptance tests") 40 | } 41 | if v := os.Getenv("DRONE_USER"); v == "" { 42 | t.Fatal("DRONE_USER must be set for acceptance tests") 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /drone/resource_user_test.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/resource" 7 | "github.com/hashicorp/terraform/terraform" 8 | "testing" 9 | ) 10 | 11 | var testUserConfig = ` 12 | resource "drone_user" "octocat" { 13 | login = "octocat" 14 | } 15 | ` 16 | 17 | func TestUser(t *testing.T) { 18 | resource.Test(t, resource.TestCase{ 19 | PreCheck: func() { testAccPreCheck(t) }, 20 | Providers: testProviders, 21 | CheckDestroy: testUserDestroy, 22 | Steps: []resource.TestStep{ 23 | { 24 | Config: testUserConfig, 25 | Check: resource.ComposeTestCheckFunc( 26 | resource.TestCheckResourceAttr( 27 | "drone_user.octocat", 28 | "login", 29 | "octocat", 30 | ), 31 | ), 32 | }, 33 | }, 34 | }) 35 | } 36 | 37 | func testUserDestroy(state *terraform.State) error { 38 | client := testProvider.Meta().(drone.Client) 39 | 40 | for _, resource := range state.RootModule().Resources { 41 | if resource.Type != "drone_user" { 42 | continue 43 | } 44 | 45 | err := client.UserDel(resource.Primary.Attributes["login"]) 46 | 47 | if err == nil { 48 | return fmt.Errorf("User still exists: %s", resource.Primary.Attributes["login"]) 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | -------------------------------------------------------------------------------- /drone/provider.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/schema" 7 | "golang.org/x/oauth2" 8 | ) 9 | 10 | func Provider() *schema.Provider { 11 | return &schema.Provider{ 12 | Schema: map[string]*schema.Schema{ 13 | "server": { 14 | Type: schema.TypeString, 15 | Required: true, 16 | Description: "URL for the drone server", 17 | DefaultFunc: schema.EnvDefaultFunc("DRONE_SERVER", nil), 18 | }, 19 | "token": { 20 | Type: schema.TypeString, 21 | Required: true, 22 | Description: "API Token for the drone server", 23 | DefaultFunc: schema.EnvDefaultFunc("DRONE_TOKEN", nil), 24 | }, 25 | }, 26 | ResourcesMap: map[string]*schema.Resource{ 27 | "drone_registry": resourceRegistry(), 28 | "drone_repo": resourceRepo(), 29 | "drone_secret": resourceSecret(), 30 | "drone_user": resourceUser(), 31 | }, 32 | ConfigureFunc: providerConfigureFunc, 33 | } 34 | } 35 | 36 | func providerConfigureFunc(data *schema.ResourceData) (interface{}, error) { 37 | config := new(oauth2.Config) 38 | 39 | auther := config.Client( 40 | oauth2.NoContext, 41 | &oauth2.Token{AccessToken: data.Get("token").(string)}, 42 | ) 43 | 44 | client := drone.NewClient(data.Get("server").(string), auther) 45 | 46 | if _, err := client.Self(); err != nil { 47 | return nil, fmt.Errorf("drone client failed: %s", err) 48 | } 49 | 50 | return client, nil 51 | } 52 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | workspace: 2 | base: /go 3 | path: src/github.com/artisanofcode/terraform-provider-drone 4 | 5 | pipeline: 6 | test: 7 | image: golang:1.10 8 | environment: 9 | - TF_ACC=1 10 | - DRONE_USER=terraform-provider-drone 11 | secrets: 12 | - drone_server 13 | - drone_token 14 | commands: 15 | - go get 16 | - go test -v ./... 17 | 18 | build: 19 | image: golang:1.10 20 | commands: 21 | - go get 22 | - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dist/linux_amd64/terraform-provider-drone_${DRONE_TAG} 23 | - CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o dist/linux_arm64/terraform-provider-drone_${DRONE_TAG} 24 | - CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -o dist/linux_arm/terraform-provider-drone_${DRONE_TAG} 25 | - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o dist/windows_amd64/terraform-provider-drone_${DRONE_TAG} 26 | - CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o dist/darwin_amd64/terraform-provider-drone_${DRONE_TAG} 27 | 28 | - tar -cvzf dist/terraform-provider-drone_linux_amd64.tar.gz -C dist/linux_amd64 terraform-provider-drone_${DRONE_TAG} 29 | - tar -cvzf dist/terraform-provider-drone_linux_arm64.tar.gz -C dist/linux_arm64 terraform-provider-drone_${DRONE_TAG} 30 | - tar -cvzf dist/terraform-provider-drone_linux_arm.tar.gz -C dist/linux_arm terraform-provider-drone_${DRONE_TAG} 31 | - tar -cvzf dist/terraform-provider-drone_windows_amd64.tar.gz -C dist/windows_amd64 terraform-provider-drone_${DRONE_TAG} 32 | - tar -cvzf dist/terraform-provider-drone_darwin_amd64.tar.gz -C dist/darwin_amd64 terraform-provider-drone_${DRONE_TAG} 33 | when: 34 | event: tag 35 | 36 | release: 37 | image: plugins/github-release 38 | files: 39 | - dist/*.tar.gz 40 | checksum: 41 | - md5 42 | - sha1 43 | - sha256 44 | - sha512 45 | secrets: 46 | - source: github_token 47 | target: github_release_api_key 48 | when: 49 | event: tag 50 | -------------------------------------------------------------------------------- /drone/resource_user.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "github.com/drone/drone-go/drone" 5 | "github.com/hashicorp/terraform/helper/schema" 6 | ) 7 | 8 | var defaultUserEvents = []string{ 9 | drone.EventPush, 10 | drone.EventTag, 11 | drone.EventDeploy, 12 | } 13 | 14 | func resourceUser() *schema.Resource { 15 | return &schema.Resource{ 16 | Schema: map[string]*schema.Schema{ 17 | "login": { 18 | Type: schema.TypeString, 19 | Required: true, 20 | ForceNew: true, 21 | }, 22 | }, 23 | 24 | Importer: &schema.ResourceImporter{ 25 | State: schema.ImportStatePassthrough, 26 | }, 27 | 28 | Create: resourceUserCreate, 29 | Read: resourceUserRead, 30 | Delete: resourceUserDelete, 31 | Exists: resourceUserExists, 32 | } 33 | } 34 | 35 | func resourceUserCreate(data *schema.ResourceData, meta interface{}) error { 36 | client := meta.(drone.Client) 37 | 38 | user, err := client.UserPost(createUser(data)) 39 | 40 | return readUser(data, user, err) 41 | } 42 | 43 | func resourceUserRead(data *schema.ResourceData, meta interface{}) error { 44 | client := meta.(drone.Client) 45 | 46 | user, err := client.User(data.Id()) 47 | 48 | return readUser(data, user, err) 49 | } 50 | 51 | func resourceUserDelete(data *schema.ResourceData, meta interface{}) error { 52 | client := meta.(drone.Client) 53 | 54 | return client.UserDel(data.Id()) 55 | } 56 | 57 | func resourceUserExists(data *schema.ResourceData, meta interface{}) (bool, error) { 58 | client := meta.(drone.Client) 59 | 60 | login := data.Id() 61 | 62 | user, err := client.User(login) 63 | 64 | exists := (user.Login == login) && (err == nil) 65 | 66 | return exists, err 67 | } 68 | 69 | func createUser(data *schema.ResourceData) (user *drone.User) { 70 | user = &drone.User{ 71 | Login: data.Get("login").(string), 72 | } 73 | 74 | return 75 | } 76 | 77 | func readUser(data *schema.ResourceData, user *drone.User, err error) error { 78 | if err != nil { 79 | return err 80 | } 81 | 82 | data.SetId(user.Login) 83 | 84 | data.Set("login", user.Login) 85 | 86 | return nil 87 | } 88 | -------------------------------------------------------------------------------- /drone/resource_registry_test.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/resource" 7 | "github.com/hashicorp/terraform/terraform" 8 | "testing" 9 | ) 10 | 11 | func testRegistryConfigBasic(user, repo, address, username, password string) string { 12 | return fmt.Sprintf(` 13 | resource "drone_repo" "repo" { 14 | repository = "%s/%s" 15 | } 16 | 17 | resource "drone_registry" "registry" { 18 | repository = "${drone_repo.repo.repository}" 19 | address = "%s" 20 | username = "%s" 21 | password = "%s" 22 | } 23 | `, 24 | user, 25 | repo, 26 | address, 27 | username, 28 | password, 29 | ) 30 | } 31 | 32 | func TestRegistry(t *testing.T) { 33 | resource.Test(t, resource.TestCase{ 34 | PreCheck: func() { testAccPreCheck(t) }, 35 | Providers: testProviders, 36 | CheckDestroy: testRegistryDestroy, 37 | Steps: []resource.TestStep{ 38 | { 39 | Config: testRegistryConfigBasic( 40 | testDroneUser, 41 | "repository-1", 42 | "example.com", 43 | "user", 44 | "pass", 45 | ), 46 | Check: resource.ComposeTestCheckFunc( 47 | resource.TestCheckResourceAttr( 48 | "drone_registry.registry", 49 | "repository", 50 | fmt.Sprintf("%s/repository-1", testDroneUser), 51 | ), 52 | resource.TestCheckResourceAttr( 53 | "drone_registry.registry", 54 | "address", 55 | "example.com", 56 | ), 57 | resource.TestCheckResourceAttr( 58 | "drone_registry.registry", 59 | "username", 60 | "user", 61 | ), 62 | resource.TestCheckResourceAttr( 63 | "drone_registry.registry", 64 | "password", 65 | "pass", 66 | ), 67 | ), 68 | }, 69 | }, 70 | }) 71 | } 72 | 73 | func testRegistryDestroy(state *terraform.State) error { 74 | client := testProvider.Meta().(drone.Client) 75 | 76 | for _, resource := range state.RootModule().Resources { 77 | if resource.Type != "drone_registry" { 78 | continue 79 | } 80 | 81 | owner, repo, err := parseRepo(resource.Primary.Attributes["repository"]) 82 | 83 | if err != nil { 84 | return err 85 | } 86 | 87 | err = client.RegistryDelete(owner, repo, resource.Primary.Attributes["address"]) 88 | 89 | if err == nil { 90 | return fmt.Errorf( 91 | "Registry still exists: %s/%s:%s", 92 | owner, 93 | repo, 94 | resource.Primary.Attributes["address"], 95 | ) 96 | } 97 | } 98 | 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /drone/resource_repo_test.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/resource" 7 | "github.com/hashicorp/terraform/terraform" 8 | "testing" 9 | ) 10 | 11 | func testRepoConfigBasic(user, repo string) string { 12 | return fmt.Sprintf(` 13 | resource "drone_repo" "repo" { 14 | repository = "%s/%s" 15 | hooks = ["push", "pull_request", "tag", "deployment"] 16 | } 17 | `, user, repo) 18 | } 19 | 20 | func TestRepo(t *testing.T) { 21 | resource.Test(t, resource.TestCase{ 22 | PreCheck: func() { testAccPreCheck(t) }, 23 | Providers: testProviders, 24 | CheckDestroy: testRepoDestroy, 25 | Steps: []resource.TestStep{ 26 | { 27 | Config: testRepoConfigBasic(testDroneUser, "repository-1"), 28 | Check: resource.ComposeTestCheckFunc( 29 | resource.TestCheckResourceAttr( 30 | "drone_repo.repo", 31 | "repository", 32 | fmt.Sprintf("%s/repository-1", testDroneUser), 33 | ), 34 | resource.TestCheckResourceAttr( 35 | "drone_repo.repo", 36 | "visibility", 37 | "private", 38 | ), 39 | resource.TestCheckResourceAttr( 40 | "drone_repo.repo", 41 | "hooks.#", 42 | "4", 43 | ), 44 | resource.TestCheckResourceAttr( 45 | "drone_repo.repo", 46 | "hooks.1329302135", 47 | "deployment", 48 | ), 49 | resource.TestCheckResourceAttr( 50 | "drone_repo.repo", 51 | "hooks.1396138718", 52 | "pull_request", 53 | ), 54 | resource.TestCheckResourceAttr( 55 | "drone_repo.repo", 56 | "hooks.398155140", 57 | "tag", 58 | ), 59 | resource.TestCheckResourceAttr( 60 | "drone_repo.repo", 61 | "hooks.696883710", 62 | "push", 63 | ), 64 | resource.TestCheckResourceAttr( 65 | "drone_repo.repo", 66 | "timeout", 67 | "0", 68 | ), 69 | resource.TestCheckResourceAttr( 70 | "drone_repo.repo", 71 | "timeout", 72 | "0", 73 | ), 74 | resource.TestCheckResourceAttr( 75 | "drone_repo.repo", 76 | "gated", 77 | "false", 78 | ), 79 | resource.TestCheckResourceAttr( 80 | "drone_repo.repo", 81 | "trusted", 82 | "false", 83 | ), 84 | ), 85 | }, 86 | }, 87 | }) 88 | } 89 | 90 | func testRepoDestroy(state *terraform.State) error { 91 | client := testProvider.Meta().(drone.Client) 92 | 93 | for _, resource := range state.RootModule().Resources { 94 | if resource.Type != "drone_repo" { 95 | continue 96 | } 97 | 98 | owner, repo, err := parseRepo(resource.Primary.Attributes["repository"]) 99 | 100 | if err != nil { 101 | return err 102 | } 103 | 104 | repositories, err := client.RepoList() 105 | 106 | for _, repository := range repositories { 107 | if (repository.Owner == owner) && (repository.Name == repo) { 108 | client.RepoDel(owner, repo) 109 | return fmt.Errorf("Repo still exists: %s/%s", owner, repo) 110 | } 111 | } 112 | } 113 | 114 | return nil 115 | } 116 | -------------------------------------------------------------------------------- /drone/resource_secret_test.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/resource" 7 | "github.com/hashicorp/terraform/terraform" 8 | "testing" 9 | ) 10 | 11 | func testSecretConfigBasic(user, repo, name, value string) string { 12 | return fmt.Sprintf(` 13 | resource "drone_repo" "repo" { 14 | repository = "%s/%s" 15 | } 16 | 17 | resource "drone_secret" "secret" { 18 | repository = "${drone_repo.repo.repository}" 19 | name = "%s" 20 | value = "%s" 21 | events = ["push", "pull_request", "tag", "deployment"] 22 | } 23 | `, 24 | user, 25 | repo, 26 | name, 27 | value, 28 | ) 29 | } 30 | 31 | func TestSecret(t *testing.T) { 32 | resource.Test(t, resource.TestCase{ 33 | PreCheck: func() { testAccPreCheck(t) }, 34 | Providers: testProviders, 35 | CheckDestroy: testSecretDestroy, 36 | Steps: []resource.TestStep{ 37 | { 38 | Config: testSecretConfigBasic( 39 | testDroneUser, 40 | "repository-1", 41 | "password", 42 | "1234567890", 43 | ), 44 | Check: resource.ComposeTestCheckFunc( 45 | resource.TestCheckResourceAttr( 46 | "drone_secret.secret", 47 | "repository", 48 | fmt.Sprintf("%s/repository-1", testDroneUser), 49 | ), 50 | resource.TestCheckResourceAttr( 51 | "drone_secret.secret", 52 | "name", 53 | "password", 54 | ), 55 | resource.TestCheckResourceAttr( 56 | "drone_secret.secret", 57 | "value", 58 | "1234567890", 59 | ), 60 | resource.TestCheckResourceAttr( 61 | "drone_secret.secret", 62 | "images.#", 63 | "0", 64 | ), 65 | resource.TestCheckResourceAttr( 66 | "drone_secret.secret", 67 | "events.#", 68 | "4", 69 | ), 70 | resource.TestCheckResourceAttr( 71 | "drone_secret.secret", 72 | "events.1329302135", 73 | "deployment", 74 | ), 75 | resource.TestCheckResourceAttr( 76 | "drone_secret.secret", 77 | "events.1396138718", 78 | "pull_request", 79 | ), 80 | resource.TestCheckResourceAttr( 81 | "drone_secret.secret", 82 | "events.398155140", 83 | "tag", 84 | ), 85 | resource.TestCheckResourceAttr( 86 | "drone_secret.secret", 87 | "events.696883710", 88 | "push", 89 | ), 90 | ), 91 | }, 92 | }, 93 | }) 94 | } 95 | 96 | func testSecretDestroy(state *terraform.State) error { 97 | client := testProvider.Meta().(drone.Client) 98 | 99 | for _, resource := range state.RootModule().Resources { 100 | if resource.Type != "drone_secret" { 101 | continue 102 | } 103 | 104 | owner, repo, err := parseRepo(resource.Primary.Attributes["repository"]) 105 | 106 | if err != nil { 107 | return err 108 | } 109 | 110 | err = client.SecretDelete(owner, repo, resource.Primary.Attributes["name"]) 111 | 112 | if err == nil { 113 | return fmt.Errorf( 114 | "Secret still exists: %s/%s:%s", 115 | owner, 116 | repo, 117 | resource.Primary.Attributes["name"], 118 | ) 119 | } 120 | } 121 | 122 | return nil 123 | } 124 | -------------------------------------------------------------------------------- /drone/resource_registry.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/schema" 7 | "github.com/hashicorp/terraform/helper/validation" 8 | "regexp" 9 | ) 10 | 11 | func resourceRegistry() *schema.Resource { 12 | return &schema.Resource{ 13 | Schema: map[string]*schema.Schema{ 14 | "repository": { 15 | Type: schema.TypeString, 16 | Required: true, 17 | ForceNew: true, 18 | ValidateFunc: validation.StringMatch( 19 | regexp.MustCompile("^[^/ ]+/[^/ ]+$"), 20 | "Invalid repository (e.g. octocat/hello-world)", 21 | ), 22 | }, 23 | "address": { 24 | Type: schema.TypeString, 25 | Required: true, 26 | ForceNew: true, 27 | }, 28 | "username": { 29 | Type: schema.TypeString, 30 | Required: true, 31 | }, 32 | "password": { 33 | Type: schema.TypeString, 34 | Required: true, 35 | Sensitive: true, 36 | }, 37 | }, 38 | 39 | Importer: &schema.ResourceImporter{ 40 | State: schema.ImportStatePassthrough, 41 | }, 42 | 43 | Create: resourceRegistryCreate, 44 | Read: resourceRegistryRead, 45 | Update: resourceRegistryUpdate, 46 | Delete: resourceRegistryDelete, 47 | Exists: resourceRegistryExists, 48 | } 49 | } 50 | 51 | func resourceRegistryCreate(data *schema.ResourceData, meta interface{}) error { 52 | client := meta.(drone.Client) 53 | 54 | owner, repo, err := parseRepo(data.Get("repository").(string)) 55 | 56 | if err != nil { 57 | return err 58 | } 59 | 60 | registry, err := client.RegistryCreate(owner, repo, createRegistry(data)) 61 | 62 | data.Set("password", data.Get("password").(string)) 63 | 64 | return readRegistry(data, owner, repo, registry, err) 65 | } 66 | 67 | func resourceRegistryRead(data *schema.ResourceData, meta interface{}) error { 68 | client := meta.(drone.Client) 69 | 70 | owner, repo, address, err := parseId(data.Id(), "drone.io") 71 | 72 | if err != nil { 73 | return err 74 | } 75 | 76 | registry, err := client.Registry(owner, repo, address) 77 | 78 | return readRegistry(data, owner, repo, registry, err) 79 | } 80 | 81 | func resourceRegistryUpdate(data *schema.ResourceData, meta interface{}) error { 82 | client := meta.(drone.Client) 83 | 84 | owner, repo, err := parseRepo(data.Get("repository").(string)) 85 | 86 | if err != nil { 87 | return err 88 | } 89 | 90 | registry, err := client.RegistryUpdate(owner, repo, createRegistry(data)) 91 | 92 | data.Set("password", data.Get("password").(string)) 93 | 94 | return readRegistry(data, owner, repo, registry, err) 95 | } 96 | 97 | func resourceRegistryDelete(data *schema.ResourceData, meta interface{}) error { 98 | client := meta.(drone.Client) 99 | 100 | owner, repo, address, err := parseId(data.Id(), "drone.io") 101 | 102 | if err != nil { 103 | return err 104 | } 105 | 106 | return client.RegistryDelete(owner, repo, address) 107 | } 108 | 109 | func resourceRegistryExists(data *schema.ResourceData, meta interface{}) (bool, error) { 110 | client := meta.(drone.Client) 111 | 112 | owner, repo, address, err := parseId(data.Id(), "drone.io") 113 | 114 | if err != nil { 115 | return false, err 116 | } 117 | 118 | registry, err := client.Registry(owner, repo, address) 119 | 120 | exists := (registry.Address == address) && (err == nil) 121 | 122 | return exists, err 123 | } 124 | 125 | func createRegistry(data *schema.ResourceData) (registry *drone.Registry) { 126 | registry = &drone.Registry{ 127 | Address: data.Get("address").(string), 128 | Username: data.Get("username").(string), 129 | Password: data.Get("password").(string), 130 | } 131 | 132 | return 133 | } 134 | 135 | func readRegistry(data *schema.ResourceData, owner, repo string, registry *drone.Registry, err error) error { 136 | if err != nil { 137 | return err 138 | } 139 | 140 | data.SetId(fmt.Sprintf("%s/%s/%s", owner, repo, registry.Address)) 141 | 142 | data.Set("repository", fmt.Sprintf("%s/%s", owner, repo)) 143 | data.Set("address", registry.Address) 144 | data.Set("username", registry.Username) 145 | 146 | return nil 147 | } 148 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drone Terraform Provider 2 | 3 | A [Terraform](https://www.terraform.io) provider for configuring the 4 | [Drone](https://drone.io) continuous delivery platform. 5 | 6 | ## Installing 7 | 8 | You can download the plugin from the [Releases](https://github.com/artisanofcode/terraform-provider-drone/releases/latest) page, 9 | for help installing please refer to the [Official Documentation](https://www.terraform.io/docs/plugins/basics.html#installing-a-plugin). 10 | 11 | 12 | ## Example 13 | 14 | ```terraform 15 | provider "drone" { 16 | server = "https:://ci.example.com/" 17 | token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXh0Ijoib2N0b2NhdCIsInR5cGUiOiJ1c2VyIn0.Fg0eYxO9x2CfGIvIHDZKhQbCGbRAsSB_iRDJlDEW6vc" 18 | } 19 | 20 | resource "drone_repo" "hello_world" { 21 | repository = "octocat/hello-world" 22 | visability = "public" 23 | hooks = ["push", "pull_request", "tag", "deployment"] 24 | } 25 | 26 | resource "drone_secret" "master_password" { 27 | repository = "${resource.hello_world.repository}" 28 | name = "master_password" 29 | value = "correct horse battery staple" 30 | events = ["push", "pull_request", "tag", "deployment"] 31 | } 32 | ``` 33 | 34 | ## Provider 35 | 36 | #### Example Usage 37 | 38 | ```terraform 39 | provider "drone" { 40 | server = "https://ci.example.com/" 41 | token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0ZXh0Ijoib2N0b2NhdCIsInR5cGUiOiJ1c2VyIn0.Fg0eYxO9x2CfGIvIHDZKhQbCGbRAsSB_iRDJlDEW6vc" 42 | } 43 | ```` 44 | 45 | #### Argument Reference 46 | 47 | * `server` - (Optional) The Drone servers url, It must be provided, but can also 48 | be sourced from the `DRONE_SERVER` environment variable. 49 | * `token` - (Optional) The Drone servers api token, It must be provided, but can 50 | also be sourced from the `DRONE_TOKEN` environment variable. 51 | 52 | ## Resources 53 | 54 | ### `drone_registry` 55 | 56 | Manage a repository registry. 57 | 58 | #### Example Usage 59 | 60 | ```terraform 61 | resource "drone_registry" "docker_io" { 62 | repository = "octocat/hello-world" 63 | address = "docker.io" 64 | username = "octocat" 65 | password = "correct horse battery staple" 66 | } 67 | ``` 68 | 69 | #### Argument Reference 70 | 71 | * `repository` - (Required) Repository name (e.g. `octocat/hello-world`). 72 | * `address` - (Required) Registry address. 73 | * `username` - (Required) Registry username. 74 | * `password` - (Required) Registry password. 75 | 76 | ### `drone_repo` 77 | 78 | Activate and configure a repository. 79 | 80 | #### Example Usage 81 | 82 | ```terraform 83 | resource "drone_repo" "hello_world" { 84 | repository = "octocat/hello-world" 85 | visability = "public" 86 | hooks = ["push", "pull_request", "tag", "deployment"] 87 | } 88 | ``` 89 | 90 | #### Argument Reference 91 | 92 | * `repository` - (Required) Repository name (e.g. `octocat/hello-world`). 93 | * `trusted` - (Optional) Repository is trusted (default: `false`). 94 | * `gated` - (Optional) Repository is gated (default: `false`). 95 | * `timeout` - (Optional) Repository timeout (default: `0`). 96 | * `visibility` - (Optional) Repository visibility (default: `private`). 97 | * `hooks` - (Optional) List of hooks this repository should setup is limited to, 98 | values must be `push`, `pull_request`, `tag`, and/or `deployment`. 99 | 100 | ### `drone_secret` 101 | 102 | Manage a repository secret. 103 | 104 | #### Example Usage 105 | 106 | ```terraform 107 | resource "drone_secret" "master_password" { 108 | repository = "octocat/hello-world" 109 | name = "master_password" 110 | value = "correct horse battery staple" 111 | events = ["push", "pull_request", "tag", "deployment"] 112 | } 113 | ```` 114 | 115 | #### Argument Reference 116 | 117 | * `repository` - (Required) Repository name (e.g. `octocat/hello-world`). 118 | * `name` - (Required) Secret name. 119 | * `value` - (Required) Secret value. 120 | * `images` - (Optional) List of images this secret is limited to. 121 | * `events` - (Optional) List of events this repository should setup is limited to, 122 | values must be `push`, `pull_request`, `tag`, and/or `deployment` (default: `["push", "tag", "deployment"]`). 123 | 124 | ### `drone_user` 125 | 126 | Manage a user. 127 | 128 | #### Example Usage 129 | 130 | ```terraform 131 | resource "drone_user" "octocat" { 132 | login = "octocat" 133 | } 134 | ```` 135 | 136 | #### Argument Reference 137 | 138 | * `login` - (Required) Login name. 139 | 140 | ## Source 141 | 142 | To install from source: 143 | 144 | ```shell 145 | git clone git://github.com/artisanofcode/terraform-provider-drone.git 146 | cd terraform-provider-drone 147 | go get 148 | go build 149 | ``` 150 | 151 | ## Licence 152 | 153 | This project is licensed under the [MIT licence](http://dan.mit-license.org/). 154 | 155 | ## Meta 156 | 157 | This project uses [Semantic Versioning](http://semver.org/). 158 | -------------------------------------------------------------------------------- /drone/resource_secret.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/schema" 7 | "github.com/hashicorp/terraform/helper/validation" 8 | "regexp" 9 | ) 10 | 11 | var ( 12 | defaultSecretEvents = []string{ 13 | drone.EventPush, 14 | drone.EventTag, 15 | drone.EventDeploy, 16 | } 17 | validSecretEvents = []string{ 18 | drone.EventPull, 19 | drone.EventPush, 20 | drone.EventTag, 21 | drone.EventDeploy, 22 | } 23 | ) 24 | 25 | func resourceSecret() *schema.Resource { 26 | return &schema.Resource{ 27 | Schema: map[string]*schema.Schema{ 28 | "repository": { 29 | Type: schema.TypeString, 30 | Required: true, 31 | ForceNew: true, 32 | ValidateFunc: validation.StringMatch( 33 | regexp.MustCompile("^[^/ ]+/[^/ ]+$"), 34 | "Invalid repository (e.g. octocat/hello-world)", 35 | ), 36 | }, 37 | "name": { 38 | Type: schema.TypeString, 39 | Required: true, 40 | ForceNew: true, 41 | }, 42 | "value": { 43 | Type: schema.TypeString, 44 | Required: true, 45 | Sensitive: true, 46 | }, 47 | "images": { 48 | Type: schema.TypeSet, 49 | Optional: true, 50 | // ValidateFunc: validation.ValidateListUniqueStrings, 51 | Elem: &schema.Schema{ 52 | Type: schema.TypeString, 53 | }, 54 | }, 55 | "events": { 56 | Type: schema.TypeSet, 57 | Optional: true, 58 | // ValidateFunc: validation.ValidateListUniqueStrings, 59 | Elem: &schema.Schema{ 60 | Type: schema.TypeString, 61 | ValidateFunc: validation.StringInSlice(validSecretEvents, true), 62 | }, 63 | }, 64 | }, 65 | 66 | Importer: &schema.ResourceImporter{ 67 | State: schema.ImportStatePassthrough, 68 | }, 69 | 70 | Create: resourceSecretCreate, 71 | Read: resourceSecretRead, 72 | Update: resourceSecretUpdate, 73 | Delete: resourceSecretDelete, 74 | Exists: resourceSecretExists, 75 | } 76 | } 77 | 78 | func resourceSecretCreate(data *schema.ResourceData, meta interface{}) error { 79 | client := meta.(drone.Client) 80 | 81 | owner, repo, err := parseRepo(data.Get("repository").(string)) 82 | 83 | if err != nil { 84 | return err 85 | } 86 | 87 | secret, err := client.SecretCreate(owner, repo, createSecret(data)) 88 | 89 | data.Set("value", data.Get("value").(string)) 90 | 91 | return readSecret(data, owner, repo, secret, err) 92 | } 93 | 94 | func resourceSecretRead(data *schema.ResourceData, meta interface{}) error { 95 | client := meta.(drone.Client) 96 | 97 | owner, repo, name, err := parseId(data.Id(), "secret_password") 98 | 99 | if err != nil { 100 | return err 101 | } 102 | 103 | secret, err := client.Secret(owner, repo, name) 104 | 105 | return readSecret(data, owner, repo, secret, err) 106 | } 107 | 108 | func resourceSecretUpdate(data *schema.ResourceData, meta interface{}) error { 109 | client := meta.(drone.Client) 110 | 111 | owner, repo, err := parseRepo(data.Get("repository").(string)) 112 | 113 | if err != nil { 114 | return err 115 | } 116 | 117 | secret, err := client.SecretUpdate(owner, repo, createSecret(data)) 118 | 119 | data.Set("value", data.Get("value").(string)) 120 | 121 | return readSecret(data, owner, repo, secret, err) 122 | } 123 | 124 | func resourceSecretDelete(data *schema.ResourceData, meta interface{}) error { 125 | client := meta.(drone.Client) 126 | 127 | owner, repo, name, err := parseId(data.Id(), "secret_password") 128 | 129 | if err != nil { 130 | return err 131 | } 132 | 133 | return client.SecretDelete(owner, repo, name) 134 | } 135 | 136 | func resourceSecretExists(data *schema.ResourceData, meta interface{}) (bool, error) { 137 | client := meta.(drone.Client) 138 | 139 | owner, repo, name, err := parseId(data.Id(), "secret_password") 140 | 141 | if err != nil { 142 | return false, err 143 | } 144 | 145 | secret, err := client.Secret(owner, repo, name) 146 | 147 | exists := (secret.Name == name) && (err == nil) 148 | 149 | return exists, err 150 | } 151 | 152 | func createSecret(data *schema.ResourceData) (secret *drone.Secret) { 153 | events := []string{} 154 | eventSet := data.Get("events").(*schema.Set) 155 | for _, v := range eventSet.List() { 156 | events = append(events, v.(string)) 157 | } 158 | 159 | images := []string{} 160 | imageSet := data.Get("images").(*schema.Set) 161 | for _, v := range imageSet.List() { 162 | images = append(images, v.(string)) 163 | } 164 | 165 | secret = &drone.Secret{ 166 | Name: data.Get("name").(string), 167 | Value: data.Get("value").(string), 168 | Images: images, 169 | Events: events, 170 | } 171 | 172 | if len(secret.Events) == 0 { 173 | secret.Events = defaultSecretEvents 174 | } 175 | 176 | return 177 | } 178 | 179 | func readSecret(data *schema.ResourceData, owner, repo string, secret *drone.Secret, err error) error { 180 | if err != nil { 181 | return err 182 | } 183 | 184 | data.SetId(fmt.Sprintf("%s/%s/%s", owner, repo, secret.Name)) 185 | 186 | data.Set("repository", fmt.Sprintf("%s/%s", owner, repo)) 187 | data.Set("name", secret.Name) 188 | data.Set("images", secret.Images) 189 | data.Set("events", secret.Events) 190 | 191 | return nil 192 | } 193 | -------------------------------------------------------------------------------- /drone/resource_repo.go: -------------------------------------------------------------------------------- 1 | package drone 2 | 3 | import ( 4 | "fmt" 5 | "github.com/drone/drone-go/drone" 6 | "github.com/hashicorp/terraform/helper/schema" 7 | "github.com/hashicorp/terraform/helper/validation" 8 | "regexp" 9 | ) 10 | 11 | var validRepoHooks = []string{ 12 | drone.EventPull, 13 | drone.EventPush, 14 | drone.EventTag, 15 | drone.EventDeploy, 16 | } 17 | 18 | func resourceRepo() *schema.Resource { 19 | return &schema.Resource{ 20 | Schema: map[string]*schema.Schema{ 21 | "repository": { 22 | Type: schema.TypeString, 23 | Required: true, 24 | ForceNew: true, 25 | ValidateFunc: validation.StringMatch( 26 | regexp.MustCompile("^[^/ ]+/[^/ ]+$"), 27 | "Invalid repository (e.g. octocat/hello-world)", 28 | ), 29 | }, 30 | "trusted": { 31 | Type: schema.TypeBool, 32 | Optional: true, 33 | }, 34 | "gated": { 35 | Type: schema.TypeBool, 36 | Optional: true, 37 | }, 38 | "timeout": { 39 | Type: schema.TypeInt, 40 | Optional: true, 41 | }, 42 | "visibility": { 43 | Type: schema.TypeString, 44 | Optional: true, 45 | Default: "private", 46 | }, 47 | "hooks": { 48 | Type: schema.TypeSet, 49 | Optional: true, 50 | // ValidateFunc: validation.ValidateListUniqueStrings, 51 | Elem: &schema.Schema{ 52 | Type: schema.TypeString, 53 | ValidateFunc: validation.StringInSlice(validRepoHooks, true), 54 | }, 55 | }, 56 | }, 57 | 58 | Importer: &schema.ResourceImporter{ 59 | State: schema.ImportStatePassthrough, 60 | }, 61 | 62 | Create: resourceRepoCreate, 63 | Read: resourceRepoRead, 64 | Update: resourceRepoUpdate, 65 | Delete: resourceRepoDelete, 66 | Exists: resourceRepoExists, 67 | } 68 | } 69 | 70 | func resourceRepoCreate(data *schema.ResourceData, meta interface{}) error { 71 | client := meta.(drone.Client) 72 | 73 | owner, repo, err := parseRepo(data.Get("repository").(string)) 74 | 75 | if err != nil { 76 | return err 77 | } 78 | 79 | _, err = client.RepoPost(owner, repo) 80 | 81 | if err != nil { 82 | return err 83 | } 84 | 85 | repository, err := client.RepoPatch(owner, repo, createRepo(data)) 86 | 87 | if err != nil { 88 | return err 89 | } 90 | 91 | return readRepo(data, repository, err) 92 | } 93 | 94 | func resourceRepoRead(data *schema.ResourceData, meta interface{}) error { 95 | client := meta.(drone.Client) 96 | 97 | owner, repo, err := parseRepo(data.Id()) 98 | 99 | if err != nil { 100 | return err 101 | } 102 | 103 | repository, err := client.Repo(owner, repo) 104 | 105 | return readRepo(data, repository, err) 106 | } 107 | 108 | func resourceRepoUpdate(data *schema.ResourceData, meta interface{}) error { 109 | client := meta.(drone.Client) 110 | 111 | owner, repo, err := parseRepo(data.Get("repository").(string)) 112 | 113 | if err != nil { 114 | return err 115 | } 116 | 117 | repository, err := client.RepoPatch(owner, repo, createRepo(data)) 118 | 119 | return readRepo(data, repository, err) 120 | } 121 | 122 | func resourceRepoDelete(data *schema.ResourceData, meta interface{}) error { 123 | client := meta.(drone.Client) 124 | 125 | owner, repo, err := parseRepo(data.Id()) 126 | 127 | if err != nil { 128 | return err 129 | } 130 | 131 | return client.RepoDel(owner, repo) 132 | } 133 | 134 | func resourceRepoExists(data *schema.ResourceData, meta interface{}) (bool, error) { 135 | client := meta.(drone.Client) 136 | 137 | owner, repo, err := parseRepo(data.Id()) 138 | 139 | if err != nil { 140 | return false, err 141 | } 142 | 143 | repository, err := client.Repo(owner, repo) 144 | 145 | exists := (repository.Owner == owner) && (repository.Name == repo) && (err == nil) 146 | 147 | return exists, err 148 | } 149 | 150 | func createRepo(data *schema.ResourceData) (repository *drone.RepoPatch) { 151 | hooks := data.Get("hooks").(*schema.Set) 152 | 153 | trusted := data.Get("trusted").(bool) 154 | gated := data.Get("gated").(bool) 155 | timeout := int64(data.Get("timeout").(int)) 156 | visibility := data.Get("visibility").(string) 157 | pull := hooks.Contains(drone.EventPull) 158 | push := hooks.Contains(drone.EventPush) 159 | deploy := hooks.Contains(drone.EventDeploy) 160 | tag := hooks.Contains(drone.EventTag) 161 | 162 | repository = &drone.RepoPatch{ 163 | IsTrusted: &trusted, 164 | IsGated: &gated, 165 | Timeout: &timeout, 166 | Visibility: &visibility, 167 | AllowPull: &pull, 168 | AllowPush: &push, 169 | AllowDeploy: &deploy, 170 | AllowTag: &tag, 171 | } 172 | 173 | return 174 | } 175 | 176 | func readRepo(data *schema.ResourceData, repository *drone.Repo, err error) error { 177 | if err != nil { 178 | return err 179 | } 180 | 181 | data.SetId(fmt.Sprintf("%s/%s", repository.Owner, repository.Name)) 182 | 183 | hooks := make([]string, 0) 184 | 185 | if repository.AllowPull == true { 186 | hooks = append(hooks, drone.EventPull) 187 | } 188 | 189 | if repository.AllowPush == true { 190 | hooks = append(hooks, drone.EventPush) 191 | } 192 | 193 | if repository.AllowDeploy == true { 194 | hooks = append(hooks, drone.EventDeploy) 195 | } 196 | 197 | if repository.AllowTag == true { 198 | hooks = append(hooks, drone.EventTag) 199 | } 200 | 201 | data.Set("repository", fmt.Sprintf("%s/%s", repository.Owner, repository.Name)) 202 | data.Set("trusted", repository.IsTrusted) 203 | data.Set("gated", repository.IsGated) 204 | data.Set("timeout", repository.Timeout) 205 | data.Set("visibility", repository.Visibility) 206 | data.Set("hooks", hooks) 207 | 208 | return nil 209 | } 210 | --------------------------------------------------------------------------------