github.com/wanddynosios/cli/v8@v8.7.9-0.20240221182337-1a92e3a7017f/api/cloudcontroller/ccv3/package.go (about)

     1  package ccv3
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"mime/multipart"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    13  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/constant"
    14  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal"
    15  	"code.cloudfoundry.org/cli/resources"
    16  )
    17  
    18  //go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 io.Reader
    19  
    20  // CreatePackage creates a package with the given settings, Type and the
    21  // ApplicationRelationship must be set.
    22  func (client *Client) CreatePackage(pkg resources.Package) (resources.Package, Warnings, error) {
    23  	var responseBody resources.Package
    24  
    25  	_, warnings, err := client.MakeRequest(RequestParams{
    26  		RequestName:  internal.PostPackageRequest,
    27  		RequestBody:  pkg,
    28  		ResponseBody: &responseBody,
    29  	})
    30  
    31  	return responseBody, warnings, err
    32  }
    33  
    34  // GetPackage returns the package with the given GUID.
    35  func (client *Client) GetPackage(packageGUID string) (resources.Package, Warnings, error) {
    36  	var responseBody resources.Package
    37  
    38  	_, warnings, err := client.MakeRequest(RequestParams{
    39  		RequestName:  internal.GetPackageRequest,
    40  		URIParams:    internal.Params{"package_guid": packageGUID},
    41  		ResponseBody: &responseBody,
    42  	})
    43  
    44  	return responseBody, warnings, err
    45  }
    46  
    47  // GetPackages returns the list of packages.
    48  func (client *Client) GetPackages(query ...Query) ([]resources.Package, Warnings, error) {
    49  	var packages []resources.Package
    50  
    51  	_, warnings, err := client.MakeListRequest(RequestParams{
    52  		RequestName:  internal.GetPackagesRequest,
    53  		Query:        query,
    54  		ResponseBody: resources.Package{},
    55  		AppendToList: func(item interface{}) error {
    56  			packages = append(packages, item.(resources.Package))
    57  			return nil
    58  		},
    59  	})
    60  
    61  	return packages, warnings, err
    62  }
    63  
    64  // UploadBitsPackage uploads the newResources and a list of existing resources
    65  // to the cloud controller. An updated package is returned. The function will
    66  // act differently given the following Readers:
    67  //   - io.ReadSeeker: Will function properly on retry.
    68  //   - io.Reader: Will return a ccerror.PipeSeekError on retry.
    69  //   - nil: Will not add the "application" section to the request. The newResourcesLength is ignored in this case.
    70  //
    71  // Note: In order to determine if package creation is successful, poll the
    72  // Package's state field for more information.
    73  func (client *Client) UploadBitsPackage(pkg resources.Package, matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (resources.Package, Warnings, error) {
    74  	if matchedResources == nil {
    75  		return resources.Package{}, nil, ccerror.NilObjectError{Object: "matchedResources"}
    76  	}
    77  
    78  	if newResources == nil {
    79  		return client.uploadExistingResourcesOnly(pkg.GUID, matchedResources)
    80  	}
    81  
    82  	return client.uploadNewAndExistingResources(pkg.GUID, matchedResources, newResources, newResourcesLength)
    83  }
    84  
    85  // UploadPackage uploads a file to a given package's Upload resource. Note:
    86  // fileToUpload is read entirely into memory prior to sending data to CC.
    87  func (client *Client) UploadPackage(pkg resources.Package, fileToUpload string) (resources.Package, Warnings, error) {
    88  	body, contentType, err := client.createUploadBuffer(fileToUpload, "bits")
    89  	if err != nil {
    90  		return resources.Package{}, nil, err
    91  	}
    92  
    93  	responsePackage := resources.Package{}
    94  	_, warnings, err := client.MakeRequestSendRaw(
    95  		internal.PostPackageBitsRequest,
    96  		internal.Params{"package_guid": pkg.GUID},
    97  		body.Bytes(),
    98  		contentType,
    99  		&responsePackage,
   100  	)
   101  
   102  	return responsePackage, warnings, err
   103  }
   104  
   105  // CopyPackage copies a package from a source package to a destination package
   106  // Note: source app guid is in URL; dest app guid is in body
   107  func (client *Client) CopyPackage(sourcePkgGUID string, targetAppGUID string) (resources.Package, Warnings, error) {
   108  	var targetPackage resources.Package
   109  
   110  	_, warnings, err := client.MakeRequest(RequestParams{
   111  		RequestName: internal.PostPackageRequest,
   112  		Query:       []Query{{Key: SourceGUID, Values: []string{sourcePkgGUID}}},
   113  		RequestBody: map[string]resources.Relationships{
   114  			"relationships": {
   115  				constant.RelationshipTypeApplication: resources.Relationship{GUID: targetAppGUID},
   116  			},
   117  		},
   118  		ResponseBody: &targetPackage,
   119  	})
   120  
   121  	return targetPackage, warnings, err
   122  }
   123  
   124  func (client *Client) calculateAppBitsRequestSize(matchedResources []Resource, newResourcesLength int64) (int64, error) {
   125  	body := &bytes.Buffer{}
   126  	form := multipart.NewWriter(body)
   127  
   128  	jsonResources, err := json.Marshal(matchedResources)
   129  	if err != nil {
   130  		return 0, err
   131  	}
   132  	err = form.WriteField("resources", string(jsonResources))
   133  	if err != nil {
   134  		return 0, err
   135  	}
   136  	_, err = form.CreateFormFile("bits", "package.zip")
   137  	if err != nil {
   138  		return 0, err
   139  	}
   140  	err = form.Close()
   141  	if err != nil {
   142  		return 0, err
   143  	}
   144  
   145  	return int64(body.Len()) + newResourcesLength, nil
   146  }
   147  
   148  func (client *Client) createMultipartBodyAndHeaderForAppBits(matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (string, io.ReadSeeker, <-chan error) {
   149  	writerOutput, writerInput := cloudcontroller.NewPipeBomb()
   150  	form := multipart.NewWriter(writerInput)
   151  
   152  	writeErrors := make(chan error)
   153  
   154  	go func() {
   155  		defer close(writeErrors)
   156  		defer writerInput.Close()
   157  
   158  		jsonResources, err := json.Marshal(matchedResources)
   159  		if err != nil {
   160  			writeErrors <- err
   161  			return
   162  		}
   163  
   164  		err = form.WriteField("resources", string(jsonResources))
   165  		if err != nil {
   166  			writeErrors <- err
   167  			return
   168  		}
   169  
   170  		writer, err := form.CreateFormFile("bits", "package.zip")
   171  		if err != nil {
   172  			writeErrors <- err
   173  			return
   174  		}
   175  
   176  		if newResourcesLength != 0 {
   177  			_, err = io.Copy(writer, newResources)
   178  			if err != nil {
   179  				writeErrors <- err
   180  				return
   181  			}
   182  		}
   183  
   184  		err = form.Close()
   185  		if err != nil {
   186  			writeErrors <- err
   187  		}
   188  	}()
   189  
   190  	return form.FormDataContentType(), writerOutput, writeErrors
   191  }
   192  
   193  func (*Client) createUploadBuffer(path string, paramName string) (bytes.Buffer, string, error) {
   194  	file, err := os.Open(path)
   195  	if err != nil {
   196  		return bytes.Buffer{}, "", err
   197  	}
   198  	defer file.Close()
   199  
   200  	body := bytes.Buffer{}
   201  	writer := multipart.NewWriter(&body)
   202  	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
   203  	if err != nil {
   204  		return bytes.Buffer{}, "", err
   205  	}
   206  	_, err = io.Copy(part, file)
   207  	if err != nil {
   208  		return bytes.Buffer{}, "", err
   209  	}
   210  
   211  	err = writer.Close()
   212  
   213  	return body, writer.FormDataContentType(), err
   214  }
   215  
   216  func (client *Client) uploadExistingResourcesOnly(packageGUID string, matchedResources []Resource) (resources.Package, Warnings, error) {
   217  	jsonResources, err := json.Marshal(matchedResources)
   218  	if err != nil {
   219  		return resources.Package{}, nil, err
   220  	}
   221  
   222  	body := bytes.NewBuffer(nil)
   223  	form := multipart.NewWriter(body)
   224  	err = form.WriteField("resources", string(jsonResources))
   225  	if err != nil {
   226  		return resources.Package{}, nil, err
   227  	}
   228  
   229  	err = form.Close()
   230  	if err != nil {
   231  		return resources.Package{}, nil, err
   232  	}
   233  
   234  	responsePackage := resources.Package{}
   235  
   236  	_, warnings, err := client.MakeRequestSendRaw(
   237  		internal.PostPackageBitsRequest,
   238  		internal.Params{"package_guid": packageGUID},
   239  		body.Bytes(),
   240  		form.FormDataContentType(),
   241  		&responsePackage,
   242  	)
   243  
   244  	return responsePackage, warnings, err
   245  }
   246  
   247  func (client *Client) uploadNewAndExistingResources(packageGUID string, matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (resources.Package, Warnings, error) {
   248  	contentLength, err := client.calculateAppBitsRequestSize(matchedResources, newResourcesLength)
   249  	if err != nil {
   250  		return resources.Package{}, nil, err
   251  	}
   252  
   253  	contentType, body, writeErrors := client.createMultipartBodyAndHeaderForAppBits(matchedResources, newResources, newResourcesLength)
   254  
   255  	responseBody := resources.Package{}
   256  	_, warnings, err := client.MakeRequestUploadAsync(
   257  		internal.PostPackageBitsRequest,
   258  		internal.Params{"package_guid": packageGUID},
   259  		contentType,
   260  		body,
   261  		contentLength,
   262  		&responseBody,
   263  		writeErrors,
   264  	)
   265  	return responseBody, warnings, err
   266  }