github.com/mook-as/cf-cli@v7.0.0-beta.28.0.20200120190804-b91c115fae48+incompatible/cf/api/applicationbits/application_bits.go (about)

     1  package applicationbits
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"mime/multipart"
     9  	"net/textproto"
    10  	"os"
    11  	"time"
    12  
    13  	"code.cloudfoundry.org/cli/cf/api/resources"
    14  	"code.cloudfoundry.org/cli/cf/configuration/coreconfig"
    15  	. "code.cloudfoundry.org/cli/cf/i18n"
    16  	"code.cloudfoundry.org/cli/cf/net"
    17  	"code.cloudfoundry.org/gofileutils/fileutils"
    18  )
    19  
    20  const (
    21  	DefaultAppUploadBitsTimeout = 15 * time.Minute
    22  )
    23  
    24  //go:generate counterfeiter . Repository
    25  
    26  type Repository interface {
    27  	GetApplicationFiles(appFilesRequest []resources.AppFileResource) ([]resources.AppFileResource, error)
    28  	UploadBits(appGUID string, zipFile *os.File, presentFiles []resources.AppFileResource) (apiErr error)
    29  }
    30  
    31  type CloudControllerApplicationBitsRepository struct {
    32  	config  coreconfig.Reader
    33  	gateway net.Gateway
    34  }
    35  
    36  func NewCloudControllerApplicationBitsRepository(config coreconfig.Reader, gateway net.Gateway) (repo CloudControllerApplicationBitsRepository) {
    37  	repo.config = config
    38  	repo.gateway = gateway
    39  	return
    40  }
    41  
    42  func (repo CloudControllerApplicationBitsRepository) UploadBits(appGUID string, zipFile *os.File, presentFiles []resources.AppFileResource) (apiErr error) {
    43  	apiURL := fmt.Sprintf("/v2/apps/%s/bits", appGUID)
    44  	fileutils.TempFile("requests", func(requestFile *os.File, err error) {
    45  		if err != nil {
    46  			apiErr = fmt.Errorf("%s: %s", T("Error creating tmp file: {{.Err}}", map[string]interface{}{"Err": err}), err.Error())
    47  			return
    48  		}
    49  
    50  		// json.Marshal represents a nil value as "null" instead of an empty slice "[]"
    51  		if presentFiles == nil {
    52  			presentFiles = []resources.AppFileResource{}
    53  		}
    54  
    55  		presentFilesJSON, err := json.Marshal(presentFiles)
    56  		if err != nil {
    57  			apiErr = fmt.Errorf("%s: %s", T("Error marshaling JSON"), err.Error())
    58  			return
    59  		}
    60  
    61  		boundary, err := repo.writeUploadBody(zipFile, requestFile, presentFilesJSON)
    62  		if err != nil {
    63  			apiErr = fmt.Errorf("%s: %s", T("Error writing to tmp file: {{.Err}}", map[string]interface{}{"Err": err}), err.Error())
    64  			return
    65  		}
    66  
    67  		var request *net.Request
    68  		request, apiErr = repo.gateway.NewRequestForFile("PUT", repo.config.APIEndpoint()+apiURL, repo.config.AccessToken(), requestFile)
    69  		if apiErr != nil {
    70  			return
    71  		}
    72  
    73  		contentType := fmt.Sprintf("multipart/form-data; boundary=%s", boundary)
    74  		request.HTTPReq.Header.Set("Content-Type", contentType)
    75  
    76  		response := &resources.Resource{}
    77  		_, apiErr = repo.gateway.PerformPollingRequestForJSONResponse(repo.config.APIEndpoint(), request, response, DefaultAppUploadBitsTimeout)
    78  		if apiErr != nil {
    79  			return
    80  		}
    81  	})
    82  
    83  	return
    84  }
    85  
    86  func (repo CloudControllerApplicationBitsRepository) GetApplicationFiles(appFilesToCheck []resources.AppFileResource) ([]resources.AppFileResource, error) {
    87  	integrityFieldsJSON, err := json.Marshal(mapAppFilesToIntegrityFields(appFilesToCheck))
    88  	if err != nil {
    89  		apiErr := fmt.Errorf("%s: %s", T("Failed to create json for resource_match request"), err.Error())
    90  		return nil, apiErr
    91  	}
    92  
    93  	responseFieldsColl := []resources.IntegrityFields{}
    94  	apiErr := repo.gateway.UpdateResourceSync(
    95  		repo.config.APIEndpoint(),
    96  		"/v2/resource_match",
    97  		bytes.NewReader(integrityFieldsJSON),
    98  		&responseFieldsColl)
    99  
   100  	if apiErr != nil {
   101  		return nil, apiErr
   102  	}
   103  
   104  	return intersectAppFilesIntegrityFields(appFilesToCheck, responseFieldsColl), nil
   105  }
   106  
   107  func mapAppFilesToIntegrityFields(in []resources.AppFileResource) (out []resources.IntegrityFields) {
   108  	for _, appFile := range in {
   109  		out = append(out, appFile.ToIntegrityFields())
   110  	}
   111  	return out
   112  }
   113  
   114  func intersectAppFilesIntegrityFields(
   115  	appFiles []resources.AppFileResource,
   116  	integrityFields []resources.IntegrityFields,
   117  ) (out []resources.AppFileResource) {
   118  	inputFiles := appFilesBySha(appFiles)
   119  	for _, responseFields := range integrityFields {
   120  		item, found := inputFiles[responseFields.Sha1]
   121  		if found {
   122  			out = append(out, item)
   123  		}
   124  	}
   125  	return out
   126  }
   127  
   128  func appFilesBySha(in []resources.AppFileResource) (out map[string]resources.AppFileResource) {
   129  	out = map[string]resources.AppFileResource{}
   130  	for _, inputFileResource := range in {
   131  		out[inputFileResource.Sha1] = inputFileResource
   132  	}
   133  	return out
   134  }
   135  
   136  func (repo CloudControllerApplicationBitsRepository) writeUploadBody(zipFile *os.File, body *os.File, presentResourcesJSON []byte) (boundary string, err error) {
   137  	writer := multipart.NewWriter(body)
   138  	defer writer.Close()
   139  
   140  	boundary = writer.Boundary()
   141  
   142  	part, err := writer.CreateFormField("resources")
   143  	if err != nil {
   144  		return
   145  	}
   146  
   147  	_, err = io.Copy(part, bytes.NewBuffer(presentResourcesJSON))
   148  	if err != nil {
   149  		return
   150  	}
   151  
   152  	if zipFile != nil {
   153  		zipStats, zipErr := zipFile.Stat()
   154  		if zipErr != nil {
   155  			return
   156  		}
   157  
   158  		if zipStats.Size() == 0 {
   159  			return
   160  		}
   161  
   162  		part, zipErr = createZipPartWriter(zipStats, writer)
   163  		if zipErr != nil {
   164  			return
   165  		}
   166  
   167  		_, zipErr = io.Copy(part, zipFile)
   168  		if zipErr != nil {
   169  			return
   170  		}
   171  	}
   172  
   173  	return
   174  }
   175  
   176  func createZipPartWriter(zipStats os.FileInfo, writer *multipart.Writer) (io.Writer, error) {
   177  	h := make(textproto.MIMEHeader)
   178  	h.Set("Content-Disposition", `form-data; name="application"; filename="application.zip"`)
   179  	h.Set("Content-Type", "application/zip")
   180  	h.Set("Content-Length", fmt.Sprintf("%d", zipStats.Size()))
   181  	h.Set("Content-Transfer-Encoding", "binary")
   182  	return writer.CreatePart(h)
   183  }