Skip to content
Merged
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
13 changes: 13 additions & 0 deletions docs/resources/repository_pypi_group.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,16 @@ Required:
Optional:

- `policy_names` (Set of String) Set of Cleanup Policies that will apply to this Repository

## Import

Import is supported using the following syntax:

```shell
# Existing group pypi repository configuration can be imported as follows.
#
# NOTE: The Identifier REPOSITORY_NAME needs to match repository name in your sonatype nexus repository instance.

# Example
terraform import sonatyperepo_repository_pypi_group.example REPOSITORY_NAME
```
13 changes: 13 additions & 0 deletions docs/resources/repository_pypi_hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,16 @@ Optional:
Optional:

- `proprietary_components` (Boolean) Components in this repository count as proprietary for namespace conflict attacks (requires Sonatype Nexus Repository Firewall)

## Import

Import is supported using the following syntax:

```shell
# Existing hosted pypi repository configuration can be imported as follows.
#
# NOTE: The Identifier REPOSITORY_NAME needs to match repository name in your sonatype nexus repository instance.

# Example
terraform import sonatyperepo_repository_pypi_hosted.example REPOSITORY_NAME
```
13 changes: 13 additions & 0 deletions docs/resources/repository_pypi_proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,16 @@ Required:
Optional:

- `asset_path_regex` (String) Regular Expression of Asset Paths to pull pre-emptively pull

## Import

Import is supported using the following syntax:

