mirror of
https://github.com/ysoftdevs/terraform-provider-bitbucketserver.git
synced 2026-03-27 20:01:07 +01:00
Initial Commit
This commit is contained in:
104
bitbucket/client.go
Normal file
104
bitbucket/client.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Error represents a error from the bitbucket api.
|
||||
type Error struct {
|
||||
APIError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
} `json:"error,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
StatusCode int
|
||||
Endpoint string
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("API Error: %d %s %s", e.StatusCode, e.Endpoint, e.APIError.Message)
|
||||
}
|
||||
|
||||
type BitbucketClient struct {
|
||||
Server string
|
||||
Username string
|
||||
Password string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func (c *BitbucketClient) Do(method, endpoint string, payload *bytes.Buffer) (*http.Response, error) {
|
||||
|
||||
absoluteendpoint := c.Server + endpoint
|
||||
log.Printf("[DEBUG] Sending request to %s %s", method, absoluteendpoint)
|
||||
|
||||
var bodyreader io.Reader
|
||||
|
||||
if payload != nil {
|
||||
log.Printf("[DEBUG] With payload %s", payload.String())
|
||||
bodyreader = payload
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, absoluteendpoint, bodyreader)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.SetBasicAuth(c.Username, c.Password)
|
||||
|
||||
if payload != nil {
|
||||
// Can cause bad request when putting default reviews if set.
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
req.Close = true
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
log.Printf("[DEBUG] Resp: %v Err: %v", resp, err)
|
||||
if resp.StatusCode >= 400 || resp.StatusCode < 200 {
|
||||
apiError := Error{
|
||||
StatusCode: resp.StatusCode,
|
||||
Endpoint: endpoint,
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] Resp Body: %s", string(body))
|
||||
|
||||
err = json.Unmarshal(body, &apiError)
|
||||
if err != nil {
|
||||
apiError.APIError.Message = string(body)
|
||||
}
|
||||
|
||||
return resp, error(apiError)
|
||||
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (c *BitbucketClient) Get(endpoint string) (*http.Response, error) {
|
||||
return c.Do("GET", endpoint, nil)
|
||||
}
|
||||
|
||||
func (c *BitbucketClient) Post(endpoint string, jsonpayload *bytes.Buffer) (*http.Response, error) {
|
||||
return c.Do("POST", endpoint, jsonpayload)
|
||||
}
|
||||
|
||||
func (c *BitbucketClient) Put(endpoint string, jsonpayload *bytes.Buffer) (*http.Response, error) {
|
||||
return c.Do("PUT", endpoint, jsonpayload)
|
||||
}
|
||||
|
||||
func (c *BitbucketClient) PutOnly(endpoint string) (*http.Response, error) {
|
||||
return c.Do("PUT", endpoint, nil)
|
||||
}
|
||||
|
||||
func (c *BitbucketClient) Delete(endpoint string) (*http.Response, error) {
|
||||
return c.Do("DELETE", endpoint, nil)
|
||||
}
|
||||
52
bitbucket/provider.go
Normal file
52
bitbucket/provider.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func Provider() terraform.ResourceProvider {
|
||||
return &schema.Provider{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"server": {
|
||||
Required: true,
|
||||
Type: schema.TypeString,
|
||||
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_SERVER", nil),
|
||||
},
|
||||
"username": {
|
||||
Required: true,
|
||||
Type: schema.TypeString,
|
||||
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_USERNAME", nil),
|
||||
},
|
||||
"password": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_PASSWORD", nil),
|
||||
},
|
||||
},
|
||||
ConfigureFunc: providerConfigure,
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"bitbucketserver_repository": resourceRepository(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
||||
|
||||
serverSanitized := d.Get("server").(string)
|
||||
if strings.HasSuffix(serverSanitized, "/") {
|
||||
serverSanitized = serverSanitized[0 : len(serverSanitized)-1]
|
||||
}
|
||||
|
||||
client := &BitbucketClient{
|
||||
Server: serverSanitized,
|
||||
Username: d.Get("username").(string),
|
||||
Password: d.Get("password").(string),
|
||||
HTTPClient: &http.Client{},
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
44
bitbucket/provider_test.go
Normal file
44
bitbucket/provider_test.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const testRepo string = "test-repo"
|
||||
|
||||
var testAccProviders map[string]terraform.ResourceProvider
|
||||
var testAccProvider *schema.Provider
|
||||
|
||||
func init() {
|
||||
testAccProvider = Provider().(*schema.Provider)
|
||||
testAccProviders = map[string]terraform.ResourceProvider{
|
||||
"bitbucketserver": testAccProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider(t *testing.T) {
|
||||
if err := Provider().(*schema.Provider).InternalValidate(); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_impl(t *testing.T) {
|
||||
var _ terraform.ResourceProvider = Provider()
|
||||
}
|
||||
|
||||
func testAccPreCheck(t *testing.T) {
|
||||
if v := os.Getenv("BITBUCKET_SERVER"); v == "" {
|
||||
t.Fatal("BITBUCKET_SERVER must be set for acceptance tests")
|
||||
}
|
||||
|
||||
if v := os.Getenv("BITBUCKET_USERNAME"); v == "" {
|
||||
t.Fatal("BITBUCKET_USERNAME must be set for acceptance tests")
|
||||
}
|
||||
|
||||
if v := os.Getenv("BITBUCKET_PASSWORD"); v == "" {
|
||||
t.Fatal("BITBUCKET_PASSWORD must be set for acceptance tests")
|
||||
}
|
||||
}
|
||||
246
bitbucket/resource_repository.go
Normal file
246
bitbucket/resource_repository.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type CloneUrl struct {
|
||||
Href string `json:"href,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type Repository struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
SCM string `json:"scmId,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Forkable bool `json:"forkable,omitempty"`
|
||||
Public bool `json:"public,omitempty"`
|
||||
Links struct {
|
||||
Clone []CloneUrl `json:"clone,omitempty"`
|
||||
} `json:"links,omitempty"`
|
||||
}
|
||||
|
||||
func resourceRepository() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Create: resourceRepositoryCreate,
|
||||
Update: resourceRepositoryUpdate,
|
||||
Read: resourceRepositoryRead,
|
||||
Exists: resourceRepositoryExists,
|
||||
Delete: resourceRepositoryDelete,
|
||||
Importer: &schema.ResourceImporter{
|
||||
State: schema.ImportStatePassthrough,
|
||||
},
|
||||
|
||||
Schema: map[string]*schema.Schema{
|
||||
"name": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"slug": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
},
|
||||
"project": {
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
},
|
||||
"scm": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Default: "git",
|
||||
},
|
||||
"description": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
},
|
||||
"forkable": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: true,
|
||||
},
|
||||
"public": {
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Default: false,
|
||||
},
|
||||
"clone_ssh": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
"clone_https": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newRepositoryFromResource(d *schema.ResourceData) (Repo *Repository, Project string) {
|
||||
repo := &Repository{
|
||||
Name: d.Get("name").(string),
|
||||
Slug: d.Get("slug").(string),
|
||||
SCM: d.Get("scm").(string),
|
||||
Description: d.Get("description").(string),
|
||||
Forkable: d.Get("forkable").(bool),
|
||||
Public: d.Get("public").(bool),
|
||||
}
|
||||
|
||||
return repo, d.Get("project").(string)
|
||||
}
|
||||
|
||||
func resourceRepositoryUpdate(d *schema.ResourceData, m interface{}) error {
|
||||
client := m.(*BitbucketClient)
|
||||
repo, project := newRepositoryFromResource(d)
|
||||
|
||||
bytedata, err := json.Marshal(repo)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
repoSlug := determineSlug(d)
|
||||
|
||||
_, err = client.Put(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s",
|
||||
project,
|
||||
repoSlug,
|
||||
), bytes.NewBuffer(bytedata))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return resourceRepositoryRead(d, m)
|
||||
}
|
||||
|
||||
func resourceRepositoryCreate(d *schema.ResourceData, m interface{}) error {
|
||||
client := m.(*BitbucketClient)
|
||||
repo, project := newRepositoryFromResource(d)
|
||||
|
||||
bytedata, err := json.Marshal(repo)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = client.Post(fmt.Sprintf("/rest/api/1.0/projects/%s/repos",
|
||||
project,
|
||||
), bytes.NewBuffer(bytedata))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.SetId(string(fmt.Sprintf("%s/%s", project, repo.Name)))
|
||||
|
||||
return resourceRepositoryRead(d, m)
|
||||
}
|
||||
|
||||
func resourceRepositoryRead(d *schema.ResourceData, m interface{}) error {
|
||||
id := d.Id()
|
||||
if id != "" {
|
||||
idparts := strings.Split(id, "/")
|
||||
if len(idparts) == 2 {
|
||||
d.Set("project", idparts[0])
|
||||
d.Set("slug", idparts[1])
|
||||
} else {
|
||||
return fmt.Errorf("incorrect ID format, should match `project/slug`")
|
||||
}
|
||||
}
|
||||
|
||||
repoSlug := determineSlug(d)
|
||||
project := d.Get("project").(string)
|
||||
|
||||
client := m.(*BitbucketClient)
|
||||
repo_req, err := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s",
|
||||
project,
|
||||
repoSlug,
|
||||
))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if repo_req.StatusCode == 200 {
|
||||
|
||||
var repo Repository
|
||||
|
||||
body, readerr := ioutil.ReadAll(repo_req.Body)
|
||||
if readerr != nil {
|
||||
return readerr
|
||||
}
|
||||
|
||||
decodeerr := json.Unmarshal(body, &repo)
|
||||
if decodeerr != nil {
|
||||
return decodeerr
|
||||
}
|
||||
|
||||
d.Set("name", repo.Name)
|
||||
if repo.Slug != "" && repo.Name != repo.Slug {
|
||||
d.Set("slug", repo.Slug)
|
||||
}
|
||||
d.Set("scmId", repo.SCM)
|
||||
d.Set("description", repo.Description)
|
||||
d.Set("forkable", repo.Forkable)
|
||||
d.Set("public", repo.Public)
|
||||
|
||||
for _, clone_url := range repo.Links.Clone {
|
||||
if clone_url.Name == "http" {
|
||||
d.Set("clone_https", clone_url.Href)
|
||||
} else {
|
||||
d.Set("clone_ssh", clone_url.Href)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceRepositoryExists(d *schema.ResourceData, m interface{}) (bool, error) {
|
||||
client := m.(*BitbucketClient)
|
||||
repoSlug := determineSlug(d)
|
||||
project := d.Get("project").(string)
|
||||
repo_req, err := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s",
|
||||
project,
|
||||
repoSlug,
|
||||
))
|
||||
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get repository %s/%s from bitbucket: %+v", project, repoSlug, err)
|
||||
}
|
||||
|
||||
if repo_req.StatusCode == 200 {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
func resourceRepositoryDelete(d *schema.ResourceData, m interface{}) error {
|
||||
repoSlug := determineSlug(d)
|
||||
project := d.Get("project").(string)
|
||||
client := m.(*BitbucketClient)
|
||||
_, err := client.Delete(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s",
|
||||
project,
|
||||
repoSlug,
|
||||
))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func determineSlug(d *schema.ResourceData) string {
|
||||
var repoSlug string
|
||||
repoSlug = d.Get("slug").(string)
|
||||
if repoSlug == "" {
|
||||
repoSlug = d.Get("name").(string)
|
||||
}
|
||||
|
||||
return repoSlug
|
||||
}
|
||||
89
bitbucket/resource_repository_test.go
Normal file
89
bitbucket/resource_repository_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package bitbucket
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/resource"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
)
|
||||
|
||||
func TestAccBitbucketRepository_basic(t *testing.T) {
|
||||
var repo Repository
|
||||
|
||||
testAccBitbucketRepositoryConfig := `
|
||||
resource "bitbucketserver_repository" "test_repo" {
|
||||
project = "TEST"
|
||||
name = "test-repo-for-repository-test"
|
||||
}
|
||||
`
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckBitbucketRepositoryDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccBitbucketRepositoryConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckBitbucketRepositoryExists("bitbucketserver_repository.test_repo", &repo),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccBitbucketRepository_namewithspaces(t *testing.T) {
|
||||
var repo Repository
|
||||
|
||||
testAccBitbucketRepositoryConfig := `
|
||||
resource "bitbucketserver_repository" "test_repo" {
|
||||
project = "TEST"
|
||||
name = "Test Repo For Repository Test"
|
||||
slug = "test-repo-for-repository-test"
|
||||
}
|
||||
`
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
CheckDestroy: testAccCheckBitbucketRepositoryDestroy,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: testAccBitbucketRepositoryConfig,
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckBitbucketRepositoryExists("bitbucketserver_repository.test_repo", &repo),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func testAccCheckBitbucketRepositoryDestroy(s *terraform.State) error {
|
||||
client := testAccProvider.Meta().(*BitbucketClient)
|
||||
rs, ok := s.RootModule().Resources["bitbucketserver_repository.test_repo"]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found %s", "bitbucketserver_repository.test_repo")
|
||||
}
|
||||
|
||||
response, _ := client.Get(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s", rs.Primary.Attributes["project"], rs.Primary.Attributes["slug"]))
|
||||
|
||||
if response.StatusCode != 404 {
|
||||
return fmt.Errorf("repository still exists")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func testAccCheckBitbucketRepositoryExists(n string, repository *Repository) resource.TestCheckFunc {
|
||||
return func(s *terraform.State) error {
|
||||
rs, ok := s.RootModule().Resources[n]
|
||||
if !ok {
|
||||
return fmt.Errorf("not found %s", n)
|
||||
}
|
||||
if rs.Primary.ID == "" {
|
||||
return fmt.Errorf("no repository ID is set")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user