From 3083ece877461896b5c9afdc18cbeb53c64d10bf Mon Sep 17 00:00:00 2001 From: gatici Date: Sun, 2 Jun 2024 09:43:58 +0300 Subject: [PATCH] Update custom images when application is updated Signed-off-by: gatici --- internal/juju/applications.go | 116 ++++++++++++++++++---------------- internal/juju/interfaces.go | 3 + internal/juju/mock_test.go | 17 +++++ main.go | 2 +- 4 files changed, 84 insertions(+), 54 deletions(-) diff --git a/internal/juju/applications.go b/internal/juju/applications.go index c4e18b72..9aa747ca 100644 --- a/internal/juju/applications.go +++ b/internal/juju/applications.go @@ -69,7 +69,7 @@ const ( ) const ( - // HeaderContentType is the header name for the type of a file upload. + // HeaderContentType is the header name for the type of file upload. HeaderContentType = "Content-Type" // HeaderContentSha384 is the header name for the sha hash of a file upload. HeaderContentSha384 = "Content-Sha384" @@ -448,7 +448,7 @@ func (c applicationsClient) CreateApplication(ctx context.Context, input *Create if applicationAPIClient.BestAPIVersion() >= 19 { err = c.deployFromRepository(applicationAPIClient, resourceHttpClient, transformedInput) } else { - err = c.legacyDeploy(ctx, conn, applicationAPIClient, transformedInput, resourceHttpClient) + err = c.legacyDeploy(ctx, conn, applicationAPIClient, transformedInput) err = jujuerrors.Annotate(err, "legacy deploy method") } if err != nil { @@ -502,7 +502,7 @@ func (c applicationsClient) deployFromRepository(applicationAPIClient *apiapplic // Remove the funcationality 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, resourceHttpClient *HttpRequestClient) error { +func (c applicationsClient) legacyDeploy(ctx context.Context, conn api.Connection, applicationAPIClient *apiapplication.Client, transformedInput transformedCreateApplicationInput) error { // Version needed for operating system selection. c.controllerVersion, _ = conn.ServerVersion() @@ -621,7 +621,7 @@ func (c applicationsClient) legacyDeploy(ctx context.Context, conn api.Connectio Origin: resultOrigin, } - resources, err := c.processResources(charmsAPIClient, conn, charmID, transformedInput.applicationName, transformedInput.resources, resourceHttpClient) + resources, err := c.processResources(charmsAPIClient, conn, charmID, transformedInput.applicationName, transformedInput.resources) if err != nil && !jujuerrors.Is(err, jujuerrors.AlreadyExists) { return err } @@ -832,7 +832,7 @@ func splitCommaDelimitedList(list string) []string { // processResources is a helper function to process the charm // metadata and request the download of any additional resource. -func (c applicationsClient) processResources(charmsAPIClient *apicharms.Client, conn api.Connection, charmID apiapplication.CharmID, appName string, resources map[string]string, resourceHttpClient *HttpRequestClient) (map[string]string, error) { +func (c applicationsClient) processResources(charmsAPIClient *apicharms.Client, conn api.Connection, charmID apiapplication.CharmID, appName string, resources map[string]string) (map[string]string, error) { charmInfo, err := charmsAPIClient.CharmInfo(charmID.URL) if err != nil { return nil, typedError(err) @@ -848,7 +848,7 @@ func (c applicationsClient) processResources(charmsAPIClient *apicharms.Client, return nil, err } - return addPendingResources(appName, charmInfo.Meta.Resources, resources, charmID, resourcesAPIClient, resourceHttpClient) + return addPendingResources(appName, charmInfo.Meta.Resources, resources, charmID, resourcesAPIClient) } // ReadApplicationWithRetryOnNotFound calls ReadApplication until @@ -1470,65 +1470,74 @@ func (c applicationsClient) updateResources(appName string, resources map[string return nil, nil } - return addPendingResources(appName, filtered, resources, charmID, resourcesAPIClient, resourceHttpClient) + return addPendingResources(appName, filtered, resources, charmID, resourcesAPIClient) } func addPendingResources(appName string, resourcesToBeAdded map[string]charmresources.Meta, resourcesRevisions map[string]string, - charmID apiapplication.CharmID, resourcesAPIClient ResourceAPIClient, resourceHttpClient *HttpRequestClient) (map[string]string, error) { - pendingResources := []charmresources.Resource{} - pendingResourceUploads := []apiapplication.PendingResourceUpload{} + charmID apiapplication.CharmID, resourcesAPIClient ResourceAPIClient) (map[string]string, error) { + pendingResourcesforAdd := []charmresources.Resource{} + toReturn := map[string]string{} for _, resourceMeta := range resourcesToBeAdded { - aux := charmresources.Resource{ - Meta: resourceMeta, - Origin: charmresources.OriginStore, - Revision: -1, - } if resourcesRevisions != nil { - if revision, ok := resourcesRevisions[resourceMeta.Name]; ok { - if isInt(revision) { - iRevision, err := strconv.Atoi(revision) + if deployValue, ok := resourcesRevisions[resourceMeta.Name]; ok { + if isInt(deployValue) { + // A resource revision is provided + providedRev, err := strconv.Atoi(deployValue) + if err != nil { + return nil, typedError(err) + } + aux := charmresources.Resource{ + Meta: resourceMeta, + Origin: charmresources.OriginStore, + Revision: -1, + } + aux.Revision = providedRev + pendingResourcesforAdd = append(pendingResourcesforAdd, aux) + } else { + // A new resource to be uploaded by the client + uux := charmresources.Resource{ + Meta: resourceMeta, + Origin: charmresources.OriginUpload, + } + + fileSystem := osFilesystem{} + t, typeParseErr := charmresources.ParseType(resourceMeta.Type.String()) + if typeParseErr != nil { + return nil, typedError(typeParseErr) + } + r, openResErr := resourcecmd.OpenResource(deployValue, t, fileSystem.Open) + if openResErr != nil { + return nil, typedError(openResErr) + } + toRequestUpload, err := resourcesAPIClient.UploadPendingResource(appName, uux, deployValue, r) if err != nil { return nil, typedError(err) } - aux.Revision = iRevision + // Add the resource name and the corresponding UUID to the resources map + toReturn[resourceMeta.Name] = toRequestUpload } - pendingResourceUploads = append(pendingResourceUploads, apiapplication.PendingResourceUpload{ - Name: resourceMeta.Name, - Filename: resourcesRevisions[resourceMeta.Name], - Type: resourceMeta.Type.String(), - }) } } - - pendingResources = append(pendingResources, aux) - } - - resourcesReq := apiresources.AddPendingResourcesArgs{ - ApplicationID: appName, - CharmID: apiresources.CharmID{ - URL: charmID.URL, - Origin: charmID.Origin, - }, - Resources: pendingResources, } - toRequest, err := resourcesAPIClient.AddPendingResources(resourcesReq) - if err != nil { - return nil, typedError(err) - } - - fileSystem := osFilesystem{} - uploadErr := uploadExistingPendingResources(appName, pendingResourceUploads, fileSystem, resourceHttpClient) - - if uploadErr != nil { - return nil, uploadErr - } - - // now build a map with the resource name and the corresponding UUID - toReturn := map[string]string{} - for i, argsResource := range pendingResources { - toReturn[argsResource.Meta.Name] = toRequest[i] + if len(pendingResourcesforAdd) != 0 { + resourcesReqforAdd := apiresources.AddPendingResourcesArgs{ + ApplicationID: appName, + CharmID: apiresources.CharmID{ + URL: charmID.URL, + Origin: charmID.Origin, + }, + Resources: pendingResourcesforAdd, + } + toRequestAdd, err := resourcesAPIClient.AddPendingResources(resourcesReqforAdd) + if err != nil { + return nil, typedError(err) + } + // Add the resource name and the corresponding UUID to the resources map + for i, argsResource := range pendingResourcesforAdd { + toReturn[argsResource.Meta.Name] = toRequestAdd[i] + } } return toReturn, nil @@ -1547,7 +1556,7 @@ func upload(appName, name, filename, pendingID string, reader io.ReadSeeker, res if err != nil { return jujuerrors.Trace(err) } - var response params.UploadResult // ignored + var response params.UploadResult if err := resourceHttpClient.httpClient.Do(resourceHttpClient.facade.RawAPICaller().Context(), req, &response); err != nil { return jujuerrors.Trace(err) } @@ -1603,6 +1612,7 @@ func uploadExistingPendingResources( if pendingResources == nil { return nil } + pendingID := "" for _, pendingResUpload := range pendingResources { t, typeParseErr := charmresources.ParseType(pendingResUpload.Type) @@ -1615,7 +1625,7 @@ func uploadExistingPendingResources( if openResErr != nil { return jujuerrors.Annotatef(openResErr, "unable to open resource %v", pendingResUpload.Name) } - uploadErr := upload(appName, pendingResUpload.Name, pendingResUpload.Filename, "", r, resourceHttpClient) + uploadErr := upload(appName, pendingResUpload.Name, pendingResUpload.Filename, pendingID, r, resourceHttpClient) if uploadErr != nil { return jujuerrors.Trace(uploadErr) diff --git a/internal/juju/interfaces.go b/internal/juju/interfaces.go index 7f07ddce..921fb959 100644 --- a/internal/juju/interfaces.go +++ b/internal/juju/interfaces.go @@ -5,6 +5,7 @@ package juju import ( "github.com/juju/charm/v12" + charmresources "github.com/juju/charm/v12/resource" "github.com/juju/juju/api" apiapplication "github.com/juju/juju/api/client/application" apiclient "github.com/juju/juju/api/client/client" @@ -17,6 +18,7 @@ import ( "github.com/juju/juju/core/secrets" "github.com/juju/juju/rpc/params" "github.com/juju/names/v5" + "io" ) type SharedClient interface { @@ -63,6 +65,7 @@ type ModelConfigAPIClient interface { type ResourceAPIClient interface { AddPendingResources(args apiresources.AddPendingResourcesArgs) ([]string, error) ListResources(applications []string) ([]resources.ApplicationResources, error) + UploadPendingResource(applicationID string, resource charmresources.Resource, filename string, r io.ReadSeeker) (id string, err error) } type SecretAPIClient interface { diff --git a/internal/juju/mock_test.go b/internal/juju/mock_test.go index 506c13ac..47212f2e 100644 --- a/internal/juju/mock_test.go +++ b/internal/juju/mock_test.go @@ -10,9 +10,11 @@ package juju import ( + io "io" reflect "reflect" charm "github.com/juju/charm/v12" + resource "github.com/juju/charm/v12/resource" api "github.com/juju/juju/api" application "github.com/juju/juju/api/client/application" client "github.com/juju/juju/api/client/client" @@ -572,6 +574,21 @@ func (mr *MockResourceAPIClientMockRecorder) ListResources(arg0 any) *gomock.Cal return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListResources", reflect.TypeOf((*MockResourceAPIClient)(nil).ListResources), arg0) } +// UploadPendingResource mocks base method. +func (m *MockResourceAPIClient) UploadPendingResource(arg0 string, arg1 resource.Resource, arg2 string, arg3 io.ReadSeeker) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UploadPendingResource", arg0, arg1, arg2, arg3) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UploadPendingResource indicates an expected call of UploadPendingResource. +func (mr *MockResourceAPIClientMockRecorder) UploadPendingResource(arg0, arg1, arg2, arg3 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadPendingResource", reflect.TypeOf((*MockResourceAPIClient)(nil).UploadPendingResource), arg0, arg1, arg2, arg3) +} + // MockSecretAPIClient is a mock of SecretAPIClient interface. type MockSecretAPIClient struct { ctrl *gomock.Controller diff --git a/main.go b/main.go index 0462bc9d..9ed824bd 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,7 @@ var ( func main() { var debugMode bool - flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.BoolVar(&debugMode, "debug", true, "set to true to run the provider with support for debuggers like delve") flag.Parse() var serveOpts []tf6server.ServeOpt