Skip to content

Commit

Permalink
Add tests and remove resorce when the plan does not specify resource
Browse files Browse the repository at this point in the history
Signed-off-by: gatici <[email protected]>
  • Loading branch information
gatici committed Jul 2, 2024
1 parent 5501077 commit 89f16d6
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 66 deletions.
49 changes: 39 additions & 10 deletions docs/resources/application.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,24 @@ resource "juju_application" "placement_example" {
external-hostname = "..."
}
}
resource "juju_application" "custom_resources_example" {
name = "placement-example"
model = juju_model.development.name
charm {
name = "hello-kubecon"
channel = "edge"
revision = 14
series = "trusty"
}
resources = {
gosherve-image = "gatici/gosherve:1.0"
}
units = 3
placement = "0,1,2"
}
```

<!-- schema generated by tfplugindocs -->
Expand All @@ -68,16 +86,27 @@ resource "juju_application" "placement_example" {
- `name` (String) A custom name for the application deployment. If empty, uses the charm's name.
- `placement` (String) Specify the target location for the application's units
- `resources` (Map of String) Charm resource revisions. Must evaluate to a string. A resource could be a resource revision number or a custom resource.

There are a few scenarios that need to be considered:
* If the plan does not specify resource revision and resources are added to the plan,
resources with specified revisions will be attached to the application (equivalent
to juju attach-resource).
* If the plan does specify resource revisions and:
* If the charm revision or channel is updated, then resources get updated to the
latest revision.
* If the charm revision or channel are not updated, then no changes will take
place (juju does not have an "un-attach" command for resources).
There are a few scenarios that need to be considered:
* If the plan does not specify a resource and resources are added to the plan (as a revision number or a custom resource), specified resources are attached to the application (equivalent to juju attach-resource).
* If the plan does specify resource revisions and if the charm revision or channel is updated, existing resources are kept. (Resources are not detached)
* If the plan does specify resource revisions and resources are removed from the plan:
- If charm revision/channel is updated, the resources associated with the updated charm revision or channel is attached.
- If the charm revision/channel are not updated then the resources associated with the existing charm revision/channel are attached.
* Charm could be deployed without resource, then resource could be added later.
* Resources could be provided in the following formats:
- A custom repository URL
- A files ends with .txt, .json or .yaml
- A resource revision from CharmHub
* Charm could be deployed with resources.
* If the provided resource revision does not exist during initial deployment or update, Client does not start deployment with an error that resource was not found in the store.
* If the provided custom resource does not exist during initial deployment or update, Client start deployment and charm could not be deployed properly and charm will be in error state.
* If the Provided resource type is not correct then Client fails with incorrect resource error for below scenarios:
- An image is expected but file does not include image URL
- A plain text file is expected but a .json file is provided.
* If the provided resource does not exist then Client fails with path is not valid error.
* Changing resources from a file to an int or string does not create any issue. Resources are processed smoothly.
- If provided resource value is changed from an int to a file then the image resource from the file is attached.
- If provided resource value is changed from a file to an int then image revision is attached.
- `trust` (Boolean) Set the trust for the application.
- `units` (Number) The number of application units to deploy for the charm.

Expand Down
18 changes: 18 additions & 0 deletions examples/resources/juju_application/resource.tf
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,22 @@ resource "juju_application" "placement_example" {
config = {
external-hostname = "..."
}
}

resource "juju_application" "custom_resources_example" {
name = "placement-example"
model = juju_model.development.name
charm {
name = "hello-kubecon"
channel = "edge"
revision = 14
series = "trusty"
}

resources = {
gosherve-image = "gatici/gosherve:1.0"
}

units = 3
placement = "0,1,2"
}
28 changes: 28 additions & 0 deletions go.sum

Large diffs are not rendered by default.

39 changes: 21 additions & 18 deletions internal/juju/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,7 @@ const (
ContentTypeRaw = "application/octet-stream"
)
const (
// MediaTypeFormData is the media type for file uploads (see
// mime.FormatMediaType).
// MediaTypeFormData is the media type for file uploads (see mime.FormatMediaType).
MediaTypeFormData = "form-data"
// QueryParamPendingID is the query parameter we use to send up the pending id.
QueryParamPendingID = "pendingid"
Expand All @@ -76,14 +75,12 @@ const (
// HeaderContentLength is the header name for the length of a file upload.
HeaderContentLength = "Content-Length"
// HeaderContentDisposition is the header name for value that holds the filename.
// The params are formatted according to RFC 2045 and RFC 2616 (see
// mime.ParseMediaType and mime.FormatMediaType).
// See mime.ParseMediaType and mime.FormatMediaType.
HeaderContentDisposition = "Content-Disposition"
)

const (
// HTTPEndpointPath is the URL path, with substitutions, for
// a resource request.
// HTTPEndpointPath is the URL path, with substitutions, for a resource request.
HTTPEndpointPath = "/applications/%s/resources/%s"
)

Expand Down Expand Up @@ -115,8 +112,7 @@ type UploadRequest struct {

type HttpRequestClient struct {
base.ClientFacade
facade base.FacadeCaller

facade base.FacadeCaller
httpClient jujuhttp.HTTPDoer
}

Expand All @@ -130,7 +126,7 @@ type applicationNotFoundError struct {
var ApplicationNotFoundError = &applicationNotFoundError{}

// newEndpointPath returns the API URL path for the identified resource.
func newEndpointPath(application, name string) string {
func newEndpointPath(application string, name string) string {
return fmt.Sprintf(HTTPEndpointPath, application, name)
}

Expand Down Expand Up @@ -407,7 +403,7 @@ func (osFilesystem) Stat(name string) (os.FileInfo, error) {
return os.Stat(name)
}

// Checks if strings consists from digits
// isInt checks if strings consists from digits
// Used to detect resources which are given with revision number
func isInt(s string) bool {
for _, c := range s {
Expand Down Expand Up @@ -486,10 +482,13 @@ func (c applicationsClient) deployFromRepository(applicationAPIClient *apiapplic
Trust: transformedInput.trust,
Resources: transformedInput.resources,
})

if len(errs) != 0 {
return errors.Join(errs...)
}

fileSystem := osFilesystem{}
// Upload the provided local resources to Juju
uploadErr := uploadExistingPendingResources(deployInfo.Name, localPendingResources, fileSystem, resourceHttpClient)

if uploadErr != nil {
Expand All @@ -499,7 +498,7 @@ func (c applicationsClient) deployFromRepository(applicationAPIClient *apiapplic
}

// TODO (hml) 23-Feb-2024
// Remove the funcationality associated with legacyDeploy
// Remove the functionality associated with legacyDeploy
// once the provider no longer supports a version of juju
// before 3.3.
func (c applicationsClient) legacyDeploy(ctx context.Context, conn api.Connection, applicationAPIClient *apiapplication.Client, transformedInput transformedCreateApplicationInput) error {
Expand Down Expand Up @@ -1415,7 +1414,7 @@ func (c applicationsClient) computeSetCharmConfig(
Origin: resultOrigin,
}

resourceIDs, err := c.updateResources(input.AppName, input.Resources, charmsAPIClient, apiCharmID, resourcesAPIClient, resourceHttpClient)
resourceIDs, err := c.updateResources(input.AppName, input.Resources, charmsAPIClient, apiCharmID, resourcesAPIClient)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -1449,7 +1448,7 @@ func strPtr(in string) *string {
}

func (c applicationsClient) updateResources(appName string, resources map[string]string, charmsAPIClient *apicharms.Client,
charmID apiapplication.CharmID, resourcesAPIClient ResourceAPIClient, resourceHttpClient *HttpRequestClient) (map[string]string, error) {
charmID apiapplication.CharmID, resourcesAPIClient ResourceAPIClient) (map[string]string, error) {
meta, err := utils.GetMetaResources(charmID.URL, charmsAPIClient)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1487,16 +1486,20 @@ func addPendingResources(appName string, resourcesToBeAdded map[string]charmreso
if err != nil {
return nil, typedError(err)
}
aux := charmresources.Resource{
resourceFromCharmhub := charmresources.Resource{
Meta: resourceMeta,
Origin: charmresources.OriginStore,
Revision: -1,
}
aux.Revision = providedRev
pendingResourcesforAdd = append(pendingResourcesforAdd, aux)
// If the resource is removed, revision does not exist
// Charm is deployed with default resources according to charm revision or channel
if providedRev != 0 {
resourceFromCharmhub.Revision = providedRev
}
pendingResourcesforAdd = append(pendingResourcesforAdd, resourceFromCharmhub)
} else {
// A new resource to be uploaded by the client
uux := charmresources.Resource{
localResource := charmresources.Resource{
Meta: resourceMeta,
Origin: charmresources.OriginUpload,
}
Expand All @@ -1510,7 +1513,7 @@ func addPendingResources(appName string, resourcesToBeAdded map[string]charmreso
if openResErr != nil {
return nil, typedError(openResErr)
}
toRequestUpload, err := resourcesAPIClient.UploadPendingResource(appName, uux, deployValue, r)
toRequestUpload, err := resourcesAPIClient.UploadPendingResource(appName, localResource, deployValue, r)
if err != nil {
return nil, typedError(err)
}
Expand Down
17 changes: 17 additions & 0 deletions internal/juju/applications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/juju/names/v4"
"github.com/juju/utils/v3"
"github.com/juju/version/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
"go.uber.org/mock/gomock"
)
Expand Down Expand Up @@ -303,3 +304,19 @@ func (s *ApplicationSuite) TestReadApplicationRetrySubordinate() {
func TestApplicationSuite(t *testing.T) {
suite.Run(t, new(ApplicationSuite))
}

func TestNewEndpointPath(t *testing.T) {
application := "ausf"
name := "sdcore-ausf-k8s"
want := "/applications/ausf/resources/sdcore-ausf-k8s"
got := newEndpointPath(application, name)
assert.Equal(t, got, want)
}

func TestNewEndpointPathEmptyInputs(t *testing.T) {
application := ""
name := ""
want := "/applications//resources/"
got := newEndpointPath(application, name)
assert.Equal(t, got, want)
}
2 changes: 1 addition & 1 deletion internal/juju/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@

package juju_test

//go:generate go run go.uber.org/mock/mockgen -package juju -destination mock_test.go github.com/juju/terraform-provider-juju/internal/juju SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient,SecretAPIClient
//go:generate go run go.uber.org/mock/mockgen -package juju -destination mock_test.go github.com/juju/terraform-provider-juju/internal/juju SharedClient,ClientAPIClient,ApplicationAPIClient,ModelConfigAPIClient,ResourceAPIClient,SecretAPIClient,HttpRequestClient,UploadRequest,ResourceHttpClient,osFilesystem,apicharms.Client,utils.GetUpgradeResources,charmresources.ParseType,resourcecmd.Opensource,typedError,io.ReadSeeker,http.Request,mime.Encoding.Encode,Mime.FormatMediaType,http.NewRequest
//go:generate go run go.uber.org/mock/mockgen -package juju -destination jujuapi_mock_test.go github.com/juju/juju/api Connection
2 changes: 1 addition & 1 deletion internal/provider/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func (ct CloudTesting) String() string {

// CloudName returns the cloud name as displayed
// when using `juju list-clouds`. For example,
// a controller can be bootstrapped with an lxd type.
// a controller can be bootstrapped with an LXD type.
// However, that's the controller type, the cloud name
// would be localhost
func (ct CloudTesting) CloudName() string {
Expand Down
2 changes: 1 addition & 1 deletion internal/provider/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"github.com/juju/terraform-provider-juju/internal/juju"
)

const TestProviderStableVersion = "0.10.1"
const TestProviderStableVersion = "0.12.0"

// providerFactories are used to instantiate the Framework provider during
// acceptance testing.
Expand Down
41 changes: 31 additions & 10 deletions internal/provider/resource_application.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,26 @@ const (

resourceKeyMarkdownDescription = `
Charm resource revisions. Must evaluate to a string. A resource could be a resource revision number or a custom resource.
There are a few scenarios that need to be considered:
* If the plan does not specify resource revision and resources are added to the plan,
resources with specified revisions will be attached to the application (equivalent
to juju attach-resource).
* If the plan does specify resource revisions and:
* If the charm revision or channel is updated, then resources get updated to the
latest revision.
* If the charm revision or channel are not updated, then no changes will take
place (juju does not have an "un-attach" command for resources).
There are a few scenarios that need to be considered:
* If the plan does not specify a resource and resources are added to the plan (as a revision number or a custom resource), specified resources are attached to the application (equivalent to juju attach-resource).
* If the plan does specify resource revisions and if the charm revision or channel is updated, existing resources are kept. (Resources are not detached)
* If the plan does specify resource revisions and resources are removed from the plan:
- If charm revision/channel is updated, the resources associated with the updated charm revision or channel is attached.
- If the charm revision/channel are not updated then the resources associated with the existing charm revision/channel are attached.
* Charm could be deployed without resource, then resource could be added later.
* Resources could be provided in the following formats:
- A custom repository URL
- A files ends with .txt, .json or .yaml
- A resource revision from CharmHub
* Charm could be deployed with resources.
* If the provided resource revision does not exist during initial deployment or update, Client does not start deployment with an error that resource was not found in the store.
* If the provided custom resource does not exist during initial deployment or update, Client start deployment and charm could not be deployed properly and charm will be in error state.
* If the Provided resource type is not correct then Client fails with incorrect resource error for below scenarios:
- An image is expected but file does not include image URL
- A plain text file is expected but a .json file is provided.
* If the provided resource does not exist then Client fails with path is not valid error.
* If provided resource value is changed from an int to a file then the image resource from the file is attached.
* If provided resource value is changed from a file to an int then image revision is attached.
`
)

Expand Down Expand Up @@ -855,6 +865,17 @@ func (r *applicationResource) Update(ctx context.Context, req resource.UpdateReq
updateApplicationInput.Resources[k] = v
}
}
// Resources are removed
if len(planResourceMap) == 0 && len(stateResourceMap) != 0 {
for k := range stateResourceMap {
if updateApplicationInput.Resources == nil {
// initialize the resources
updateApplicationInput.Resources = make(map[string]string)
// Set resource revision
updateApplicationInput.Resources[k] = "0"
}
}
}
}

if !plan.Constraints.Equal(state.Constraints) {
Expand Down
Loading

0 comments on commit 89f16d6

Please sign in to comment.