Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion RESOURCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ The overall status of each resource or data source is captured in this document
| `github_project_column` (🚫) | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_release` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| `github_repository` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_repository_autolink_reference` | | | | | | ❓ | ❓ |
| `github_repository_autolink_reference` | ⚠️ | | | | | ❓ | ❓ |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
| `github_repository_autolink_reference` | ⚠️ ||||| | |
| `github_repository_autolink_reference` | ⚠️ ||||| | |

| `github_repository_collaborator` | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ | ❓ |
| `github_repository_collaborators` | ⚠️ | ✅ | ✅ | ✅ | ❌ | ❌ | ✅ |
| `github_repository_custom_property` | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Expand Down
5 changes: 3 additions & 2 deletions docs/resources/repository_autolink_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ resource "github_repository" "repo" {
name = "my-repo"
description = "GitHub repo managed by Terraform"

private = false
visibility = "public"
}

resource "github_repository_autolink_reference" "autolink" {
Expand All @@ -31,7 +31,7 @@ resource "github_repository_autolink_reference" "autolink" {

The following arguments are supported:

- `repository` - (Required) The repository of the autolink reference.
- `repository` - (Required) The repository name. If the repository is renamed, the autolink reference will be updated in-place rather than recreated.

- `key_prefix` - (Required) This prefix appended by a number will generate a link any time it is found in an issue, pull request, or commit.

Expand All @@ -44,6 +44,7 @@ The following arguments are supported:
The following additional attributes are exported:

- `etag` - An etag representing the autolink reference object.
- `repository_id` - The ID of the repository.

## Import

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ resource "github_repository" "repo" {
name = "my-repo"
description = "GitHub repo managed by Terraform"

private = false
visibility = "public"
}

resource "github_repository_autolink_reference" "autolink" {
Expand Down
193 changes: 129 additions & 64 deletions github/resource_github_repository_autolink_reference.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,49 @@ import (
"context"
"errors"
"fmt"
"log"
"net/http"
"regexp"
"strconv"
"strings"

"github.com/google/go-github/v89/github"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
)

func resourceGithubRepositoryAutolinkReference() *schema.Resource {
return &schema.Resource{
Create: resourceGithubRepositoryAutolinkReferenceCreate,
Read: resourceGithubRepositoryAutolinkReferenceRead,
Delete: resourceGithubRepositoryAutolinkReferenceDelete,
CreateContext: resourceGithubRepositoryAutolinkReferenceCreate,
UpdateContext: resourceGithubRepositoryAutolinkReferenceUpdate,
ReadContext: resourceGithubRepositoryAutolinkReferenceRead,
DeleteContext: resourceGithubRepositoryAutolinkReferenceDelete,

Importer: &schema.ResourceImporter{
Comment thread
deiga marked this conversation as resolved.
StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid ID specified: supplied ID must be written as <repository>/<autolink_reference_id>")
}

repository := parts[0]
id := parts[1]

// If the second part of the provided ID isn't an integer, assume that the
// caller provided the key prefix for the autolink reference, and look up
// the autolink by the key prefix.

_, err := strconv.Atoi(id)
if err != nil {
client := meta.(*Owner).v3client
owner := meta.(*Owner).name

autolink, err := getAutolinkByKeyPrefix(ctx, client, owner, repository, id)
if err != nil {
return nil, err
}

id = strconv.FormatInt(*autolink.ID, 10)
}

if err = d.Set("repository", repository); err != nil {
return nil, err
}
d.SetId(id)
return []*schema.ResourceData{d}, nil
},
StateContext: resourceGithubRepositoryAutolinkReferenceImport,
Comment thread
deiga marked this conversation as resolved.
},

SchemaVersion: 1,
CustomizeDiff: diffRepository,

SchemaVersion: 2,
Comment thread
stevehipwell marked this conversation as resolved.
StateUpgraders: []schema.StateUpgrader{
{
Type: resourceGithubRepositoryAutolinkReferenceV1().CoreConfigSchema().ImpliedType(),
Upgrade: resourceGithubRepositoryAutolinkReferenceStateUpgradeV1,
Version: 1,
},
},
Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The repository name",
Description: "The repository name. If the repository is renamed, the autolink reference will be updated in-place rather than recreated.",
},
"repository_id": {
Type: schema.TypeInt,
Computed: true,
Description: "The ID of the GitHub repository.",
},
"key_prefix": {
Type: schema.TypeString,
Expand Down Expand Up @@ -96,15 +80,15 @@ func resourceGithubRepositoryAutolinkReference() *schema.Resource {
}
}

func resourceGithubRepositoryAutolinkReferenceCreate(d *schema.ResourceData, meta any) error {
client := meta.(*Owner).v3client
func resourceGithubRepositoryAutolinkReferenceCreate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
meta, _ := m.(*Owner)
client := meta.v3client
owner := meta.name

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
keyPrefix := d.Get("key_prefix").(string)
targetURLTemplate := d.Get("target_url_template").(string)
isAlphanumeric := d.Get("is_alphanumeric").(bool)
ctx := context.Background()

opts := &github.AutolinkOptions{
KeyPrefix: &keyPrefix,
Expand All @@ -114,23 +98,35 @@ func resourceGithubRepositoryAutolinkReferenceCreate(d *schema.ResourceData, met

autolinkRef, _, err := client.Repositories.AddAutolink(ctx, owner, repoName, opts)
if err != nil {
return err
return diag.FromErr(err)
}
d.SetId(strconv.FormatInt(autolinkRef.GetID(), 10))

return resourceGithubRepositoryAutolinkReferenceRead(d, meta)
repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return diag.FromErr(err)
}

if err := d.Set("repository_id", int(repo.GetID())); err != nil {
return diag.FromErr(err)
}

return resourceGithubRepositoryAutolinkReferenceRead(ctx, d, m)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to remove this call and just set the etag.

}

func resourceGithubRepositoryAutolinkReferenceRead(d *schema.ResourceData, meta any) error {
client := meta.(*Owner).v3client
func resourceGithubRepositoryAutolinkReferenceRead(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
ctx = tflog.SetField(ctx, "id", d.Id())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's keep with the current pattern where we only set attributes when we need them. And as a rule of thumb id shouldn't be set unless there is an unexpected error, we want the field named in a friendly way (e.g. autolink_id).


meta, _ := m.(*Owner)
client := meta.v3client
owner := meta.name

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
autolinkRefID, err := strconv.ParseInt(d.Id(), 10, 64)
if err != nil {
return unconvertibleIdErr(d.Id(), err)
return diag.FromErr(unconvertibleIdErr(d.Id(), err))
}
Comment on lines 125 to 128

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not add a new idStringToInt64OK function with the signature func(string) (bool, diag.Diagnostics) (similar to the checkOrganizationOK I'm adding in #3537)?

ctx := context.WithValue(context.Background(), ctxId, d.Id())

if !d.IsNewResource() {
ctx = context.WithValue(ctx, ctxEtag, d.Get("etag").(string))
}
Comment on lines 130 to 132

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be removed as create shouldn't be calling through.

Expand All @@ -140,44 +136,113 @@ func resourceGithubRepositoryAutolinkReferenceRead(d *schema.ResourceData, meta
var ghErr *github.ErrorResponse
if errors.As(err, &ghErr) {
if ghErr.Response.StatusCode == http.StatusNotFound {
Comment on lines 136 to 138

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please could we use the documented errors.AsType here?

log.Printf("[INFO] Removing autolink reference for repository %s/%s from state because it no longer exists in GitHub",
owner, repoName)
tflog.Info(ctx, "Autolink reference not found, removing from state.", map[string]any{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this a single line.

"owner": owner,
"repository": repoName,
})
d.SetId("")
return nil
}
}
return err
return diag.FromErr(err)
}

// Set resource fields
d.SetId(strconv.FormatInt(autolinkRef.GetID(), 10))
if err = d.Set("repository", repoName); err != nil {
return err
return diag.FromErr(err)
}

repo, _, err := client.Repositories.Get(ctx, owner, repoName)
if err != nil {
return diag.FromErr(err)
}
if err = d.Set("repository_id", int(repo.GetID())); err != nil {
return diag.FromErr(err)
}
Comment on lines +155 to 161

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't needed here.


if err = d.Set("key_prefix", autolinkRef.KeyPrefix); err != nil {
return err
return diag.FromErr(err)
}
if err = d.Set("target_url_template", autolinkRef.URLTemplate); err != nil {
return err
return diag.FromErr(err)
}
if err = d.Set("is_alphanumeric", autolinkRef.IsAlphanumeric); err != nil {
return err
return diag.FromErr(err)
}

return nil
}

func resourceGithubRepositoryAutolinkReferenceDelete(d *schema.ResourceData, meta any) error {
client := meta.(*Owner).v3client
func resourceGithubRepositoryAutolinkReferenceUpdate(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
tflog.Warn(ctx, "Update function of autolink reference. This should not be called. But it's necessary when 'repository' doesn't have `ForceNew`", map[string]any{

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this a single line?

"repository": d.Get("repository"),
"repository_id": d.Get("repository_id"),
"id": d.Id(),
})
Comment on lines +177 to +181

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make this a single line.

return nil
}

func resourceGithubRepositoryAutolinkReferenceDelete(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics {
ctx = tflog.SetField(ctx, "id", d.Id())

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.


meta, _ := m.(*Owner)
client := meta.v3client
owner := meta.name

owner := meta.(*Owner).name
repoName := d.Get("repository").(string)
autolinkRefID, err := strconv.ParseInt(d.Id(), 10, 64)
if err != nil {
return unconvertibleIdErr(d.Id(), err)
return diag.FromErr(unconvertibleIdErr(d.Id(), err))
}
ctx := context.WithValue(context.Background(), ctxId, d.Id())

_, err = client.Repositories.DeleteAutolink(ctx, owner, repoName, autolinkRefID)
return err
if err != nil {
return diag.FromErr(err)
}

return nil
}

func resourceGithubRepositoryAutolinkReferenceImport(ctx context.Context, d *schema.ResourceData, m any) ([]*schema.ResourceData, error) {
parts := strings.Split(d.Id(), "/")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should use the : separator. AFAIK we've changed some of these without a major version.

if len(parts) != 2 {
return nil, fmt.Errorf("invalid ID specified: supplied ID must be written as <repository>/<autolink_reference_id>")
}
Comment on lines +207 to +210

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ID should be migrated as part of the schema migration so it can use the ID functions.


repository := parts[0]
id := parts[1]

meta, _ := m.(*Owner)
client := meta.v3client
owner := meta.name

// If the second part of the provided ID isn't an integer, assume that the
// caller provided the key prefix for the autolink reference, and look up
// the autolink by the key prefix.

_, err := strconv.Atoi(id)
if err != nil {
autolink, err := getAutolinkByKeyPrefix(ctx, client, owner, repository, id)
if err != nil {
return nil, err
}

id = strconv.FormatInt(*autolink.ID, 10)
}
Comment on lines +223 to +231

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
_, err := strconv.Atoi(id)
if err != nil {
autolink, err := getAutolinkByKeyPrefix(ctx, client, owner, repository, id)
if err != nil {
return nil, err
}
id = strconv.FormatInt(*autolink.ID, 10)
}
if _, err := strconv.Atoi(id); err != nil {
autolink, err := getAutolinkByKeyPrefix(ctx, client, owner, repository, id)
if err != nil {
return nil, err
}
id = strconv.FormatInt(*autolink.ID, 10)
}


d.SetId(id)

repo, _, err := client.Repositories.Get(ctx, owner, repository)
if err != nil {
return nil, fmt.Errorf("failed to retrieve repository %s: %w", repository, err)
}

if err = d.Set("repository", repository); err != nil {
return nil, err
}
if err = d.Set("repository_id", int(repo.GetID())); err != nil {
return nil, err
}

return []*schema.ResourceData{d}, nil

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to set all of the fields as they're force new.

}
70 changes: 70 additions & 0 deletions github/resource_github_repository_autolink_reference_migration.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package github

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceGithubRepositoryAutolinkReferenceV1() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"repository": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"key_prefix": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"target_url_template": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"is_alphanumeric": {
Type: schema.TypeBool,
Optional: true,
ForceNew: true,
Default: true,
},
"etag": {
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceGithubRepositoryAutolinkReferenceStateUpgradeV1(ctx context.Context, rawState map[string]any, m any) (map[string]any, error) {
tflog.Debug(ctx, "GitHub Repository Autolink Reference state before v1 migration", rawState)

meta, _ := m.(*Owner)
client := meta.v3client
owner := meta.name

repoName := ""
if v, ok := rawState["repository"]; ok {
if s, ok := v.(string); ok && s != "" {
repoName = s
}
}
if repoName == "" {
return nil, fmt.Errorf("state upgrade v1: repository is not a string or not set")
}

repo, _, err := client.Repositories.Get(ctx, owner, repoName)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use the migrateRepositoryWithID function to avoid repeating the code.

if err != nil {
return nil, fmt.Errorf("state upgrade v1: failed to retrieve repository '%s/%s': %w", owner, repoName, err)
}

rawState["repository_id"] = int(repo.GetID())

tflog.Debug(ctx, "GitHub Repository Autolink Reference state after v1 migration", rawState)

return rawState, nil
}
Loading
Loading