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"` 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"` } type RepositoryForkProject struct { Key string `json:"key,omitempty"` } type RepositoryFork struct { Name string `json:"name,omitempty"` Project RepositoryForkProject `json:"project,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, ForceNew: true, }, "slug": { Type: schema.TypeString, Optional: true, Computed: true, }, "project": { Type: schema.TypeString, Required: true, ForceNew: true, }, "description": { Type: schema.TypeString, Optional: true, }, "forkable": { Type: schema.TypeBool, Optional: true, Default: true, }, "public": { Type: schema.TypeBool, Optional: true, Default: false, }, "fork_repository_project": { Type: schema.TypeString, Optional: true, ForceNew: true, }, "fork_repository_slug": { Type: schema.TypeString, Optional: true, ForceNew: true, }, "enable_git_lfs": { 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) { repo := &Repository{ Name: d.Get("name").(string), Slug: d.Get("slug").(string), Description: d.Get("description").(string), Forkable: d.Get("forkable").(bool), Public: d.Get("public").(bool), } return repo } func resourceRepositoryUpdate(d *schema.ResourceData, m interface{}) error { client := m.(*BitbucketServerProvider).BitbucketClient project := d.Get("project").(string) repo := 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 } err = handleRepositoryGitLFSChanges(client, project, repoSlug, d) if err != nil { return err } return resourceRepositoryRead(d, m) } func resourceRepositoryCreate(d *schema.ResourceData, m interface{}) error { client := m.(*BitbucketServerProvider).BitbucketClient project := d.Get("project").(string) repoSlug := determineSlug(d) name := d.Get("name").(string) forkProject := d.Get("fork_repository_project").(string) forkRepo := d.Get("fork_repository_slug").(string) if (forkProject != "" && forkRepo == "") || (forkRepo != "" && forkProject == "") { return fmt.Errorf("both fork_repository_project and fork_repository_slug need to be specified when forking an existing repository") } if forkProject != "" { err := createNewRepositoryFromFork(client, d, project, repoSlug, forkProject, forkRepo) if err != nil { return err } } else { err := createNewRepository(client, d, project) if err != nil { return err } } d.SetId(string(fmt.Sprintf("%s/%s", project, name))) err := handleRepositoryGitLFSChanges(client, project, repoSlug, d) if err != nil { return err } if forkProject != "" { // after forking a repository, run the update loop to update any names/descriptions etc of the forked repo return resourceRepositoryUpdate(d, m) } else { return resourceRepositoryRead(d, m) } } func createNewRepository(client *BitbucketClient, d *schema.ResourceData, project string) error { repo := 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 } return nil } func createNewRepositoryFromFork(client *BitbucketClient, d *schema.ResourceData, project string, repository string, forkProject string, forkRepository string) error { requestBody := &RepositoryFork{ Name: repository, Project: RepositoryForkProject{ Key: forkProject, }, } bytedata, err := json.Marshal(requestBody) if err != nil { return err } _, err = client.Post(fmt.Sprintf("/rest/api/1.0/projects/%s/repos/%s", forkProject, forkRepository), bytes.NewBuffer(bytedata)) if err != nil { return err } return nil } func handleRepositoryGitLFSChanges(client *BitbucketClient, project string, repoSlug string, d *schema.ResourceData) error { enableGitLFS := d.Get("enable_git_lfs").(bool) if (d.IsNewResource() && enableGitLFS) || d.HasChange("enable_git_lfs") { if enableGitLFS { _, err := client.Put(fmt.Sprintf("/rest/git-lfs/admin/projects/%s/repos/%s/enabled", project, repoSlug, ), nil) if err != nil { return err } } else { _, err := client.Delete(fmt.Sprintf("/rest/git-lfs/admin/projects/%s/repos/%s/enabled", project, repoSlug, )) if err != nil { return err } } } return nil } 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.(*BitbucketServerProvider).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("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) } } gifLFS, err := client.Get(fmt.Sprintf("/rest/git-lfs/admin/projects/%s/repos/%s/enabled", project, repoSlug, )) _ = d.Set("enable_git_lfs", err == nil && gifLFS.StatusCode == 200) } return nil } func resourceRepositoryExists(d *schema.ResourceData, m interface{}) (bool, error) { var project = "" var repoSlug = "" id := d.Id() if id != "" { idparts := strings.Split(id, "/") if len(idparts) == 2 { project = idparts[0] repoSlug = idparts[1] } else { return false, fmt.Errorf("incorrect ID format, should match `project/slug`") } } client := m.(*BitbucketServerProvider).BitbucketClient 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.(*BitbucketServerProvider).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 }