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

     1  package ccv3
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"io"
     7  
     8  	"code.cloudfoundry.org/cli/api/cloudcontroller"
     9  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccerror"
    10  	"code.cloudfoundry.org/cli/api/cloudcontroller/ccv3/internal"
    11  	"code.cloudfoundry.org/cli/api/cloudcontroller/uploads"
    12  	"code.cloudfoundry.org/cli/types"
    13  )
    14  
    15  // Buildpack represents a Cloud Controller V3 buildpack.
    16  type Buildpack struct {
    17  	// Enabled is true when the buildpack can be used for staging.
    18  	Enabled types.NullBool
    19  	// Filename is the uploaded filename of the buildpack.
    20  	Filename string
    21  	// GUID is the unique identifier for the buildpack.
    22  	GUID string
    23  	// Locked is true when the buildpack cannot be updated.
    24  	Locked types.NullBool
    25  	// Name is the name of the buildpack. To be used by app buildpack field.
    26  	// (only alphanumeric characters)
    27  	Name string
    28  	// Position is the order in which the buildpacks are checked during buildpack
    29  	// auto-detection.
    30  	Position types.NullInt
    31  	// Stack is the name of the stack that the buildpack will use.
    32  	Stack string
    33  	// State is the current state of the buildpack.
    34  	State string
    35  	// Links are links to related resources.
    36  	Links APILinks
    37  	// Metadata is used for custom tagging of API resources
    38  	Metadata *Metadata
    39  }
    40  
    41  // MarshalJSON converts a Package into a Cloud Controller Package.
    42  func (buildpack Buildpack) MarshalJSON() ([]byte, error) {
    43  	ccBuildpack := struct {
    44  		Name     string    `json:"name,omitempty"`
    45  		Stack    string    `json:"stack,omitempty"`
    46  		Position *int      `json:"position,omitempty"`
    47  		Enabled  *bool     `json:"enabled,omitempty"`
    48  		Locked   *bool     `json:"locked,omitempty"`
    49  		Metadata *Metadata `json:"metadata,omitempty"`
    50  	}{
    51  		Name:  buildpack.Name,
    52  		Stack: buildpack.Stack,
    53  	}
    54  
    55  	if buildpack.Position.IsSet {
    56  		ccBuildpack.Position = &buildpack.Position.Value
    57  	}
    58  	if buildpack.Enabled.IsSet {
    59  		ccBuildpack.Enabled = &buildpack.Enabled.Value
    60  	}
    61  	if buildpack.Locked.IsSet {
    62  		ccBuildpack.Locked = &buildpack.Locked.Value
    63  	}
    64  
    65  	return json.Marshal(ccBuildpack)
    66  }
    67  
    68  func (buildpack *Buildpack) UnmarshalJSON(data []byte) error {
    69  	var ccBuildpack struct {
    70  		GUID     string         `json:"guid,omitempty"`
    71  		Links    APILinks       `json:"links,omitempty"`
    72  		Name     string         `json:"name,omitempty"`
    73  		Filename string         `json:"filename,omitempty"`
    74  		Stack    string         `json:"stack,omitempty"`
    75  		State    string         `json:"state,omitempty"`
    76  		Enabled  types.NullBool `json:"enabled"`
    77  		Locked   types.NullBool `json:"locked"`
    78  		Position types.NullInt  `json:"position"`
    79  		Metadata *Metadata      `json:"metadata"`
    80  	}
    81  
    82  	err := cloudcontroller.DecodeJSON(data, &ccBuildpack)
    83  	if err != nil {
    84  		return err
    85  	}
    86  
    87  	buildpack.Enabled = ccBuildpack.Enabled
    88  	buildpack.Filename = ccBuildpack.Filename
    89  	buildpack.GUID = ccBuildpack.GUID
    90  	buildpack.Locked = ccBuildpack.Locked
    91  	buildpack.Name = ccBuildpack.Name
    92  	buildpack.Position = ccBuildpack.Position
    93  	buildpack.Stack = ccBuildpack.Stack
    94  	buildpack.State = ccBuildpack.State
    95  	buildpack.Links = ccBuildpack.Links
    96  	buildpack.Metadata = ccBuildpack.Metadata
    97  
    98  	return nil
    99  }
   100  
   101  // CreateBuildpack creates a buildpack with the given settings, Type and the
   102  // ApplicationRelationship must be set.
   103  func (client *Client) CreateBuildpack(bp Buildpack) (Buildpack, Warnings, error) {
   104  	bodyBytes, err := json.Marshal(bp)
   105  	if err != nil {
   106  		return Buildpack{}, nil, err
   107  	}
   108  
   109  	request, err := client.newHTTPRequest(requestOptions{
   110  		RequestName: internal.PostBuildpackRequest,
   111  		Body:        bytes.NewReader(bodyBytes),
   112  	})
   113  	if err != nil {
   114  		return Buildpack{}, nil, err
   115  	}
   116  
   117  	var responseBuildpack Buildpack
   118  	response := cloudcontroller.Response{
   119  		DecodeJSONResponseInto: &responseBuildpack,
   120  	}
   121  	err = client.connection.Make(request, &response)
   122  
   123  	return responseBuildpack, response.Warnings, err
   124  }
   125  
   126  // DeleteBuildpack deletes the buildpack with the provided guid.
   127  func (client Client) DeleteBuildpack(buildpackGUID string) (JobURL, Warnings, error) {
   128  	request, err := client.newHTTPRequest(requestOptions{
   129  		RequestName: internal.DeleteBuildpackRequest,
   130  		URIParams: map[string]string{
   131  			"buildpack_guid": buildpackGUID,
   132  		},
   133  	})
   134  	if err != nil {
   135  		return "", nil, err
   136  	}
   137  
   138  	response := cloudcontroller.Response{}
   139  	err = client.connection.Make(request, &response)
   140  
   141  	return JobURL(response.ResourceLocationURL), response.Warnings, err
   142  }
   143  
   144  // GetBuildpacks lists buildpacks with optional filters.
   145  func (client *Client) GetBuildpacks(query ...Query) ([]Buildpack, Warnings, error) {
   146  	request, err := client.newHTTPRequest(requestOptions{
   147  		RequestName: internal.GetBuildpacksRequest,
   148  		Query:       query,
   149  	})
   150  	if err != nil {
   151  		return nil, nil, err
   152  	}
   153  
   154  	var fullBuildpacksList []Buildpack
   155  	warnings, err := client.paginate(request, Buildpack{}, func(item interface{}) error {
   156  		if buildpack, ok := item.(Buildpack); ok {
   157  			fullBuildpacksList = append(fullBuildpacksList, buildpack)
   158  		} else {
   159  			return ccerror.UnknownObjectInListError{
   160  				Expected:   Buildpack{},
   161  				Unexpected: item,
   162  			}
   163  		}
   164  		return nil
   165  	})
   166  
   167  	return fullBuildpacksList, warnings, err
   168  }
   169  
   170  func (client Client) UpdateBuildpack(buildpack Buildpack) (Buildpack, Warnings, error) {
   171  	bodyBytes, err := json.Marshal(buildpack)
   172  	if err != nil {
   173  		return Buildpack{}, nil, err
   174  	}
   175  
   176  	request, err := client.newHTTPRequest(requestOptions{
   177  		RequestName: internal.PatchBuildpackRequest,
   178  		Body:        bytes.NewReader(bodyBytes),
   179  		URIParams:   map[string]string{"buildpack_guid": buildpack.GUID},
   180  	})
   181  	if err != nil {
   182  		return Buildpack{}, nil, err
   183  	}
   184  
   185  	var responseBuildpack Buildpack
   186  	response := cloudcontroller.Response{
   187  		DecodeJSONResponseInto: &responseBuildpack,
   188  	}
   189  	err = client.connection.Make(request, &response)
   190  
   191  	return responseBuildpack, response.Warnings, err
   192  }
   193  
   194  // UploadBuildpack uploads the contents of a buildpack zip to the server.
   195  func (client *Client) UploadBuildpack(buildpackGUID string, buildpackPath string, buildpack io.Reader, buildpackLength int64) (JobURL, Warnings, error) {
   196  
   197  	contentLength, err := uploads.CalculateRequestSize(buildpackLength, buildpackPath, "bits")
   198  	if err != nil {
   199  		return "", nil, err
   200  	}
   201  
   202  	contentType, body, writeErrors := uploads.CreateMultipartBodyAndHeader(buildpack, buildpackPath, "bits")
   203  
   204  	request, err := client.newHTTPRequest(requestOptions{
   205  		RequestName: internal.PostBuildpackBitsRequest,
   206  		URIParams:   internal.Params{"buildpack_guid": buildpackGUID},
   207  		Body:        body,
   208  	})
   209  
   210  	if err != nil {
   211  		return "", nil, err
   212  	}
   213  
   214  	request.ContentLength = contentLength
   215  	request.Header.Set("Content-Type", contentType)
   216  
   217  	jobURL, warnings, err := client.uploadBuildpackAsynchronously(request, writeErrors)
   218  	if err != nil {
   219  		return "", warnings, err
   220  	}
   221  	return jobURL, warnings, nil
   222  }
   223  
   224  func (client *Client) uploadBuildpackAsynchronously(request *cloudcontroller.Request, writeErrors <-chan error) (JobURL, Warnings, error) {
   225  
   226  	var buildpack Buildpack
   227  	response := cloudcontroller.Response{
   228  		DecodeJSONResponseInto: &buildpack,
   229  	}
   230  
   231  	httpErrors := make(chan error)
   232  
   233  	go func() {
   234  		defer close(httpErrors)
   235  
   236  		err := client.connection.Make(request, &response)
   237  		if err != nil {
   238  			httpErrors <- err
   239  		}
   240  	}()
   241  
   242  	// The following section makes the following assumptions:
   243  	// 1) If an error occurs during file reading, an EOF is sent to the request
   244  	// object. Thus ending the request transfer.
   245  	// 2) If an error occurs during request transfer, an EOF is sent to the pipe.
   246  	// Thus ending the writing routine.
   247  	var firstError error
   248  	var writeClosed, httpClosed bool
   249  
   250  	for {
   251  		select {
   252  		case writeErr, ok := <-writeErrors:
   253  			if !ok {
   254  				writeClosed = true
   255  				break // for select
   256  			}
   257  			if firstError == nil {
   258  				firstError = writeErr
   259  			}
   260  		case httpErr, ok := <-httpErrors:
   261  			if !ok {
   262  				httpClosed = true
   263  				break // for select
   264  			}
   265  			if firstError == nil {
   266  				firstError = httpErr
   267  			}
   268  		}
   269  
   270  		if writeClosed && httpClosed {
   271  			break // for for
   272  		}
   273  	}
   274  	return JobURL(response.ResourceLocationURL), response.Warnings, firstError
   275  }