diff --git a/github/data_source_github_repository_milestone.go b/github/data_source_github_repository_milestone.go new file mode 100644 index 0000000000..6c517e332c --- /dev/null +++ b/github/data_source_github_repository_milestone.go @@ -0,0 +1,66 @@ +package github + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "strconv" +) + +func dataSourceGithubRepositoryMilestone() *schema.Resource { + return &schema.Resource{ + Read: dataSourceGithubRepositoryMilestoneRead, + + Schema: map[string]*schema.Schema{ + "owner": { + Type: schema.TypeString, + Required: true, + }, + "repository": { + Type: schema.TypeString, + Required: true, + }, + "number": { + Type: schema.TypeInt, + Required: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "due_date": { + Type: schema.TypeString, + Computed: true, + }, + "state": { + Type: schema.TypeString, + Computed: true, + }, + "title": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceGithubRepositoryMilestoneRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Organization).v3client + ctx := context.Background() + + owner := d.Get("owner").(string) + repoName := d.Get("repository").(string) + + number := d.Get("number").(int) + milestone, _, err := conn.Issues.GetMilestone(ctx, owner, repoName, number) + if err != nil { + return err + } + + d.SetId(strconv.FormatInt(milestone.GetID(), 10)) + d.Set("description", milestone.GetDescription()) + d.Set("due_date", milestone.GetDueOn().Format(layoutISO)) + d.Set("state", milestone.GetState()) + d.Set("title", milestone.GetTitle()) + + return nil +} diff --git a/github/data_source_github_repository_milestone_test.go b/github/data_source_github_repository_milestone_test.go new file mode 100644 index 0000000000..0938a52ae4 --- /dev/null +++ b/github/data_source_github_repository_milestone_test.go @@ -0,0 +1,92 @@ +package github + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "regexp" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccGithubRepositoryMilestoneDataSource_noMatchReturnsError(t *testing.T) { + repo := "nonExistentRepo" + owner := "no-user" + number := "1" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckGithubRepositoryMilestoneDataSourceNonExistentConfig(repo, owner, number), + ExpectError: regexp.MustCompile(`Not Found`), + }, + }, + }) +} + +func TestAccGithubRepositoryMilestoneDataSource_existing(t *testing.T) { + repo := acctest.RandomWithPrefix("tf-acc-test") + title := acctest.RandomWithPrefix("ms") + description := acctest.RandomWithPrefix("tf-acc-test-desc") + dueDate := time.Now().UTC().Format(layoutISO) + + rn := "github_repository_milestone.test" + dataSource := "data.github_repository_milestone.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { + testAccPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCheckGithubRepositoryMilestoneDataSourceConfig(repo, title, description, dueDate), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(dataSource, "title", rn, "title"), + resource.TestCheckResourceAttrPair(dataSource, "description", rn, "description"), + resource.TestCheckResourceAttrPair(dataSource, "due_date", rn, "due_date"), + resource.TestCheckResourceAttrPair(dataSource, "state", rn, "state"), + resource.TestCheckResourceAttrPair(dataSource, "number", rn, "number"), + resource.TestCheckResourceAttrPair(dataSource, "owner", rn, "owner"), + resource.TestCheckResourceAttrPair(dataSource, "repository", rn, "repository"), + ), + }, + }, + }) +} + +func testAccCheckGithubRepositoryMilestoneDataSourceNonExistentConfig(owner, repo, number string) string { + return fmt.Sprintf(` +data "github_repository_milestone" "test" { + owner = "%s" + repository = "%s" + number = "%s" +} +`, owner, repo, number) +} + +func testAccCheckGithubRepositoryMilestoneDataSourceConfig(repo, title, description, dueDate string) string { + return fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" +} + +resource "github_repository_milestone" "test" { + owner = split("/", "${github_repository.test.full_name}")[0] + repository = github_repository.test.name + title = "%s" + description = "%s" + due_date = "%s" +} + +data "github_repository_milestone" "test" { + owner = github_repository_milestone.test.owner + repository = github_repository_milestone.test.repository + number = github_repository_milestone.test.number +} +`, repo, title, description, dueDate) +} diff --git a/github/provider.go b/github/provider.go index f5d98f7f6b..1c6073ba76 100644 --- a/github/provider.go +++ b/github/provider.go @@ -53,6 +53,7 @@ func Provider() terraform.ResourceProvider { "github_repository_collaborator": resourceGithubRepositoryCollaborator(), "github_repository_deploy_key": resourceGithubRepositoryDeployKey(), "github_repository_file": resourceGithubRepositoryFile(), + "github_repository_milestone": resourceGithubRepositoryMilestone(), "github_repository_project": resourceGithubRepositoryProject(), "github_repository_webhook": resourceGithubRepositoryWebhook(), "github_repository": resourceGithubRepository(), @@ -76,6 +77,7 @@ func Provider() terraform.ResourceProvider { "github_release": dataSourceGithubRelease(), "github_repositories": dataSourceGithubRepositories(), "github_repository": dataSourceGithubRepository(), + "github_repository_milestone": dataSourceGithubRepositoryMilestone(), "github_team": dataSourceGithubTeam(), "github_user": dataSourceGithubUser(), }, diff --git a/github/resource_github_repository_milestone.go b/github/resource_github_repository_milestone.go new file mode 100644 index 0000000000..30be4bfa92 --- /dev/null +++ b/github/resource_github_repository_milestone.go @@ -0,0 +1,232 @@ +package github + +import ( + "context" + "fmt" + "github.com/google/go-github/v31/github" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" + "log" + "net/http" + "strconv" + "strings" + "time" +) + +func resourceGithubRepositoryMilestone() *schema.Resource { + return &schema.Resource{ + Create: resourceGithubRepositoryMilestoneCreate, + Read: resourceGithubRepositoryMilestoneRead, + Update: resourceGithubRepositoryMilestoneUpdate, + Delete: resourceGithubRepositoryMilestoneDelete, + Importer: &schema.ResourceImporter{ + State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), "/") + if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return nil, fmt.Errorf("Invalid ID format, must be provided as OWNER/REPOSITORY/NUMBER") + } + d.Set("owner", parts[0]) + d.Set("repository", parts[1]) + number, err := strconv.Atoi(parts[2]) + if err != nil { + return nil, err + } + d.Set("number", number) + d.SetId(fmt.Sprintf("%s/%s/%d", parts[0], parts[1], number)) + + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeString, + Required: true, + }, + "owner": { + Type: schema.TypeString, + Required: true, + }, + "repository": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "due_date": { + Type: schema.TypeString, + Optional: true, + Description: "in yyyy-mm-dd format", + }, + "state": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "open", "closed", + }, true), + Default: "open", + }, + "number": { + Type: schema.TypeInt, + Computed: true, + }, + }, + } +} + +const ( + layoutISO = "2006-01-02" +) + +func resourceGithubRepositoryMilestoneCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Organization).v3client + ctx := context.Background() + owner := d.Get("owner").(string) + repoName := d.Get("repository").(string) + + milestone := &github.Milestone{ + Title: github.String(d.Get("title").(string)), + } + + if v, ok := d.GetOk("description"); ok && len(v.(string)) > 0 { + milestone.Description = github.String(v.(string)) + } + if v, ok := d.GetOk("due_date"); ok && len(v.(string)) > 0 { + dueDate, err := time.Parse(layoutISO, v.(string)) + if err != nil { + return err + } + date := time.Date(dueDate.Year(), dueDate.Month(), dueDate.Day(), 7, 0, 0, 0, time.UTC) + milestone.DueOn = &date + } + if v, ok := d.GetOk("state"); ok && len(v.(string)) > 0 { + milestone.State = github.String(v.(string)) + } + + log.Printf("[DEBUG] Creating milestone for repository: %s/%s", owner, repoName) + milestone, _, err := conn.Issues.CreateMilestone(ctx, owner, repoName, milestone) + if err != nil { + return err + } + + d.SetId(fmt.Sprintf("%s/%s/%d", owner, repoName, milestone.GetNumber())) + + return resourceGithubRepositoryMilestoneRead(d, meta) +} + +func resourceGithubRepositoryMilestoneRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Organization).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + + owner := d.Get("owner").(string) + repoName := d.Get("repository").(string) + number, err := parseMilestoneNumber(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Reading milestone for repository: %s/%s", owner, repoName) + milestone, _, err := conn.Issues.GetMilestone(ctx, owner, repoName, number) + if err != nil { + if ghErr, ok := err.(*github.ErrorResponse); ok { + if ghErr.Response.StatusCode == http.StatusNotModified { + return nil + } + if ghErr.Response.StatusCode == http.StatusNotFound { + log.Printf("[WARN] Removing milestone for %s/%s from state because it no longer exists in GitHub", + owner, repoName) + d.SetId("") + return nil + } + } + return err + } + + d.Set("title", milestone.GetTitle()) + d.Set("description", milestone.GetDescription()) + d.Set("number", milestone.GetNumber()) + d.Set("state", milestone.GetState()) + if dueOn := milestone.GetDueOn(); !dueOn.IsZero() { + d.Set("due_date", milestone.GetDueOn().Format(layoutISO)) + } + + return nil +} + +func resourceGithubRepositoryMilestoneUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Organization).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + owner := d.Get("owner").(string) + repoName := d.Get("repository").(string) + number, err := parseMilestoneNumber(d.Id()) + if err != nil { + return err + } + + milestone := &github.Milestone{} + if d.HasChanges("title") { + _, n := d.GetChange("title") + milestone.Title = github.String(n.(string)) + } + + if d.HasChanges("description") { + _, n := d.GetChange("description") + milestone.Description = github.String(n.(string)) + } + + if d.HasChanges("due_date") { + _, n := d.GetChange("due_date") + dueDate, err := time.Parse(layoutISO, n.(string)) + if err != nil { + return err + } + date := time.Date(dueDate.Year(), dueDate.Month(), dueDate.Day(), 7, 0, 0, 0, time.UTC) + milestone.DueOn = &date + } + + if d.HasChanges("state") { + _, n := d.GetChange("state") + milestone.State = github.String(n.(string)) + } + + log.Printf("[DEBUG] Updating milestone for repository: %s/%s", owner, repoName) + _, _, err = conn.Issues.EditMilestone(ctx, owner, repoName, number, milestone) + if err != nil { + return err + } + + return resourceGithubRepositoryMilestoneRead(d, meta) +} + +func resourceGithubRepositoryMilestoneDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*Organization).v3client + ctx := context.WithValue(context.Background(), ctxId, d.Id()) + owner := d.Get("owner").(string) + repoName := d.Get("repository").(string) + number, err := parseMilestoneNumber(d.Id()) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting milestone for repository: %s/%s", owner, repoName) + _, err = conn.Issues.DeleteMilestone(ctx, owner, repoName, number) + if err != nil { + return err + } + + return nil +} + +func parseMilestoneNumber(id string) (int, error) { + parts := strings.Split(id, "/") + if len(parts) != 3 { + return -1, fmt.Errorf("ID not properly formatted: %s", id) + } + number, err := strconv.Atoi(parts[2]) + if err != nil { + return -1, err + } + return number, nil +} diff --git a/github/resource_github_repository_milestone_test.go b/github/resource_github_repository_milestone_test.go new file mode 100644 index 0000000000..f6c7d74aa1 --- /dev/null +++ b/github/resource_github_repository_milestone_test.go @@ -0,0 +1,323 @@ +package github + +import ( + "context" + "fmt" + "strings" + "testing" + "time" + + "github.com/google/go-github/v31/github" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccGithubRepositoryMilestone_basic(t *testing.T) { + var milestone github.Milestone + title := acctest.RandomWithPrefix("tf-acc-test") + repoName := acctest.RandomWithPrefix("tf-acc-test-repo") + rn := "github_repository_milestone.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryMilestoneDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryMilestoneConfig(repoName, title), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryMilestoneExists(rn, &milestone), + resource.TestCheckResourceAttr(rn, "title", title), + resource.TestCheckResourceAttr(rn, "description", ""), + resource.TestCheckResourceAttr(rn, "state", "open"), + resource.TestCheckResourceAttr(rn, "number", "1"), + ), + }, + { + ResourceName: rn, + ImportStateIdFunc: testAccGithubRepositoryMilestoneImportStateIdFunc(rn), + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccGithubRepositoryMilestone_update(t *testing.T) { + var milestone github.Milestone + title := acctest.RandomWithPrefix("tf-acc-test") + repoName := acctest.RandomWithPrefix("tf-acc-test-repo") + dueDate := time.Now().UTC().Format(layoutISO) + description := acctest.RandomWithPrefix("tf-acc-test-desc") + rn := "github_repository_milestone.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryMilestoneDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryMilestoneConfig(repoName, title), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryMilestoneExists(rn, &milestone), + resource.TestCheckResourceAttr(rn, "title", title), + resource.TestCheckResourceAttr(rn, "description", ""), + resource.TestCheckResourceAttr(rn, "state", "open"), + resource.TestCheckResourceAttr(rn, "number", "1"), + ), + }, + { + ResourceName: rn, + ImportStateIdFunc: testAccGithubRepositoryMilestoneImportStateIdFunc(rn), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGithubRepositoryMilestoneConfigUpdate(repoName, title, description, dueDate), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryMilestoneExists(rn, &milestone), + resource.TestCheckResourceAttr(rn, "title", title), + resource.TestCheckResourceAttr(rn, "description", description), + resource.TestCheckResourceAttr(rn, "due_date", dueDate), + resource.TestCheckResourceAttr(rn, "state", "open"), + resource.TestCheckResourceAttr(rn, "number", "1"), + ), + }, + }, + }) +} + +func TestAccGithubRepositoryMilestone_multiple(t *testing.T) { + var milestone1, milestone2 github.Milestone + titleOne := acctest.RandomWithPrefix("tf-acc-test-ms") + titleTwo := acctest.RandomWithPrefix("tf-acc-test-ms-two") + + repoName := acctest.RandomWithPrefix("tf-acc-test-repo") + dueDate := time.Now().UTC().Format(layoutISO) + descriptionOne := acctest.RandomWithPrefix("tf-acc-test-desc") + descriptionTwo := acctest.RandomWithPrefix("tf-acc-test-desc-two") + + rn := "github_repository_milestone.test" + rnTwo := "github_repository_milestone.test_two" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryMilestoneDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryMultipleMilestoneConfig(repoName, titleOne, descriptionOne, titleTwo, descriptionTwo, dueDate), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryMilestoneExists(rn, &milestone1), + resource.TestCheckResourceAttr(rn, "title", titleOne), + resource.TestCheckResourceAttr(rn, "description", descriptionOne), + resource.TestCheckResourceAttr(rn, "due_date", dueDate), + resource.TestCheckResourceAttr(rn, "state", "closed"), + resource.TestCheckResourceAttrSet(rn, "number"), + testAccCheckGithubRepositoryMilestoneExists(rnTwo, &milestone2), + resource.TestCheckResourceAttr(rnTwo, "title", titleTwo), + resource.TestCheckResourceAttr(rnTwo, "description", descriptionTwo), + resource.TestCheckResourceAttr(rn, "due_date", dueDate), + resource.TestCheckResourceAttr(rnTwo, "state", "open"), + resource.TestCheckResourceAttrSet(rnTwo, "number"), + ), + }, + { + ResourceName: rn, + ImportStateIdFunc: testAccGithubRepositoryMilestoneImportStateIdFunc(rn), + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: rnTwo, + ImportStateIdFunc: testAccGithubRepositoryMilestoneImportStateIdFunc(rnTwo), + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGithubRepositoryMilestoneConfigUpdate(repoName, titleOne, descriptionOne, dueDate), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryMilestoneExists(rn, &milestone1), + resource.TestCheckResourceAttr(rn, "title", titleOne), + resource.TestCheckResourceAttr(rn, "description", descriptionOne), + resource.TestCheckResourceAttr(rn, "due_date", dueDate), + resource.TestCheckResourceAttr(rn, "state", "open"), + resource.TestCheckResourceAttrSet(rn, "number"), + ), + }, + }, + }) +} + +func TestAccGithubRepositoryMilestone_disappears(t *testing.T) { + var milestone github.Milestone + + title := acctest.RandomWithPrefix("tf-acc-test") + repoName := acctest.RandomWithPrefix("tf-acc-test-repo") + rn := "github_repository_milestone.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckGithubRepositoryMilestoneDestroy, + Steps: []resource.TestStep{ + { + Config: testAccGithubRepositoryMilestoneConfig(title, repoName), + Check: resource.ComposeTestCheckFunc( + testAccCheckGithubRepositoryMilestoneExists(rn, &milestone), + testAccCheckGithubRepositoryMilestoneDisappears(rn), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckGithubRepositoryMilestoneExists(rn string, milestone *github.Milestone) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("not Found: %s", rn) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no Milestone ID is set") + } + + conn := testAccProvider.Meta().(*Organization).v3client + number, err := parseMilestoneNumber(rs.Primary.ID) + if err != nil { + return err + } + parts := strings.Split(rs.Primary.ID, "/") + owner := parts[0] + repoName := parts[1] + + m, _, err := conn.Issues.GetMilestone(context.TODO(), owner, repoName, number) + if err != nil { + return err + } + *milestone = *m + return nil + } +} + +func testAccCheckGithubRepositoryMilestoneDisappears(rn string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return fmt.Errorf("Not found: %s", rn) + } + conn := testAccProvider.Meta().(*Organization).v3client + number, err := parseMilestoneNumber(rs.Primary.ID) + if err != nil { + return err + } + parts := strings.Split(rs.Primary.ID, "/") + owner := parts[0] + repoName := parts[1] + + _, err = conn.Issues.DeleteMilestone(context.TODO(), owner, repoName, number) + return err + } +} + +func testAccCheckGithubRepositoryMilestoneDestroy(s *terraform.State) error { + conn := testAccProvider.Meta().(*Organization).v3client + + for _, rs := range s.RootModule().Resources { + if rs.Type != "github_repository_milestone" { + continue + } + + number, err := parseMilestoneNumber(rs.Primary.ID) + if err != nil { + return err + } + parts := strings.Split(rs.Primary.ID, "/") + owner := parts[0] + repoName := parts[1] + + milestone, resp, err := conn.Issues.GetMilestone(context.TODO(), owner, repoName, number) + if err != nil { + if resp.StatusCode != 404 { + return err + } + } + if milestone != nil { + return fmt.Errorf("milestone still exists") + } + + return nil + } + + return nil +} + +func testAccGithubRepositoryMilestoneImportStateIdFunc(rn string) resource.ImportStateIdFunc { + return func(s *terraform.State) (string, error) { + rs, ok := s.RootModule().Resources[rn] + if !ok { + return "", fmt.Errorf("Not found: %s", rn) + } + + return fmt.Sprintf("%s/%s/%s", rs.Primary.Attributes["owner"], rs.Primary.Attributes["repository"], rs.Primary.Attributes["number"]), nil + } +} + +func testAccGithubRepositoryMilestoneConfig(repoName, title string) string { + return fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" +} + +resource "github_repository_milestone" "test" { + owner = split("/", "${github_repository.test.full_name}")[0] + repository = github_repository.test.name + title = "%s" +} +`, repoName, title) +} + +func testAccGithubRepositoryMilestoneConfigUpdate(repoName, title, description, dueDate string) string { + return fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" +} + +resource "github_repository_milestone" "test" { + owner = split("/", "${github_repository.test.full_name}")[0] + repository = github_repository.test.name + title = "%s" + description = "%s" + due_date = "%s" +} +`, repoName, title, description, dueDate) +} + +func testAccGithubRepositoryMultipleMilestoneConfig(repoName, titleOne, descriptionOne, titleTwo, descriptionTwo, dueDate string) string { + return fmt.Sprintf(` +resource "github_repository" "test" { + name = "%s" +} + +resource "github_repository_milestone" "test" { + owner = split("/", "${github_repository.test.full_name}")[0] + repository = github_repository.test.name + title = "%s" + description = "%s" + due_date = "%s" + state = "closed" +} + +resource "github_repository_milestone" "test_two" { + owner = split("/", "${github_repository.test.full_name}")[0] + repository = github_repository.test.name + title = "%s" + description = "%s" + due_date = "%s" + state = "open" +} +`, repoName, titleOne, descriptionOne, dueDate, titleTwo, descriptionTwo, dueDate) +} diff --git a/website/docs/d/repository_milestone.html.markdown b/website/docs/d/repository_milestone.html.markdown new file mode 100644 index 0000000000..fe177f4b68 --- /dev/null +++ b/website/docs/d/repository_milestone.html.markdown @@ -0,0 +1,35 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_milestone" +description: |- + Get information on a GitHub Repository Milestone. +--- + +# github_repository_milestone + +Use this data source to retrieve information about a specific GitHub milestone in a repository. + +## Example Usage + +```hcl +data "github_repository_milestone" "example" { + owner = "example-owner" + repository = "example-repository" + number = 1 +} +``` + +## Argument Reference + + * `owner` - (Required) Owner of the repository. + + * `repository` - (Required) Name of the repository to retrieve the milestone from. + + * `number` - (Required) The number of the milestone. + +## Attributes Reference + + * `description` - Description of the milestone. + * `due_date` - The milestone due date (in ISO-8601 `yyyy-mm-dd` format). + * `state` - State of the milestone. + * `title` - Title of the milestone. diff --git a/website/docs/r/repository_milestone.html.markdown b/website/docs/r/repository_milestone.html.markdown new file mode 100644 index 0000000000..7a5cb263da --- /dev/null +++ b/website/docs/r/repository_milestone.html.markdown @@ -0,0 +1,54 @@ +--- +layout: "github" +page_title: "GitHub: github_repository_milestone" +description: |- + Provides a GitHub repository milestone resource. +--- + +# github_repository_milestone + +Provides a GitHub respository milestone resource. + +This resource allows you to create and manage milestones for a Github Repository within an organization or user account. + +## Example Usage + +```hcl +# Create a milestone for a repository +resource "github_repository_milestone" "example" { + owner = "example-owner" + repository = "example-repository" + title = "v1.1.0" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `owner` - (Required) The owner of the Github Repository. + +* `repository` - (Required) The name of the Github Repository. + +* `title` - (Required) The title of the milestone. + +* `description` - (Optional) A description of the milestone. + +* `due_date` - (Optional) The milestone due date. In `yyyy-mm-dd` format. + +* `state` - (Optional) The state of the milestone. Either `open` or `closed`. Default: `open` + + +## Attributes Reference + +The following additional attributes are exported: + +* `number` - The number of the milestone. + +## Import + +A GitHub Repository Milestone can be imported using an ID made up of `owner/repository/number`, e.g. + +``` +$ terraform import github_repository_milestone.example example-owner/example-repository/1 +``` diff --git a/website/github.erb b/website/github.erb index ec606a8feb..75d912966d 100644 --- a/website/github.erb +++ b/website/github.erb @@ -43,6 +43,9 @@
  • github_repository
  • +
  • + github_repository_milestone +
  • github_team
  • @@ -94,6 +97,9 @@
  • github_repository_file
  • +
  • + github_repository_milestone +
  • github_repository_project