Skip to content

Commit

Permalink
Merge pull request #147 from dweebo/issue-88/add-tag-immutability
Browse files Browse the repository at this point in the history
Issue 88/add tag immutability support
  • Loading branch information
wrighbr authored Oct 18, 2021
2 parents 1cc92c1 + cbaef97 commit c65b9bd
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 1 deletion.
68 changes: 68 additions & 0 deletions client/immutable_tag_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package client

import (
"log"
"strconv"
"strings"

"github.com/BESTSELLER/terraform-provider-harbor/models"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func GetImmutableTagRuleBody(d *schema.ResourceData) *models.ImmutableTagRule {
tagId := 0
if d.Id() != "" {
lastSlashIndex := strings.LastIndex(d.Id(), "/")
tagId, _ = strconv.Atoi(d.Id()[lastSlashIndex+1:])
}

tags := []models.ImmutableTagRuleTagSelectors{}
tag := models.ImmutableTagRuleTagSelectors{
Kind: "doublestar",
}

if d.Get("tag_matching").(string) != "" {
tag.Decoration = "matches"
tag.Pattern = d.Get("tag_matching").(string)
d.Set("tag_excluding", nil)
}
if d.Get("tag_excluding").(string) != "" {
tag.Decoration = "excludes"
tag.Pattern = d.Get("tag_excluding").(string)
d.Set("tag_matching", nil)
}
tags = append(tags, tag)

scopeSelectorsRepository := models.ScopeSelectors{
Repository: []models.Repository{},
}

repo := models.Repository{
Kind: "doublestar",
}

if d.Get("repo_matching").(string) != "" {
repo.Decoration = "repoMatches"
repo.Pattern = d.Get("repo_matching").(string)
d.Set("repo_excluding", nil)
}
if d.Get("repo_excluding").(string) != "" {
repo.Decoration = "repoExcludes"
repo.Pattern = d.Get("repo_excluding").(string)
d.Set("repo_matching", nil)
}
scopeSelectorsRepository.Repository = append(scopeSelectorsRepository.Repository, repo)

body := models.ImmutableTagRule{
Id: tagId,
Disabled: d.Get("disabled").(bool),
ScopeSelectors: scopeSelectorsRepository,
ImmutableTagRuleTagSelectors: tags,
Action: "immutable",
Template: "immutable_template",
}

log.Printf("[DEBUG] %+v\n ", body)

return &body
}
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The Harbor provider is used to configure an instance of Harbor. The provider nee
* [Resource: harbor_config_system](resources/config_system.md)
* [Resource: harbor_config_email](resources/config_email.md)
* [Resource: harbor_garbage_collection](resources/garbage_collection.md)
* [Resource: harbor_immutable_tag_rule](resources/immutable_tag_rule.md)
* [Resource: harbor_interrogation_services](resources/interrogation_services.md)
* [Resource: harbor_label](resources/label.md)
* [Resource: harbor_project_member_group](resources/project_member_group.md)
Expand All @@ -29,7 +30,7 @@ provider "harbor" {
## Argument Reference
The following arguments are supported:

* **url** - (Required) The url of the harbor
* **url** - (Required) The url of the harbor
* **username** - (Required) The username to be used to access harbor
* **password** - (Required) The password to be used to access harbor
* **insecure** - (Optional) Choose to igorne certificate errors
Expand Down
32 changes: 32 additions & 0 deletions docs/resources/immutable_tag_rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Resource: harbor_immutable_tag_rule

## Example Usage

```hcl
resource "harbor_project" "main" {
name = "acctest"
}
resource "harbor_immutable_tag_rule" "main" {
project_id = harbor_project.main.id
repo_matching = "**"
tag_excluding = "latest"
}
```

## Argument Reference
The following arguments are supported:

* `disabled`- (Optional) Specify if the rule is disable or not. Defaults to `false`
* `repo_matching`- (Optional) For the repositories matching.
* `repo_excluding` - (Optional) For the repositories excuding.
* `tag_matching`- (Optional) For the tag matching.
* `tag_excluding` - (Optional) For the tag excuding.
* `scope` - (Required) The project id of which you would like to apply this policy.

## Import
Harbor immutable tag rule can be imported using the `project and immutabletagrule ids` eg,

`
terraform import harbor_immutable_tag_rule.main /projects/4/immutabletagrules/25
`
18 changes: 18 additions & 0 deletions models/immutable_tag_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package models

var PathImmutableTagRules = "/immutabletagrules"

type ImmutableTagRule struct {
Id int `json:"id,omitempty"`
Disabled bool `json:"disabled"`
ScopeSelectors ScopeSelectors `json:"scope_selectors"`
ImmutableTagRuleTagSelectors []ImmutableTagRuleTagSelectors `json:"tag_selectors"`
Action string `json:"action"`
Template string `json:"template"`
}

type ImmutableTagRuleTagSelectors struct {
Kind string `json:"kind"`
Decoration string `json:"decoration"`
Pattern string `json:"pattern"`
}
1 change: 1 addition & 0 deletions provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func Provider() *schema.Provider {
"harbor_retention_policy": resourceRetention(),
"harbor_garbage_collection": resourceGC(),
"harbor_label": resourceLabel(),
"harbor_immutable_tag_rule": resourceImmutableTagRule(),
},
DataSourcesMap: map[string]*schema.Resource{
"harbor_project": dataProject(),
Expand Down
147 changes: 147 additions & 0 deletions provider/resource_immutable_tag_rule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package provider

import (
"encoding/json"
"fmt"
"log"
"strconv"
"strings"

"github.com/BESTSELLER/terraform-provider-harbor/client"
"github.com/BESTSELLER/terraform-provider-harbor/models"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceImmutableTagRule() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"project_id": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"disabled": {
Type: schema.TypeBool,
Optional: true,
Default: false,
},
"repo_matching": {
Type: schema.TypeString,
Optional: true,
// ConflictsWith: []string{".repo_excluding"},
},
"repo_excluding": {
Type: schema.TypeString,
Optional: true,
// ConflictsWith: []string{".repo_matching"},
},
"tag_matching": {
Type: schema.TypeString,
Optional: true,
// ConflictsWith: []string{".tag_excluding"},
},
"tag_excluding": {
Type: schema.TypeString,
Optional: true,
// ConflictsWith: []string{".tag_matching"},
},
},
Create: resourceImmutableTagRuleCreate,
Read: resourceImmutableTagRuleRead,
Update: resourceImmutableTagRuleUpdate,
Delete: resourceImmutableTagRuleDelete,
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},
}
}

