github.com/randomtask1155/cli@v6.41.1-0.20181227003417-a98eed78cbde+incompatible/api/cloudcontroller/ccv2/buildpack.go (about)

     1  package ccv2
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  	"mime/multipart"
     8  	"path/filepath"
     9  
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    12  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv2/internal"
    13  	"code.cloudfoundry.org/cli/types"
    14  )
    15  
    16  // Buildpack represents a Cloud Controller Buildpack.
    17  type Buildpack struct {
    18  	Locked   types.NullBool
    19  	Enabled  types.NullBool
    20  	GUID     string
    21  	Name     string
    22  	Position types.NullInt
    23  	Stack    string
    24  }
    25  
    26  func (buildpack Buildpack) MarshalJSON() ([]byte, error) {
    27  	ccBuildpack := struct {
    28  		Locked   *bool  `json:"locked,omitempty"`
    29  		Enabled  *bool  `json:"enabled,omitempty"`
    30  		Name     string `json:"name"`
    31  		Position *int   `json:"position,omitempty"`
    32  		Stack    string `json:"stack,omitempty"`
    33  	}{
    34  		Name:  buildpack.Name,
    35  		Stack: buildpack.Stack,
    36  	}
    37  
    38  	if buildpack.Position.IsSet {
    39  		ccBuildpack.Position = &buildpack.Position.Value
    40  	}
    41  	if buildpack.Enabled.IsSet {
    42  		ccBuildpack.Enabled = &buildpack.Enabled.Value
    43  	}
    44  	if buildpack.Locked.IsSet {
    45  		ccBuildpack.Locked = &buildpack.Locked.Value
    46  	}
    47  
    48  	return json.Marshal(ccBuildpack)
    49  }
    50  
    51  func (buildpack *Buildpack) UnmarshalJSON(data []byte) error {
    52  	var alias struct {
    53  		Metadata internal.Metadata `json:"metadata"`
    54  		Entity   struct {
    55  			Locked   types.NullBool `json:"locked"`
    56  			Enabled  types.NullBool `json:"enabled"`
    57  			Name     string         `json:"name"`
    58  			Position types.NullInt  `json:"position"`
    59  			Stack    string         `json:"stack"`
    60  		} `json:"entity"`
    61  	}
    62  
    63  	err := json.Unmarshal(data, &alias)
    64  	if err != nil {
    65  		return err
    66  	}
    67  
    68  	buildpack.Locked = alias.Entity.Locked
    69  	buildpack.Enabled = alias.Entity.Enabled
    70  	buildpack.GUID = alias.Metadata.GUID
    71  	buildpack.Name = alias.Entity.Name
    72  	buildpack.Position = alias.Entity.Position
    73  	buildpack.Stack = alias.Entity.Stack
    74  	return nil
    75  }
    76  
    77  // CreateBuildpack creates a new buildpack.
    78  func (client *Client) CreateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
    79  	body, err := json.Marshal(buildpack)
    80  	if err != nil {
    81  		return Buildpack{}, nil, err
    82  	}
    83  
    84  	request, err := client.newHTTPRequest(requestOptions{
    85  		RequestName: internal.PostBuildpackRequest,
    86  		Body:        bytes.NewReader(body),
    87  	})
    88  	if err != nil {
    89  		return Buildpack{}, nil, err
    90  	}
    91  
    92  	var createdBuildpack Buildpack
    93  	response := cloudcontroller.Response{
    94  		DecodeJSONResponseInto: &createdBuildpack,
    95  	}
    96  
    97  	err = client.connection.Make(request, &response)
    98  	return createdBuildpack, response.Warnings, err
    99  }
   100  
   101  // GetBuildpacks searches for a buildpack with the given name and returns it if it exists.
   102  func (client *Client) GetBuildpacks(filters ...Filter) ([]Buildpack, Warnings, error) {
   103  	request, err := client.newHTTPRequest(requestOptions{
   104  		RequestName: internal.GetBuildpacksRequest,
   105  		Query:       ConvertFilterParameters(filters),
   106  	})
   107  
   108  	if err != nil {
   109  		return nil, nil, err
   110  	}
   111  
   112  	var buildpacks []Buildpack
   113  	warnings, err := client.paginate(request, Buildpack{}, func(item interface{}) error {
   114  		if buildpack, ok := item.(Buildpack); ok {
   115  			buildpacks = append(buildpacks, buildpack)
   116  		} else {
   117  			return ccerror.UnknownObjectInListError{
   118  				Expected:   Buildpack{},
   119  				Unexpected: item,
   120  			}
   121  		}
   122  		return nil
   123  	})
   124  
   125  	return buildpacks, warnings, err
   126  }
   127  
   128  // UpdateBuildpack updates the buildpack with the provided GUID and returns the
   129  // updated buildpack. Note: Stack cannot be updated without uploading a new
   130  // buildpack.
   131  func (client *Client) UpdateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
   132  	body, err := json.Marshal(buildpack)
   133  	if err != nil {
   134  		return Buildpack{}, nil, err
   135  	}
   136  
   137  	request, err := client.newHTTPRequest(requestOptions{
   138  		RequestName: internal.PutBuildpackRequest,
   139  		URIParams:   Params{"buildpack_guid": buildpack.GUID},
   140  		Body:        bytes.NewReader(body),
   141  	})
   142  	if err != nil {
   143  		return Buildpack{}, nil, err
   144  	}
   145  
   146  	var updatedBuildpack Buildpack
   147  	response := cloudcontroller.Response{
   148  		DecodeJSONResponseInto: &updatedBuildpack,
   149  	}
   150  
   151  	err = client.connection.Make(request, &response)
   152  	if err != nil {
   153  		return Buildpack{}, response.Warnings, err
   154  	}
   155  
   156  	return updatedBuildpack, response.Warnings, nil
   157  }
   158  
   159  // UploadBuildpack uploads the contents of a buildpack zip to the server.
   160  func (client *Client) UploadBuildpack(buildpackGUID string, buildpackPath string, buildpack io.Reader, buildpackLength int64) (Warnings, error) {
   161  
   162  	contentLength, err := client.calculateBuildpackRequestSize(buildpackLength, buildpackPath)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	contentType, body, writeErrors := client.createMultipartBodyAndHeaderForBuildpack(buildpack, buildpackPath)
   168  
   169  	request, err := client.newHTTPRequest(requestOptions{
   170  		RequestName: internal.PutBuildpackBitsRequest,
   171  		URIParams:   Params{"buildpack_guid": buildpackGUID},
   172  		Body:        body,
   173  	})
   174  
   175  	if err != nil {
   176  		return nil, err
   177  	}
   178  
   179  	request.Header.Set("Content-Type", contentType)
   180  	request.ContentLength = contentLength
   181  
   182  	_, warnings, err := client.uploadBuildpackAsynchronously(request, writeErrors)
   183  	if err != nil {
   184  		return warnings, err
   185  	}
   186  	return warnings, nil
   187  
   188  }
   189  
   190  func (*Client) calculateBuildpackRequestSize(buildpackSize int64, bpPath string) (int64, error) {
   191  	body := &bytes.Buffer{}
   192  	form := multipart.NewWriter(body)
   193  
   194  	bpFileName := filepath.Base(bpPath)
   195  
   196  	_, err := form.CreateFormFile("buildpack", bpFileName)
   197  	if err != nil {
   198  		return 0, err
   199  	}
   200  
   201  	err = form.Close()
   202  	if err != nil {
   203  		return 0, err
   204  	}
   205  
   206  	return int64(body.Len()) + buildpackSize, nil
   207  }
   208  
   209  func (*Client) createMultipartBodyAndHeaderForBuildpack(buildpack io.Reader, bpPath string) (string, io.ReadSeeker, <-chan error) {
   210  	writerOutput, writerInput := cloudcontroller.NewPipeBomb()
   211  
   212  	form := multipart.NewWriter(writerInput)
   213  
   214  	writeErrors := make(chan error)
   215  
   216  	go func() {
   217  		defer close(writeErrors)
   218  		defer writerInput.Close()
   219  
   220  		bpFileName := filepath.Base(bpPath)
   221  		writer, err := form.CreateFormFile("buildpack", bpFileName)
   222  		if err != nil {
   223  			writeErrors <- err
   224  			return
   225  		}
   226  
   227  		_, err = io.Copy(writer, buildpack)
   228  		if err != nil {
   229  			writeErrors <- err
   230  			return
   231  		}
   232  
   233  		err = form.Close()
   234  		if err != nil {
   235  			writeErrors <- err
   236  		}
   237  	}()
   238  
   239  	return form.FormDataContentType(), writerOutput, writeErrors
   240  }
   241  
   242  func (client *Client) uploadBuildpackAsynchronously(request *cloudcontroller.Request, writeErrors <-chan error) (Buildpack, Warnings, error) {
   243  
   244  	var buildpack Buildpack
   245  	response := cloudcontroller.Response{
   246  		DecodeJSONResponseInto: &buildpack,
   247  	}
   248  
   249  	httpErrors := make(chan error)
   250  
   251  	go func() {
   252  		defer close(httpErrors)
   253  
   254  		err := client.connection.Make(request, &response)
   255  		if err != nil {
   256  			httpErrors <- err
   257  		}
   258  	}()
   259  
   260  	// The following section makes the following assumptions:
   261  	// 1) If an error occurs during file reading, an EOF is sent to the request
   262  	// object. Thus ending the request transfer.
   263  	// 2) If an error occurs during request transfer, an EOF is sent to the pipe.
   264  	// Thus ending the writing routine.
   265  	var firstError error
   266  	var writeClosed, httpClosed bool
   267  
   268  	for {
   269  		select {
   270  		case writeErr, ok := <-writeErrors:
   271  			if !ok {
   272  				writeClosed = true
   273  				break // for select
   274  			}
   275  			if firstError == nil {
   276  				firstError = writeErr
   277  			}
   278  		case httpErr, ok := <-httpErrors:
   279  			if !ok {
   280  				httpClosed = true
   281  				break // for select
   282  			}
   283  			if firstError == nil {
   284  				firstError = httpErr
   285  			}
   286  		}
   287  
   288  		if writeClosed && httpClosed {
   289  			break // for for
   290  		}
   291  	}
   292  	return buildpack, response.Warnings, firstError
   293  }