-
Notifications
You must be signed in to change notification settings - Fork 109
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #216 from jamestoyer/feature/new-replication-resou…
…rces Create new replication resources
- Loading branch information
Showing
10 changed files
with
770 additions
and
63 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
pkg/artifactory/resource_artifactory_pull_replication.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
131
pkg/artifactory/resource_artifactory_pull_replication_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} |
Oops, something went wrong.