github.com/cloudfoundry-community/cloudfoundry-cli@v6.44.1-0.20240130060226-cda5ed8e89a5+incompatible/api/cloudcontroller/ccv2/buildpack.go (about)

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