github.com/LukasHeimann/cloudfoundrycli@v7.1.0+incompatible/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 counterfeiter io.Reader
    19  
    20  // Package represents a Cloud Controller V3 Package.
    21  type Package struct {
    22  	// CreatedAt is the time with zone when the object was created.
    23  	CreatedAt string
    24  
    25  	// DockerImage is the registry address of the docker image.
    26  	DockerImage string
    27  
    28  	// DockerPassword is the password for the docker image's registry.
    29  	DockerPassword string
    30  
    31  	// DockerUsername is the username for the docker image's registry.
    32  	DockerUsername string
    33  
    34  	// GUID is the unique identifier of the package.
    35  	GUID string
    36  
    37  	// Links are links to related resources.
    38  	Links APILinks
    39  
    40  	// Relationships are a list of relationships to other resources.
    41  	Relationships resources.Relationships
    42  
    43  	// State is the state of the package.
    44  	State constant.PackageState
    45  
    46  	// Type is the package type.
    47  	Type constant.PackageType
    48  }
    49  
    50  // MarshalJSON converts a Package into a Cloud Controller Package.
    51  func (p Package) MarshalJSON() ([]byte, error) {
    52  	type ccPackageData struct {
    53  		Image    string `json:"image,omitempty"`
    54  		Username string `json:"username,omitempty"`
    55  		Password string `json:"password,omitempty"`
    56  	}
    57  	var ccPackage struct {
    58  		GUID          string                  `json:"guid,omitempty"`
    59  		CreatedAt     string                  `json:"created_at,omitempty"`
    60  		Links         APILinks                `json:"links,omitempty"`
    61  		Relationships resources.Relationships `json:"relationships,omitempty"`
    62  		State         constant.PackageState   `json:"state,omitempty"`
    63  		Type          constant.PackageType    `json:"type,omitempty"`
    64  		Data          *ccPackageData          `json:"data,omitempty"`
    65  	}
    66  
    67  	ccPackage.GUID = p.GUID
    68  	ccPackage.CreatedAt = p.CreatedAt
    69  	ccPackage.Links = p.Links
    70  	ccPackage.Relationships = p.Relationships
    71  	ccPackage.State = p.State
    72  	ccPackage.Type = p.Type
    73  	if p.DockerImage != "" {
    74  		ccPackage.Data = &ccPackageData{
    75  			Image:    p.DockerImage,
    76  			Username: p.DockerUsername,
    77  			Password: p.DockerPassword,
    78  		}
    79  	}
    80  
    81  	return json.Marshal(ccPackage)
    82  }
    83  
    84  // UnmarshalJSON helps unmarshal a Cloud Controller Package response.
    85  func (p *Package) UnmarshalJSON(data []byte) error {
    86  	var ccPackage struct {
    87  		GUID          string                  `json:"guid,omitempty"`
    88  		CreatedAt     string                  `json:"created_at,omitempty"`
    89  		Links         APILinks                `json:"links,omitempty"`
    90  		Relationships resources.Relationships `json:"relationships,omitempty"`
    91  		State         constant.PackageState   `json:"state,omitempty"`
    92  		Type          constant.PackageType    `json:"type,omitempty"`
    93  		Data          struct {
    94  			Image    string `json:"image"`
    95  			Username string `json:"username"`
    96  			Password string `json:"password"`
    97  		} `json:"data"`
    98  	}
    99  	err := cloudcontroller.DecodeJSON(data, &ccPackage)
   100  	if err != nil {
   101  		return err
   102  	}
   103  
   104  	p.GUID = ccPackage.GUID
   105  	p.CreatedAt = ccPackage.CreatedAt
   106  	p.Links = ccPackage.Links
   107  	p.Relationships = ccPackage.Relationships
   108  	p.State = ccPackage.State
   109  	p.Type = ccPackage.Type
   110  	p.DockerImage = ccPackage.Data.Image
   111  	p.DockerUsername = ccPackage.Data.Username
   112  	p.DockerPassword = ccPackage.Data.Password
   113  
   114  	return nil
   115  }
   116  
   117  // CreatePackage creates a package with the given settings, Type and the
   118  // ApplicationRelationship must be set.
   119  func (client *Client) CreatePackage(pkg Package) (Package, Warnings, error) {
   120  	var responseBody Package
   121  
   122  	_, warnings, err := client.MakeRequest(RequestParams{
   123  		RequestName:  internal.PostPackageRequest,
   124  		RequestBody:  pkg,
   125  		ResponseBody: &responseBody,
   126  	})
   127  
   128  	return responseBody, warnings, err
   129  }
   130  
   131  // GetPackage returns the package with the given GUID.
   132  func (client *Client) GetPackage(packageGUID string) (Package, Warnings, error) {
   133  	var responseBody Package
   134  
   135  	_, warnings, err := client.MakeRequest(RequestParams{
   136  		RequestName:  internal.GetPackageRequest,
   137  		URIParams:    internal.Params{"package_guid": packageGUID},
   138  		ResponseBody: &responseBody,
   139  	})
   140  
   141  	return responseBody, warnings, err
   142  }
   143  
   144  // GetPackages returns the list of packages.
   145  func (client *Client) GetPackages(query ...Query) ([]Package, Warnings, error) {
   146  	var resources []Package
   147  
   148  	_, warnings, err := client.MakeListRequest(RequestParams{
   149  		RequestName:  internal.GetPackagesRequest,
   150  		Query:        query,
   151  		ResponseBody: Package{},
   152  		AppendToList: func(item interface{}) error {
   153  			resources = append(resources, item.(Package))
   154  			return nil
   155  		},
   156  	})
   157  
   158  	return resources, warnings, err
   159  }
   160  
   161  // UploadBitsPackage uploads the newResources and a list of existing resources
   162  // to the cloud controller. An updated package is returned. The function will
   163  // act differently given the following Readers:
   164  //   - io.ReadSeeker: Will function properly on retry.
   165  //   - io.Reader: Will return a ccerror.PipeSeekError on retry.
   166  //   - nil: Will not add the "application" section to the request. The newResourcesLength is ignored in this case.
   167  //
   168  // Note: In order to determine if package creation is successful, poll the
   169  // Package's state field for more information.
   170  func (client *Client) UploadBitsPackage(pkg Package, matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (Package, Warnings, error) {
   171  	if matchedResources == nil {
   172  		return Package{}, nil, ccerror.NilObjectError{Object: "matchedResources"}
   173  	}
   174  
   175  	if newResources == nil {
   176  		return client.uploadExistingResourcesOnly(pkg.GUID, matchedResources)
   177  	}
   178  
   179  	return client.uploadNewAndExistingResources(pkg.GUID, matchedResources, newResources, newResourcesLength)
   180  }
   181  
   182  // UploadPackage uploads a file to a given package's Upload resource. Note:
   183  // fileToUpload is read entirely into memory prior to sending data to CC.
   184  func (client *Client) UploadPackage(pkg Package, fileToUpload string) (Package, Warnings, error) {
   185  	body, contentType, err := client.createUploadBuffer(fileToUpload, "bits")
   186  	if err != nil {
   187  		return Package{}, nil, err
   188  	}
   189  
   190  	responsePackage := Package{}
   191  	_, warnings, err := client.MakeRequestSendRaw(
   192  		internal.PostPackageBitsRequest,
   193  		internal.Params{"package_guid": pkg.GUID},
   194  		body.Bytes(),
   195  		contentType,
   196  		&responsePackage,
   197  	)
   198  
   199  	return responsePackage, warnings, err
   200  }
   201  
   202  // CopyPackage copies a package from a source package to a destination package
   203  // Note: source app guid is in URL; dest app guid is in body
   204  func (client *Client) CopyPackage(sourcePkgGUID string, targetAppGUID string) (Package, Warnings, error) {
   205  	var targetPackage Package
   206  
   207  	_, warnings, err := client.MakeRequest(RequestParams{
   208  		RequestName: internal.PostPackageRequest,
   209  		Query:       []Query{{Key: SourceGUID, Values: []string{sourcePkgGUID}}},
   210  		RequestBody: map[string]resources.Relationships{
   211  			"relationships": {
   212  				constant.RelationshipTypeApplication: resources.Relationship{GUID: targetAppGUID},
   213  			},
   214  		},
   215  		ResponseBody: &targetPackage,
   216  	})
   217  
   218  	return targetPackage, warnings, err
   219  }
   220  
   221  func (client *Client) calculateAppBitsRequestSize(matchedResources []Resource, newResourcesLength int64) (int64, error) {
   222  	body := &bytes.Buffer{}
   223  	form := multipart.NewWriter(body)
   224  
   225  	jsonResources, err := json.Marshal(matchedResources)
   226  	if err != nil {
   227  		return 0, err
   228  	}
   229  	err = form.WriteField("resources", string(jsonResources))
   230  	if err != nil {
   231  		return 0, err
   232  	}
   233  	_, err = form.CreateFormFile("bits", "package.zip")
   234  	if err != nil {
   235  		return 0, err
   236  	}
   237  	err = form.Close()
   238  	if err != nil {
   239  		return 0, err
   240  	}
   241  
   242  	return int64(body.Len()) + newResourcesLength, nil
   243  }
   244  
   245  func (client *Client) createMultipartBodyAndHeaderForAppBits(matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (string, io.ReadSeeker, <-chan error) {
   246  	writerOutput, writerInput := cloudcontroller.NewPipeBomb()
   247  	form := multipart.NewWriter(writerInput)
   248  
   249  	writeErrors := make(chan error)
   250  
   251  	go func() {
   252  		defer close(writeErrors)
   253  		defer writerInput.Close()
   254  
   255  		jsonResources, err := json.Marshal(matchedResources)
   256  		if err != nil {
   257  			writeErrors <- err
   258  			return
   259  		}
   260  
   261  		err = form.WriteField("resources", string(jsonResources))
   262  		if err != nil {
   263  			writeErrors <- err
   264  			return
   265  		}
   266  
   267  		writer, err := form.CreateFormFile("bits", "package.zip")
   268  		if err != nil {
   269  			writeErrors <- err
   270  			return
   271  		}
   272  
   273  		if newResourcesLength != 0 {
   274  			_, err = io.Copy(writer, newResources)
   275  			if err != nil {
   276  				writeErrors <- err
   277  				return
   278  			}
   279  		}
   280  
   281  		err = form.Close()
   282  		if err != nil {
   283  			writeErrors <- err
   284  		}
   285  	}()
   286  
   287  	return form.FormDataContentType(), writerOutput, writeErrors
   288  }
   289  
   290  func (*Client) createUploadBuffer(path string, paramName string) (bytes.Buffer, string, error) {
   291  	file, err := os.Open(path)
   292  	if err != nil {
   293  		return bytes.Buffer{}, "", err
   294  	}
   295  	defer file.Close()
   296  
   297  	body := bytes.Buffer{}
   298  	writer := multipart.NewWriter(&body)
   299  	part, err := writer.CreateFormFile(paramName, filepath.Base(path))
   300  	if err != nil {
   301  		return bytes.Buffer{}, "", err
   302  	}
   303  	_, err = io.Copy(part, file)
   304  	if err != nil {
   305  		return bytes.Buffer{}, "", err
   306  	}
   307  
   308  	err = writer.Close()
   309  
   310  	return body, writer.FormDataContentType(), err
   311  }
   312  
   313  func (client *Client) uploadExistingResourcesOnly(packageGUID string, matchedResources []Resource) (Package, Warnings, error) {
   314  	jsonResources, err := json.Marshal(matchedResources)
   315  	if err != nil {
   316  		return Package{}, nil, err
   317  	}
   318  
   319  	body := bytes.NewBuffer(nil)
   320  	form := multipart.NewWriter(body)
   321  	err = form.WriteField("resources", string(jsonResources))
   322  	if err != nil {
   323  		return Package{}, nil, err
   324  	}
   325  
   326  	err = form.Close()
   327  	if err != nil {
   328  		return Package{}, nil, err
   329  	}
   330  
   331  	responsePackage := Package{}
   332  
   333  	_, warnings, err := client.MakeRequestSendRaw(
   334  		internal.PostPackageBitsRequest,
   335  		internal.Params{"package_guid": packageGUID},
   336  		body.Bytes(),
   337  		form.FormDataContentType(),
   338  		&responsePackage,
   339  	)
   340  
   341  	return responsePackage, warnings, err
   342  }
   343  
   344  func (client *Client) uploadNewAndExistingResources(packageGUID string, matchedResources []Resource, newResources io.Reader, newResourcesLength int64) (Package, Warnings, error) {
   345  	contentLength, err := client.calculateAppBitsRequestSize(matchedResources, newResourcesLength)
   346  	if err != nil {
   347  		return Package{}, nil, err
   348  	}
   349  
   350  	contentType, body, writeErrors := client.createMultipartBodyAndHeaderForAppBits(matchedResources, newResources, newResourcesLength)
   351  
   352  	responseBody := Package{}
   353  	_, warnings, err := client.MakeRequestUploadAsync(
   354  		internal.PostPackageBitsRequest,
   355  		internal.Params{"package_guid": packageGUID},
   356  		contentType,
   357  		body,
   358  		contentLength,
   359  		&responseBody,
   360  		writeErrors,
   361  	)
   362  	return responseBody, warnings, err
   363  }