Skip to content

Commit

Permalink
Merge pull request #216 from jamestoyer/feature/new-replication-resou…
Browse files Browse the repository at this point in the history
…rces

Create new replication resources
  • Loading branch information
alexhung authored Nov 18, 2021
2 parents 2b3b43c + 315b64d commit a4480d7
Show file tree
Hide file tree
Showing 10 changed files with 770 additions and 63 deletions.
49 changes: 49 additions & 0 deletions docs/resources/artifactory_pull_replication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Artifactory Pull Replication Resource

Provides an Artifactory pull replication resource. This can be used to create and manage pull replication in Artifactory
for a remote repo.

## Example Usage

```hcl
# Create a replication between two artifactory local repositories
resource "artifactory_local_repository" "provider_test_source" {
key = "provider_test_source"
package_type = "maven"
}
resource "artifactory_remote_repository" "provider_test_dest" {
key = "provider_test_dest"
package_type = "maven"
url = "https://example.com/artifactory/${artifactory_local_repository.artifactory_local_repository.key}"
username = "foo"
password = "bar"
}
resource "artifactory_pull_replication" "foo-rep" {
repo_key = "${artifactory_remote_repository.provider_test_dest.key}"
cron_exp = "0 0 * * * ?"
enable_event_replication = true
}
```

## Argument Reference

The following arguments are supported:

* `repo_key` - (Required)
* `cron_exp` - (Required)
* `enable_event_replication` - (Optional)
* `enabled` - (Optional)
* `sync_deletes` - (Optional)
* `sync_properties` - (Optional)
* `sync_statistics` - (Optional)
* `path_prefix` - (Optional)

## Import

Pull replication config can be imported using its repo key, e.g.

```
$ terraform import artifactory_pull_replication.foo-rep repository-key
```
68 changes: 68 additions & 0 deletions docs/resources/artifactory_push_replication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Artifactory Push Replication Resource

Provides an Artifactory push replication resource. This can be used to create and manage Artifactory push replications.