func resourceImmutableTagRuleCreate(d *schema.ResourceData, m interface{}) error {
apiClient := m.(*client.Client)

projectid := checkProjectid(d.Get("project_id").(string))
path := projectid + models.PathImmutableTagRules

body := client.GetImmutableTagRuleBody(d)
id := ""

_, headers, err := apiClient.SendRequest("POST", path, body, 201)
if err != nil {
return err
}

id, err = client.GetID(headers)
d.SetId(id)
return resourceImmutableTagRuleRead(d, m)
}

func resourceImmutableTagRuleRead(d *schema.ResourceData, m interface{}) error {
apiClient := m.(*client.Client)

lastSlashIndex := strings.LastIndex(d.Id(), "/")
projectImmutableTagRulePath := d.Id()[0:lastSlashIndex]
tagId, err := strconv.Atoi(d.Id()[lastSlashIndex+1:])
if err != nil {
return err
}
log.Printf("[DEBUG] Path to immutable tag rules: %+v\n", projectImmutableTagRulePath)

resp, _, err := apiClient.SendRequest("GET", projectImmutableTagRulePath, nil, 200)
if err != nil {
return fmt.Errorf("Resource not found %s", projectImmutableTagRulePath)
}

var immutableTagRuleModels []models.ImmutableTagRule
err = json.Unmarshal([]byte(resp), &immutableTagRuleModels)
if err != nil {
return err
}
for _, rule := range immutableTagRuleModels {
if rule.Id == tagId {
log.Printf("[DEBUG] found tag id %d", tagId)
d.Set("disabled", rule.Disabled)
d.Set("project_id", strings.ReplaceAll(projectImmutableTagRulePath, models.PathImmutableTagRules, ""))

switch rule.ImmutableTagRuleTagSelectors[0].Decoration {
case "matches":
d.Set("tag_matching", rule.ImmutableTagRuleTagSelectors[0].Pattern)
case "excludes":
d.Set("tag_excluding", rule.ImmutableTagRuleTagSelectors[0].Pattern)
}

switch rule.ScopeSelectors.Repository[0].Decoration {
case "repoMatches":
d.Set("repo_matching", rule.ScopeSelectors.Repository[0].Pattern)
case "excludes":
d.Set("repo_excluding", rule.ScopeSelectors.Repository[0].Pattern)
}

return nil
}
}

return fmt.Errorf("Resource not found %s", d.Id())
}

func resourceImmutableTagRuleUpdate(d *schema.ResourceData, m interface{}) error {
apiClient := m.(*client.Client)
body := client.GetImmutableTagRuleBody(d)
id := d.Id()
log.Printf("[DEBUG] Update Id: %+v\n", id)
_, _, err := apiClient.SendRequest("PUT", id, body, 200)
if err != nil {
return err
}

return resourceImmutableTagRuleRead(d, m)
}

func resourceImmutableTagRuleDelete(d *schema.ResourceData, m interface{}) error {
apiClient := m.(*client.Client)

_, _, err := apiClient.SendRequest("DELETE", d.Id(), nil, 200)
if err != nil {
return err
}
return nil
}

0 comments on commit c65b9bd

Please sign in to comment.