Skip to content

Commit

Permalink
Merge pull request #823 from jfrog/add-huggingface-repo-support
Browse files Browse the repository at this point in the history
Add hugging face repo support
  • Loading branch information
alexhung authored Oct 11, 2023
2 parents c8c984d + f53f5cb commit 8f4ac2a
Show file tree
Hide file tree
Showing 14 changed files with 283 additions and 97 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 9.5.0 (Oct 11, 2023). Tested on Artifactory 7.68.14 with Terraform CLI v1.6.1

FEATURES:

* resource/artifactory_local_huggingfaceml_repository, resource/artifactory_remote_huggingfaceml_repository: add new local and remote resources for managing Hugging Face repository. PR: [#823](https://github.com/jfrog/terraform-provider-artifactory/pull/823)

## 9.4.0 (Oct 5, 2023). Tested on Artifactory 7.68.13 with Terraform CLI v1.6.0

FEATURES:
Expand Down
33 changes: 33 additions & 0 deletions docs/resources/local_huggingfaceml_repository.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
subcategory: "Local Repositories"
---
# Artifactory Local Hugging Face Repository Resource

Creates a local Hugging Face repository.

Official documentation can be found [here](https://jfrog.com/help/r/jfrog-artifactory-documentation/set-up-local-hugging-face-repositories).

## Example Usage

```hcl
resource "artifactory_local_huggingfaceml_repository" "local-huggingfaceml-repo" {
key = "local-huggingfaceml-repo"
}
```

## Argument Reference

Arguments have a one to one mapping with the [JFrog API](https://www.jfrog.com/confluence/display/RTF/Repository+Configuration+JSON).

The following arguments are supported, along with the [common list of arguments for the local repositories](local.md):

* `key` - (Required) the identity key of the repo.
* `description` - (Optional)
* `notes` - (Optional)

## Import

Local repositories can be imported using their name, e.g.
```
$ terraform import artifactory_local_huggingfaceml_repository.local-huggingfaceml-repo local-huggingfaceml-repo
```
4 changes: 2 additions & 2 deletions docs/resources/remote.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ repository-specific arguments, listed in separate repository-specific documents.
Passwords can only be used when encryption is turned off, according to [Key Encryption instruction](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:
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 `lifecycle.ignore_changes` -- noting that this will require resources to be tainted for an update:

```hcl
lifecycle {
Expand Down Expand Up @@ -82,3 +81,4 @@ All generic repo arguments are supported, in addition to:
the artifact directly from the cloud storage provider. Available in Enterprise+ and Edge licenses only.
* `cdn_redirect` - (Optional) When set, download requests to this repository will redirect the client to download
the artifact directly from AWS CloudFront. Available in Enterprise+ and Edge licenses only.
* `disable_url_normalization` - (Optional) Whether to disable URL normalization, default is `false`.
2 changes: 0 additions & 2 deletions docs/resources/remote_docker_repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ resource "artifactory_remote_docker_repository" "my-remote-docker" {
enable_token_authentication = true
url = "https://registry-1.docker.io/"
block_pushing_schema1 = true
disable_url_normalization = true
}
```

Expand All @@ -43,7 +42,6 @@ The following arguments are supported, along with the [common list of arguments
This value `[**]` must be assigned to the attribute manually, if user don't specify any other non-default values.
We don't want to make this attribute required, but it must be set to avoid the state drift on update. Note: Artifactory assigns
`[**]` on update if HCL doesn't have the attribute set or the list is empty.
* `disable_url_normalization` - (Optional) Whether to disable URL normalization, default is `false`.

## Import

Expand Down
34 changes: 34 additions & 0 deletions docs/resources/remote_huggingfaceml_repository.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
subcategory: "Remote Repositories"
---
# Artifactory Remote Hugging Face Repository Resource

Provides a remote Hugging Face repository.

Official documentation can be found [here](https://jfrog.com/help/r/jfrog-artifactory-documentation/set-up-remote-hugging-face-repositories).

## Example Usage

```hcl
resource "artifactory_remote_huggingfaceml_repository" "huggingfaceml-remote" {
key = "huggingfaceml-remote-foo25"
}
```

## Argument Reference

Arguments have a one to one mapping with the [JFrog API](https://www.jfrog.com/confluence/display/RTF/Repository+Configuration+JSON).

The following arguments are supported, along with the [common list of arguments for the remote repositories](remote.md):

* `key` - (Required) A mandatory identifier for the repository that must be unique. It cannot begin with a number or
contain spaces or special characters.
* `description` - (Optional)
* `notes` - (Optional)

## Import

Remote repositories can be imported using their name, e.g.
```
$ terraform import artifactory_remote_huggingfaceml_repository.huggingfaceml-remote huggingfaceml-remote
```
1 change: 1 addition & 0 deletions pkg/artifactory/provider/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func resourcesMap() map[string]*schema.Resource {
"artifactory_remote_generic_repository": remote.ResourceArtifactoryRemoteGenericRepository(),
"artifactory_remote_go_repository": remote.ResourceArtifactoryRemoteGoRepository(),
"artifactory_remote_helm_repository": remote.ResourceArtifactoryRemoteHelmRepository(),
"artifactory_remote_huggingfaceml_repository": remote.ResourceArtifactoryRemoteHuggingFaceRepository(),
"artifactory_remote_maven_repository": remote.ResourceArtifactoryRemoteMavenRepository(),
"artifactory_remote_nuget_repository": remote.ResourceArtifactoryRemoteNugetRepository(),
"artifactory_remote_pypi_repository": remote.ResourceArtifactoryRemotePypiRepository(),
Expand Down
16 changes: 13 additions & 3 deletions pkg/artifactory/resource/repository/default_repo_layout_map.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,19 @@ var defaultRepoLayoutMap = map[string]SupportedRepoClasses{
"helm": {
RepoLayoutRef: "simple-default",
SupportedRepoTypes: map[string]bool{
"local": true,
"remote": true,
"virtual": true, "federated": true,
"local": true,
"remote": true,
"virtual": true,
"federated": true,
},
},
"huggingfaceml": {
RepoLayoutRef: "simple-default",
SupportedRepoTypes: map[string]bool{
"local": true,
"remote": true,
"virtual": false,
"federated": false,
},
},
"ivy": {
Expand Down
13 changes: 1 addition & 12 deletions pkg/artifactory/resource/repository/local/local.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var PackageTypesLikeGeneric = []string{
"gitlfs",
"go",
"helm",
"huggingfaceml",
"npm",
"opkg",
"pub",
Expand Down Expand Up @@ -60,18 +61,6 @@ func (bp RepositoryBaseParams) Id() string {
var BaseLocalRepoSchema = utilsdk.MergeMaps(
repository.BaseRepoSchema,
map[string]*schema.Schema{
"includes_pattern": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "List of artifact patterns to include when evaluating artifact requests in the form of x/y/**/z/*. When used, only artifacts matching one of the include patterns are served. By default, all artifacts are included (**/*).",
},
"excludes_pattern": {
Type: schema.TypeString,
Optional: true,
Computed: true,
Description: "List of artifact patterns to exclude when evaluating artifact requests, in the form of x/y/**/z/*. By default no artifacts are excluded.",
},
"blacked_out": {
Type: schema.TypeBool,
Optional: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"math/rand"
"regexp"
"strconv"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -521,6 +522,56 @@ func TestAccLocalDockerV2RepositoryWithDefaultMaxUniqueTagsGH370(t *testing.T) {
})
}

func TestAccLocalHuggingFaceMLRepository(t *testing.T) {
_, fqrn, name := testutil.MkNames("huggingfaceml-local", "artifactory_local_huggingfaceml_repository")

params := map[string]interface{}{
"name": name,
"blacked_out": testutil.RandBool(),
"xray_index": testutil.RandBool(),
"property_set": "artifactory",
"archive_browsing_enabled": testutil.RandBool(),
}
localRepositoryBasic := utilsdk.ExecuteTemplate("TestAccLocalHuggingFaceMLRepository", `
resource "artifactory_local_huggingfaceml_repository" "{{ .name }}" {
key = "{{ .name }}"
blacked_out = {{ .blacked_out }}
xray_index = {{ .xray_index }}
property_sets = ["{{ .property_set }}"]
archive_browsing_enabled = {{ .archive_browsing_enabled }}
}
`, params)

resource.Test(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(t) },
ProviderFactories: acctest.ProviderFactories,
CheckDestroy: acctest.VerifyDeleted(fqrn, acctest.CheckRepo),
Steps: []resource.TestStep{
{
Config: localRepositoryBasic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(fqrn, "key", name),
resource.TestCheckResourceAttr(fqrn, "blacked_out", strconv.FormatBool(params["blacked_out"].(bool))),
resource.TestCheckResourceAttr(fqrn, "xray_index", strconv.FormatBool(params["xray_index"].(bool))),
resource.TestCheckResourceAttr(fqrn, "property_sets.#", "1"),
resource.TestCheckResourceAttr(fqrn, "property_sets.0", params["property_set"].(string)),
resource.TestCheckResourceAttr(fqrn, "archive_browsing_enabled", strconv.FormatBool(params["archive_browsing_enabled"].(bool))),
resource.TestCheckResourceAttr(fqrn, "repo_layout_ref", func() string {
r, _ := repository.GetDefaultRepoLayoutRef("local", "huggingfaceml")()
return r.(string)
}()), //Check to ensure repository layout is set as per default even when it is not passed.
),
},
{
ResourceName: fqrn,
ImportState: true,
ImportStateVerify: true,
ImportStateCheck: validator.CheckImportState(name, "key"),
},
},
})
}

func TestAccLocalNugetRepository(t *testing.T) {
_, fqrn, name := testutil.MkNames("nuget-local", "artifactory_local_nuget_repository")
params := map[string]interface{}{
Expand Down Expand Up @@ -695,11 +746,13 @@ func TestAccLocalGenericRepository(t *testing.T) {
params := map[string]interface{}{
"name": name,
"priority_resolution": testutil.RandBool(),
"property_set": "artifactory",
}
localRepositoryBasic := utilsdk.ExecuteTemplate("TestAccLocalGenericRepository", `
resource "artifactory_local_generic_repository" "{{ .name }}" {
key = "{{ .name }}"
priority_resolution = "{{ .priority_resolution }}"
property_sets = ["{{ .property_set }}"]
}
`, params)

Expand All @@ -713,6 +766,8 @@ func TestAccLocalGenericRepository(t *testing.T) {
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(fqrn, "key", name),
resource.TestCheckResourceAttr(fqrn, "priority_resolution", fmt.Sprintf("%t", params["priority_resolution"])),
resource.TestCheckResourceAttr(fqrn, "property_sets.#", "1"),
resource.TestCheckResourceAttr(fqrn, "property_sets.0", params["property_set"].(string)),
),
},
{
Expand Down Expand Up @@ -845,36 +900,38 @@ func TestAccLocalNpmRepository(t *testing.T) {
})
}

func mkTestCase(repoType string, t *testing.T) (*testing.T, resource.TestCase) {
name := fmt.Sprintf("terraform-local-%s-%d-full", repoType, rand.Int())
resourceName := fmt.Sprintf("artifactory_local_%s_repository.%s", repoType, name)
func mkTestCase(packageType string, t *testing.T) (*testing.T, resource.TestCase) {
name := fmt.Sprintf("local-%s-%d-full", packageType, rand.Int())
resourceName := fmt.Sprintf("artifactory_local_%s_repository.%s", packageType, name)
xrayIndex := testutil.RandBool()
fqrn := fmt.Sprintf("artifactory_local_%s_repository.%s", repoType, name)
fqrn := fmt.Sprintf("artifactory_local_%s_repository.%s", packageType, name)

params := map[string]interface{}{
"repoType": repoType,
"name": name,
"xrayIndex": xrayIndex,
"cdnRedirect": false, // even when set to true, it comes back as false on the wire (presumably unless testing against a cloud platform)

"packageType": packageType,
"name": name,
"xrayIndex": xrayIndex,
"cdnRedirect": false, // even when set to true, it comes back as false on the wire (presumably unless testing against a cloud platform)
"property_set": "artifactory",
}
cfg := utilsdk.ExecuteTemplate("TestAccLocalRepository", `
resource "artifactory_local_{{ .repoType }}_repository" "{{ .name }}" {
resource "artifactory_local_{{ .packageType }}_repository" "{{ .name }}" {
key = "{{ .name }}"
description = "Test repo for {{ .name }}"
notes = "Test repo for {{ .name }}"
xray_index = {{ .xrayIndex }}
cdn_redirect = {{ .cdnRedirect }}
property_sets = ["{{ .property_set }}"]
}
`, params)

updatedCfg := utilsdk.ExecuteTemplate("TestAccLocalRepository", `
resource "artifactory_local_{{ .repoType }}_repository" "{{ .name }}" {
resource "artifactory_local_{{ .packageType }}_repository" "{{ .name }}" {
key = "{{ .name }}"
description = ""
notes = ""
xray_index = {{ .xrayIndex }}
cdn_redirect = {{ .cdnRedirect }}
property_sets = ["{{ .property_set }}"]
}
`, params)

Expand All @@ -887,22 +944,24 @@ func mkTestCase(repoType string, t *testing.T) (*testing.T, resource.TestCase) {
Config: cfg,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "key", name),
resource.TestCheckResourceAttr(resourceName, "package_type", repoType),
resource.TestCheckResourceAttr(resourceName, "package_type", packageType),
resource.TestCheckResourceAttr(resourceName, "description", fmt.Sprintf("Test repo for %s", name)),
resource.TestCheckResourceAttr(resourceName, "notes", fmt.Sprintf("Test repo for %s", name)),
resource.TestCheckResourceAttr(resourceName, "repo_layout_ref", func() string { r, _ := repository.GetDefaultRepoLayoutRef("local", repoType)(); return r.(string) }()), //Check to ensure repository layout is set as per default even when it is not passed.
resource.TestCheckResourceAttr(resourceName, "repo_layout_ref", func() string { r, _ := repository.GetDefaultRepoLayoutRef("local", packageType)(); return r.(string) }()), //Check to ensure repository layout is set as per default even when it is not passed.
resource.TestCheckResourceAttr(resourceName, "xray_index", fmt.Sprintf("%t", xrayIndex)),
resource.TestCheckResourceAttr(resourceName, "cdn_redirect", fmt.Sprintf("%t", params["cdnRedirect"])),
resource.TestCheckResourceAttr(resourceName, "property_sets.#", "1"),
resource.TestCheckResourceAttr(resourceName, "property_sets.0", params["property_set"].(string)),
),
},
{
Config: updatedCfg,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "key", name),
resource.TestCheckResourceAttr(resourceName, "package_type", repoType),
resource.TestCheckResourceAttr(resourceName, "package_type", packageType),
resource.TestCheckResourceAttr(resourceName, "description", ""),
resource.TestCheckResourceAttr(resourceName, "notes", ""),
resource.TestCheckResourceAttr(resourceName, "repo_layout_ref", func() string { r, _ := repository.GetDefaultRepoLayoutRef("local", repoType)(); return r.(string) }()), //Check to ensure repository layout is set as per default even when it is not passed.
resource.TestCheckResourceAttr(resourceName, "repo_layout_ref", func() string { r, _ := repository.GetDefaultRepoLayoutRef("local", packageType)(); return r.(string) }()), //Check to ensure repository layout is set as per default even when it is not passed.
resource.TestCheckResourceAttr(resourceName, "xray_index", fmt.Sprintf("%t", xrayIndex)),
resource.TestCheckResourceAttr(resourceName, "cdn_redirect", fmt.Sprintf("%t", params["cdnRedirect"])),
),
Expand All @@ -917,10 +976,10 @@ func mkTestCase(repoType string, t *testing.T) (*testing.T, resource.TestCase) {
}
}

func TestAccLocalAllRepoTypes(t *testing.T) {
for _, repo := range local.PackageTypesLikeGeneric {
t.Run(repo, func(t *testing.T) {
resource.Test(mkTestCase(repo, t))
func TestAccLocalAllPackageTypes(t *testing.T) {
for _, packageType := range local.PackageTypesLikeGeneric {
t.Run(packageType, func(t *testing.T) {
resource.Test(mkTestCase(packageType, t))
})
}
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/artifactory/resource/repository/remote/remote.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type RepositoryRemoteBaseParams struct {
ListRemoteFolderItems bool `json:"listRemoteFolderItems"`
DownloadRedirect bool `hcl:"download_direct" json:"downloadRedirect,omitempty"`
CdnRedirect bool `json:"cdnRedirect"`
DisableURLNormalization bool `hcl:"disable_url_normalization" json:"disableUrlNormalization"`
}

func (r RepositoryRemoteBaseParams) GetRclass() string {
Expand Down Expand Up @@ -369,6 +370,12 @@ var BaseRemoteRepoSchema = func(isResource bool) map[string]*schema.Schema {
Default: false,
Description: "When set, download requests to this repository will redirect the client to download the artifact directly from AWS CloudFront. Available in Enterprise+ and Edge licenses only. Default value is 'false'",
},
"disable_url_normalization": {
Type: schema.TypeBool,
Optional: true,
Default: false,
Description: "Whether to disable URL normalization, default is `false`.",
},
},
)
}
Expand Down Expand Up @@ -506,6 +513,7 @@ func UnpackBaseRemoteRepo(s *schema.ResourceData, packageType string) Repository
PriorityResolution: d.GetBool("priority_resolution", false),
ListRemoteFolderItems: d.GetBool("list_remote_folder_items", false),
MismatchingMimeTypeOverrideList: d.GetString("mismatching_mime_types_override_list", false),
DisableURLNormalization: d.GetBool("disable_url_normalization", false),
}
if v, ok := d.GetOk("content_synchronisation"); ok {
contentSynchronisationConfig := v.([]interface{})[0].(map[string]interface{})
Expand Down
Loading

0 comments on commit 8f4ac2a

Please sign in to comment.