```shell
# Existing proxy pypi repository configuration can be imported as follows.
#
# NOTE: The Identifier REPOSITORY_NAME needs to match repository name in your sonatype nexus repository instance.

# Example
terraform import sonatyperepo_repository_pypi_proxy.example REPOSITORY_NAME
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Existing group pypi repository configuration can be imported as follows.
#
# NOTE: The Identifier REPOSITORY_NAME needs to match repository name in your sonatype nexus repository instance.

# Example
terraform import sonatyperepo_repository_pypi_group.example REPOSITORY_NAME
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Existing hosted pypi repository configuration can be imported as follows.
#
# NOTE: The Identifier REPOSITORY_NAME needs to match repository name in your sonatype nexus repository instance.

# Example
terraform import sonatyperepo_repository_pypi_hosted.example REPOSITORY_NAME
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Existing proxy pypi repository configuration can be imported as follows.
#
# NOTE: The Identifier REPOSITORY_NAME needs to match repository name in your sonatype nexus repository instance.

# Example
terraform import sonatyperepo_repository_pypi_proxy.example REPOSITORY_NAME
24 changes: 12 additions & 12 deletions internal/provider/model/repository_pypi.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,7 @@ type RepositoryPyPiHostedModel struct {
}

func (m *RepositoryPyPiHostedModel) FromApiModel(api sonatyperepo.SimpleApiHostedRepository) {
m.Name = types.StringPointerValue(api.Name)
m.Online = types.BoolValue(api.Online)
m.Url = types.StringPointerValue(api.Url)

// Cleanup
if api.Cleanup != nil && len(api.Cleanup.PolicyNames) > 0 {
m.Cleanup = NewRepositoryCleanupModel()
mapCleanupFromApi(api.Cleanup, m.Cleanup)
}

// Storage
m.Storage.MapFromApi(&api.Storage)
m.mapSimpleApiHostedRepository(api)
}

func (m *RepositoryPyPiHostedModel) ToApiCreateModel() sonatyperepo.PypiHostedRepositoryApiRequest {
Expand Down Expand Up @@ -94,10 +83,21 @@ func (m *RepositoryPyPiProxyModel) FromApiModel(api sonatyperepo.PyPiProxyApiRep
m.HttpClient.MapFromApiHttpClientAttributes(&api.HttpClient)
m.RoutingRule = types.StringPointerValue(api.RoutingRuleName)
if api.Replication != nil {
m.Replication = &RepositoryReplicationModel{}
m.Replication.MapFromApi(api.Replication)
} else {
// Set default values when API doesn't provide replication data
m.Replication = &RepositoryReplicationModel{
PreemptivePullEnabled: types.BoolValue(common.DEFAULT_PROXY_PREEMPTIVE_PULL),
AssetPathRegex: types.StringNull(),
}
}

// PyPi
// pypi is required for PyPi proxy repositories, so always populate it
if m.PyPi == nil {
m.PyPi = &ProxyRemoveQuarrantiedModel{}
}
m.PyPi.MapFromPyPiApi(&api.Pypi)
}

Expand Down
42 changes: 30 additions & 12 deletions internal/provider/repository/format/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,32 +92,50 @@ func (f *BaseRepositoryFormat) ValidateRepositoryForImport(repositoryData any, e

// Get Format field
formatField := v.FieldByName("Format")
if !formatField.IsValid() || formatField.IsNil() {
if !formatField.IsValid() {
return fmt.Errorf(errRepositoryFormatNil, expectedFormat)
}

// Extract the string value from the *string
formatPtr := formatField.Interface().(*string)
actualFormat := strings.ToLower(*formatPtr)
expectedFormatLower := strings.ToLower(expectedFormat)
// Handle both *string and string types
var actualFormat string
if formatField.Kind() == reflect.Ptr {
if formatField.IsNil() {
return fmt.Errorf(errRepositoryFormatNil, expectedFormat)
}
formatPtr := formatField.Interface().(*string)
actualFormat = strings.ToLower(*formatPtr)
} else {
actualFormat = strings.ToLower(formatField.Interface().(string))
}

expectedFormatLower := strings.ToLower(expectedFormat)
if actualFormat != expectedFormatLower {
return fmt.Errorf(errRepositoryFormatMismatch, *formatPtr, expectedFormat)
return fmt.Errorf(errRepositoryFormatMismatch, actualFormat, expectedFormat)
}

// Get Type field
typeField := v.FieldByName("Type")
if !typeField.IsValid() || typeField.IsNil() {
if !typeField.IsValid() {
expectedTypeStr := expectedType.String()
return fmt.Errorf(errRepositoryTypeNil, expectedTypeStr)
}

// Extract the string value from the *string
typePtr := typeField.Interface().(*string)
expectedTypeStr := expectedType.String()
// Handle both *string and string types
var actualType string
if typeField.Kind() == reflect.Ptr {
if typeField.IsNil() {
expectedTypeStr := expectedType.String()
return fmt.Errorf(errRepositoryTypeNil, expectedTypeStr)
}
typePtr := typeField.Interface().(*string)
actualType = *typePtr
} else {
actualType = typeField.Interface().(string)
}

if *typePtr != expectedTypeStr {
return fmt.Errorf(errRepositoryTypeMismatch, *typePtr, expectedTypeStr)
expectedTypeStr := expectedType.String()
if actualType != expectedTypeStr {
return fmt.Errorf(errRepositoryTypeMismatch, actualType, expectedTypeStr)
}

return nil
Expand Down
57 changes: 54 additions & 3 deletions internal/provider/repository/format/pypi.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ func (f *PyPiRepositoryFormatHosted) DoReadRequest(state any, apiClient *sonatyp

// Call to API to Read
apiResponse, httpResponse, err := apiClient.RepositoryManagementAPI.GetPypiHostedRepository(ctx, stateModel.Name.ValueString()).Execute()
if err != nil {
return nil, httpResponse, err
}
return *apiResponse, httpResponse, err
}

Expand Down Expand Up @@ -111,11 +114,25 @@ func (f *PyPiRepositoryFormatHosted) UpdatePlanForState(plan any) any {
}

func (f *PyPiRepositoryFormatHosted) UpdateStateFromApi(state any, api any) any {
stateModel := (state).(model.RepositoryPyPiHostedModel)
var stateModel model.RepositoryPyPiHostedModel
// During import, state might be nil, so we create a new model
if state != nil {
stateModel = (state).(model.RepositoryPyPiHostedModel)
}
stateModel.FromApiModel((api).(sonatyperepo.SimpleApiHostedRepository))
return stateModel
}

// DoImportRequest implements the import functionality for PyPI Hosted repositories
func (f *PyPiRepositoryFormatHosted) DoImportRequest(repositoryName string, apiClient *sonatyperepo.APIClient, ctx context.Context) (any, *http.Response, error) {
// Call to API to Read repository for import
apiResponse, httpResponse, err := apiClient.RepositoryManagementAPI.GetPypiHostedRepository(ctx, repositoryName).Execute()
if err != nil {
return nil, httpResponse, err
}
return *apiResponse, httpResponse, nil
}

// --------------------------------------------
// PROXY PyPi Format Functions
// --------------------------------------------
Expand All @@ -133,6 +150,9 @@ func (f *PyPiRepositoryFormatProxy) DoReadRequest(state any, apiClient *sonatype

// Call to API to Read
apiResponse, httpResponse, err := apiClient.RepositoryManagementAPI.GetPypiProxyRepository(ctx, stateModel.Name.ValueString()).Execute()
if err != nil {
return nil, httpResponse, err
}
return *apiResponse, httpResponse, err
}

Expand Down Expand Up @@ -170,11 +190,25 @@ func (f *PyPiRepositoryFormatProxy) UpdatePlanForState(plan any) any {
}

func (f *PyPiRepositoryFormatProxy) UpdateStateFromApi(state any, api any) any {
stateModel := (state).(model.RepositoryPyPiProxyModel)
var stateModel model.RepositoryPyPiProxyModel
// During import, state might be nil, so we create a new model
if state != nil {
stateModel = (state).(model.RepositoryPyPiProxyModel)
}
stateModel.FromApiModel((api).(sonatyperepo.PyPiProxyApiRepository))
return stateModel
}

// DoImportRequest implements the import functionality for PyPI Proxy repositories
func (f *PyPiRepositoryFormatProxy) DoImportRequest(repositoryName string, apiClient *sonatyperepo.APIClient, ctx context.Context) (any, *http.Response, error) {
// Call to API to Read repository for import
apiResponse, httpResponse, err := apiClient.RepositoryManagementAPI.GetPypiProxyRepository(ctx, repositoryName).Execute()
if err != nil {
return nil, httpResponse, err
}
return *apiResponse, httpResponse, nil
}

// --------------------------------------------
// GORUP PyPi Format Functions
// --------------------------------------------
Expand All @@ -192,6 +226,9 @@ func (f *PyPiRepositoryFormatGroup) DoReadRequest(state any, apiClient *sonatype

// Call to API to Read
apiResponse, httpResponse, err := apiClient.RepositoryManagementAPI.GetPypiGroupRepository(ctx, stateModel.Name.ValueString()).Execute()
if err != nil {
return nil, httpResponse, err
}
return *apiResponse, httpResponse, err
}

Expand Down Expand Up @@ -227,11 +264,25 @@ func (f *PyPiRepositoryFormatGroup) UpdatePlanForState(plan any) any {
}

func (f *PyPiRepositoryFormatGroup) UpdateStateFromApi(state any, api any) any {
stateModel := (state).(model.RepositoryPyPiGroupModel)
var stateModel model.RepositoryPyPiGroupModel
// During import, state might be nil, so we create a new model
if state != nil {
stateModel = (state).(model.RepositoryPyPiGroupModel)
}
stateModel.FromApiModel((api).(sonatyperepo.SimpleApiGroupDeployRepository))
return stateModel
}

// DoImportRequest implements the import functionality for PyPI Group repositories
func (f *PyPiRepositoryFormatGroup) DoImportRequest(repositoryName string, apiClient *sonatyperepo.APIClient, ctx context.Context) (any, *http.Response, error) {
// Call to API to Read repository for import
apiResponse, httpResponse, err := apiClient.RepositoryManagementAPI.GetPypiGroupRepository(ctx, repositoryName).Execute()
if err != nil {
return nil, httpResponse, err
}
return *apiResponse, httpResponse, nil
}

// --------------------------------------------
// Common Functions
// --------------------------------------------
Expand Down
6 changes: 3 additions & 3 deletions internal/provider/repository/repository_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ func (r *repositoryResource) ImportState(ctx context.Context, req resource.Impor
if httpResponse != nil && httpResponse.StatusCode == http.StatusNotFound {
resp.Diagnostics.AddError(
fmt.Sprintf("Repository '%s' not found", repositoryName),
fmt.Sprintf("The %s %s repository '%s' does not exist or you do not have permission to access it.",
fmt.Sprintf("The %s %s repository '%s' does not exist or you do not have permission to access it.",
r.RepositoryFormat.GetKey(), r.RepositoryType.String(), repositoryName),
)
} else {
Expand All @@ -408,7 +408,7 @@ func (r *repositoryResource) ImportState(ctx context.Context, req resource.Impor
// UpdateStateFromApi expects an empty instance of the proper model type and returns a populated one
// Pass nil as the first parameter - UpdateStateFromApi will create the proper model type
stateModel := r.RepositoryFormat.UpdateStateFromApi(nil, apiResponse)

// Update plan for state (sets last_updated timestamp)
stateModel = r.RepositoryFormat.UpdatePlanForState(stateModel)

Expand Down Expand Up @@ -504,4 +504,4 @@ func getHostedStandardSchema(repoFormat string, repoType format.RepositoryType)
},
},
}
}
}
Loading