### Passwords
Passwords can only be used when encryption is turned off (https://www.jfrog.com/confluence/display/RTF/Artifactory+Key+Encryption).
Since only the artifactory server can decrypt them it is impossible for terraform to diff changes correctly.

To get full management, passwords can be decrypted globally using `POST /api/system/decrypt`. If this is not possible,
the password diff can be disabled per resource with-- noting that this will require resources to be tainted for an update:
```hcl
lifecycle {
ignore_changes = ["password"]
}
```

## Example Usage

```hcl
# Create a replication between two artifactory local repositories
resource "artifactory_local_repository" "provider_test_source" {
key = "provider_test_source"
package_type = "maven"
}
resource "artifactory_local_repository" "provider_test_dest" {
key = "provider_test_dest"
package_type = "maven"
}
resource "artifactory_push_replication" "foo-rep" {
repo_key = "${artifactory_local_repository.provider_test_source.key}"
cron_exp = "0 0 * * * ?"
enable_event_replication = true
replications {
url = "$var.artifactory_url"
username = "$var.artifactory_username"
password = "$var.artifactory_password"
}
}
```

## Argument Reference

The following arguments are supported:

* `repo_key` - (Required)
* `cron_exp` - (Required)
* `enable_event_replication` - (Optional)
* `replications` - (Optional)
* `url` - (Required)
* `socket_timeout_millis` - (Optional)
* `username` - (Optional)
* `password` - (Optional) Requires password encryption to be turned off `POST /api/system/decrypt`
* `enabled` - (Optional)
* `sync_deletes` - (Optional)
* `sync_properties` - (Optional)
* `sync_statistics` - (Optional)
* `path_prefix` - (Optional)

## Import

Push replication configs can be imported using their repo key, e.g.

```
$ terraform import artifactory_push_replication.foo-rep provider_test_source
```
8 changes: 5 additions & 3 deletions pkg/artifactory/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,16 +101,18 @@ func Provider() *schema.Provider {
"artifactory_group": resourceArtifactoryGroup(),
"artifactory_user": resourceArtifactoryUser(),
"artifactory_permission_target": resourceArtifactoryPermissionTarget(),
"artifactory_replication_config": resourceArtifactoryReplicationConfig(),
"artifactory_single_replication_config": resourceArtifactorySingleReplicationConfig(),
"artifactory_pull_replication": resourceArtifactoryPullReplication(),
"artifactory_push_replication": resourceArtifactoryPushReplication(),
"artifactory_certificate": resourceArtifactoryCertificate(),
"artifactory_api_key": resourceArtifactoryApiKey(),
"artifactory_access_token": resourceArtifactoryAccessToken(),
"artifactory_general_security": resourceArtifactoryGeneralSecurity(),
"artifactory_oauth_settings": resourceArtifactoryOauthSettings(),
"artifactory_saml_settings": resourceArtifactorySamlSettings(),
// Deprecated. Remove in V3
"artifactory_permission_targets": resourceArtifactoryPermissionTargets(),
"artifactory_permission_targets": resourceArtifactoryPermissionTargets(),
"artifactory_replication_config": resourceArtifactoryReplicationConfig(),
"artifactory_single_replication_config": resourceArtifactorySingleReplicationConfig(),
// Xray resources
"artifactory_xray_policy": resourceXrayPolicy(),
"artifactory_xray_watch": resourceXrayWatch(),
Expand Down
115 changes: 115 additions & 0 deletions pkg/artifactory/resource_artifactory_pull_replication.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package artifactory

import (
"context"
"encoding/json"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"

"github.com/go-resty/resty/v2"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/jfrog/jfrog-client-go/artifactory/services/utils"
)

func resourceArtifactoryPullReplication() *schema.Resource {
return &schema.Resource{
CreateContext: resourcePullReplicationCreate,
ReadContext: resourcePullReplicationRead,
UpdateContext: resourcePullReplicationUpdate,
DeleteContext: resourceReplicationDelete,

Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
},

Schema: mergeSchema(replicationSchemaCommon, replicationSchema),
Description: "Used for configuring pull replication on remote repos.",
}
}

func unpackPullReplication(s *schema.ResourceData) *utils.ReplicationBody {
d := &ResourceData{s}
replicationConfig := new(utils.ReplicationBody)

replicationConfig.RepoKey = d.getString("repo_key", false)
replicationConfig.CronExp = d.getString("cron_exp", false)
replicationConfig.EnableEventReplication = d.getBool("enable_event_replication", false)
replicationConfig.Enabled = d.getBool("enabled", false)
replicationConfig.SyncDeletes = d.getBool("sync_deletes", false)
replicationConfig.SyncProperties = d.getBool("sync_properties", false)
replicationConfig.SyncStatistics = d.getBool("sync_statistics", false)
replicationConfig.PathPrefix = d.getString("path_prefix", false)

return replicationConfig
}

func packPullReplicationBody(config PullReplication, d *schema.ResourceData) diag.Diagnostics {
setValue := mkLens(d)

setValue("repo_key", config.RepoKey)
setValue("cron_exp", config.CronExp)
setValue("enable_event_replication", config.EnableEventReplication)

setValue("enabled", config.Enabled)
setValue("sync_deletes", config.SyncDeletes)
setValue("sync_properties", config.SyncProperties)

errors := setValue("path_prefix", config.PathPrefix)

if errors != nil && len(errors) > 0 {
return diag.Errorf("failed to pack replication config %q", errors)
}

return nil
}
func resourcePullReplicationCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
replicationConfig := unpackPullReplication(d)
// The password is sent clear
_, err := m.(*resty.Client).R().SetBody(replicationConfig).Put(replicationEndpoint + replicationConfig.RepoKey)
if err != nil {
return diag.FromErr(err)
}

d.SetId(replicationConfig.RepoKey)
return resourcePullReplicationRead(ctx, d, m)
}

// PullReplication this is the structure for a PULL replication on a remote repo
type PullReplication struct {
Enabled bool `json:"enabled"`
CronExp string `json:"cronExp"`
SyncDeletes bool `json:"syncDeletes"`
SyncProperties bool `json:"syncProperties"`
PathPrefix string `json:"pathPrefix"`
RepoKey string `json:"repoKey"`
ReplicationKey string `json:"replicationKey"`
EnableEventReplication bool `json:"enableEventReplication"`
}

func resourcePullReplicationRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
var result interface{}

resp, err := m.(*resty.Client).R().SetResult(&result).Get(replicationEndpoint + d.Id())
// password comes back scrambled
if err != nil {
return diag.FromErr(err)
}

final := PullReplication{}
err = json.Unmarshal(resp.Body(), &final)
if err != nil {
return diag.FromErr(err)
}
return packPullReplicationBody(final, d)
}

func resourcePullReplicationUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
replicationConfig := unpackPullReplication(d)
_, err := m.(*resty.Client).R().SetBody(replicationConfig).Post(replicationEndpoint + replicationConfig.RepoKey)
if err != nil {
return diag.FromErr(err)
}

d.SetId(replicationConfig.RepoKey)

return resourcePullReplicationRead(ctx, d, m)
}
131 changes: 131 additions & 0 deletions pkg/artifactory/resource_artifactory_pull_replication_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package artifactory

import (
"fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"os"
"regexp"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func mkTclForPullRepConfg(name, cron, url string) string {
const tcl = `
resource "artifactory_local_repository" "%s" {
key = "%s"
package_type = "maven"
}
resource "artifactory_pull_replication" "%s" {
repo_key = "${artifactory_local_repository.%s.key}"
cron_exp = "%s"
enable_event_replication = true
}
`
return fmt.Sprintf(tcl,
name,
name,
name,
name,
cron,
)
}
func TestInvalidCronPullReplication(t *testing.T) {

_, fqrn, name := mkNames("lib-local", "artifactory_pull_replication")
var failCron = mkTclForPullRepConfg(name, "0 0 * * * !!", os.Getenv("ARTIFACTORY_URL"))

resource.Test(t, resource.TestCase{
CheckDestroy: testAccCheckReplicationDestroy(fqrn),
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviders,
Steps: []resource.TestStep{
{
Config: failCron,
ExpectError: regexp.MustCompile(`.*syntax error in year field: '!!'.*`),
},
},
})
}

func TestAccPullReplication_full(t *testing.T) {
_, fqrn, name := mkNames("lib-local", "artifactory_pull_replication")
config := mkTclForPullRepConfg(name, "0 0 * * * ?", os.Getenv("ARTIFACTORY_URL"))
resource.Test(t, resource.TestCase{
CheckDestroy: testAccCheckReplicationDestroy(fqrn),
ProviderFactories: testAccProviders,

Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(fqrn, "repo_key", name),
resource.TestCheckResourceAttr(fqrn, "cron_exp", "0 0 * * * ?"),
resource.TestCheckResourceAttr(fqrn, "enable_event_replication", "true"),
),
},
},
})
}

func compositeCheckDestroy(funcs ...func(state *terraform.State) error) func(state *terraform.State) error {
return func(state *terraform.State) error {
var errors []error
for _, f := range funcs {
err := f(state)
if err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
return fmt.Errorf("%q", errors)
}
return nil
}
}
func TestAccPullReplicationRemoteRepo(t *testing.T) {
_, fqrn, name := mkNames("lib-remote", "artifactory_pull_replication")
_, fqrepoName, repo_name := mkNames("lib-remote", "artifactory_remote_repository")
var tcl = `
resource "artifactory_remote_repository" "{{ .remote_name }}" {
key = "{{ .remote_name }}"
package_type = "maven"
url = "https://repo1.maven.org/maven2/"
repo_layout_ref = "maven-2-default"
}
resource "artifactory_pull_replication" "{{ .repoconfig_name }}" {
repo_key = "{{ .remote_name }}"
cron_exp = "0 0 12 ? * MON *"
enable_event_replication = false
depends_on = [artifactory_remote_repository.{{ .remote_name }}]
}
`
tcl = executeTemplate("foo", tcl, map[string]string{
"repoconfig_name": name,
"remote_name": repo_name,
})
resource.Test(t, resource.TestCase{
CheckDestroy: compositeCheckDestroy(
verifyDeleted(fqrepoName, testCheckRepo),
testAccCheckReplicationDestroy(fqrn),
),

ProviderFactories: testAccProviders,

Steps: []resource.TestStep{
{
Config: tcl,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(fqrn, "repo_key", repo_name),
resource.TestCheckResourceAttr(fqrn, "cron_exp", "0 0 12 ? * MON *"),
resource.TestCheckResourceAttr(fqrn, "enable_event_replication", "false"),
resource.TestCheckResourceAttr(fqrn, "enabled", "false"),
resource.TestCheckResourceAttr(fqrn, "sync_deletes", "false"),
resource.TestCheckResourceAttr(fqrn, "sync_properties", "false"),
),
},
},
})
}
Loading

0 comments on commit a4480d7

Please sign in to comment.