github.com/pivotal-cf/go-pivnet/v6@v6.0.2/product_files.go (about)

     1  package pivnet
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  
    10  	"github.com/pivotal-cf/go-pivnet/v6/download"
    11  	"github.com/pivotal-cf/go-pivnet/v6/logger"
    12  )
    13  
    14  type ProductFilesService struct {
    15  	client Client
    16  }
    17  
    18  type CreateProductFileConfig struct {
    19  	ProductSlug        string
    20  	AWSObjectKey       string
    21  	Description        string
    22  	DocsURL            string
    23  	FileType           string
    24  	FileVersion        string
    25  	IncludedFiles      []string
    26  	SHA256             string
    27  	MD5                string
    28  	Name               string
    29  	Platforms          []string
    30  	ReleasedAt         string
    31  	SystemRequirements []string
    32  }
    33  
    34  type ProductFilesResponse struct {
    35  	ProductFiles []ProductFile `json:"product_files,omitempty"`
    36  }
    37  
    38  type ProductFileResponse struct {
    39  	ProductFile ProductFile `json:"product_file,omitempty"`
    40  }
    41  
    42  type ProductFile struct {
    43  	ID                 int      `json:"id,omitempty" yaml:"id,omitempty"`
    44  	AWSObjectKey       string   `json:"aws_object_key,omitempty" yaml:"aws_object_key,omitempty"`
    45  	Description        string   `json:"description,omitempty" yaml:"description,omitempty"`
    46  	DocsURL            string   `json:"docs_url,omitempty" yaml:"docs_url,omitempty"`
    47  	FileTransferStatus string   `json:"file_transfer_status,omitempty" yaml:"file_transfer_status,omitempty"`
    48  	FileType           string   `json:"file_type,omitempty" yaml:"file_type,omitempty"`
    49  	FileVersion        string   `json:"file_version,omitempty" yaml:"file_version,omitempty"`
    50  	HasSignatureFile   bool     `json:"has_signature_file,omitempty" yaml:"has_signature_file,omitempty"`
    51  	IncludedFiles      []string `json:"included_files,omitempty" yaml:"included_files,omitempty"`
    52  	SHA256             string   `json:"sha256,omitempty" yaml:"sha256,omitempty"`
    53  	MD5                string   `json:"md5,omitempty" yaml:"md5,omitempty"`
    54  	Name               string   `json:"name,omitempty" yaml:"name,omitempty"`
    55  	Platforms          []string `json:"platforms,omitempty" yaml:"platforms,omitempty"`
    56  	ReadyToServe       bool     `json:"ready_to_serve,omitempty" yaml:"ready_to_serve,omitempty"`
    57  	ReleasedAt         string   `json:"released_at,omitempty" yaml:"released_at,omitempty"`
    58  	Size               int      `json:"size,omitempty" yaml:"size,omitempty"`
    59  	SystemRequirements []string `json:"system_requirements,omitempty" yaml:"system_requirements,omitempty"`
    60  	Links              *Links   `json:"_links,omitempty" yaml:"_links,omitempty"`
    61  }
    62  
    63  func (p ProductFile) DownloadLink() (string, error) {
    64  	if p.Links == nil {
    65  		return "", fmt.Errorf("Could not determine download link - links map is empty")
    66  	}
    67  
    68  	return p.Links.Download["href"], nil
    69  }
    70  
    71  const (
    72  	FileTypeSoftware          = "Software"
    73  	FileTypeDocumentation     = "Documentation"
    74  	FileTypeOpenSourceLicense = "Open Source License"
    75  )
    76  
    77  func (p ProductFilesService) List(productSlug string) ([]ProductFile, error) {
    78  	url := fmt.Sprintf("/products/%s/product_files", productSlug)
    79  
    80  	var response ProductFilesResponse
    81  	resp, err := p.client.MakeRequest(
    82  		"GET",
    83  		url,
    84  		http.StatusOK,
    85  		nil,
    86  	)
    87  	if err != nil {
    88  		return []ProductFile{}, err
    89  	}
    90  	defer resp.Body.Close()
    91  
    92  	err = json.NewDecoder(resp.Body).Decode(&response)
    93  	if err != nil {
    94  		return []ProductFile{}, err
    95  	}
    96  
    97  	return response.ProductFiles, nil
    98  }
    99  
   100  func (p ProductFilesService) ListForRelease(productSlug string, releaseID int) ([]ProductFile, error) {
   101  	url := fmt.Sprintf(
   102  		"/products/%s/releases/%d/product_files",
   103  		productSlug,
   104  		releaseID,
   105  	)
   106  
   107  	var response ProductFilesResponse
   108  	resp, err := p.client.MakeRequest(
   109  		"GET",
   110  		url,
   111  		http.StatusOK,
   112  		nil,
   113  	)
   114  	if err != nil {
   115  		return []ProductFile{}, err
   116  	}
   117  	defer resp.Body.Close()
   118  
   119  	err = json.NewDecoder(resp.Body).Decode(&response)
   120  	if err != nil {
   121  		return []ProductFile{}, err
   122  	}
   123  
   124  	return response.ProductFiles, nil
   125  }
   126  
   127  func (p ProductFilesService) Get(productSlug string, productFileID int) (ProductFile, error) {
   128  	url := fmt.Sprintf(
   129  		"/products/%s/product_files/%d",
   130  		productSlug,
   131  		productFileID,
   132  	)
   133  
   134  	var response ProductFileResponse
   135  	resp, err := p.client.MakeRequest(
   136  		"GET",
   137  		url,
   138  		http.StatusOK,
   139  		nil,
   140  	)
   141  	if err != nil {
   142  		return ProductFile{}, err
   143  	}
   144  	defer resp.Body.Close()
   145  
   146  	err = json.NewDecoder(resp.Body).Decode(&response)
   147  	if err != nil {
   148  		return ProductFile{}, err
   149  	}
   150  
   151  	return response.ProductFile, nil
   152  }
   153  
   154  func (p ProductFilesService) GetForRelease(productSlug string, releaseID int, productFileID int) (ProductFile, error) {
   155  	url := fmt.Sprintf(
   156  		"/products/%s/releases/%d/product_files/%d",
   157  		productSlug,
   158  		releaseID,
   159  		productFileID,
   160  	)
   161  
   162  	var response ProductFileResponse
   163  	resp, err := p.client.MakeRequest(
   164  		"GET",
   165  		url,
   166  		http.StatusOK,
   167  		nil,
   168  	)
   169  	if err != nil {
   170  		return ProductFile{}, err
   171  	}
   172  	defer resp.Body.Close()
   173  
   174  	err = json.NewDecoder(resp.Body).Decode(&response)
   175  	if err != nil {
   176  		return ProductFile{}, err
   177  	}
   178  
   179  	return response.ProductFile, nil
   180  }
   181  
   182  func (p ProductFilesService) Create(config CreateProductFileConfig) (ProductFile, error) {
   183  	if config.AWSObjectKey == "" {
   184  		return ProductFile{}, fmt.Errorf("AWS object key must not be empty")
   185  	}
   186  
   187  	url := fmt.Sprintf("/products/%s/product_files", config.ProductSlug)
   188  
   189  	body := createUpdateProductFileBody{
   190  		ProductFile: ProductFile{
   191  			AWSObjectKey:       config.AWSObjectKey,
   192  			Description:        config.Description,
   193  			DocsURL:            config.DocsURL,
   194  			FileType:           config.FileType,
   195  			FileVersion:        config.FileVersion,
   196  			IncludedFiles:      config.IncludedFiles,
   197  			SHA256:             config.SHA256,
   198  			MD5:                config.MD5,
   199  			Name:               config.Name,
   200  			Platforms:          config.Platforms,
   201  			ReleasedAt:         config.ReleasedAt,
   202  			SystemRequirements: config.SystemRequirements,
   203  		},
   204  	}
   205  
   206  	b, err := json.Marshal(body)
   207  	if err != nil {
   208  		// Untested as we cannot force an error because we are marshalling
   209  		// a known-good body
   210  		return ProductFile{}, err
   211  	}
   212  
   213  	var response ProductFileResponse
   214  	resp, err := p.client.MakeRequest(
   215  		"POST",
   216  		url,
   217  		http.StatusCreated,
   218  		bytes.NewReader(b),
   219  	)
   220  	if err != nil {
   221  		_, ok := err.(ErrTooManyRequests)
   222  		if ok {
   223  			return ProductFile{}, fmt.Errorf("You have hit the file creation limit. Please wait before creating more files. Contact pivnet-eng@pivotal.io with additional questions.")
   224  		} else {
   225  			return ProductFile{}, err
   226  		}
   227  	}
   228  	defer resp.Body.Close()
   229  
   230  	err = json.NewDecoder(resp.Body).Decode(&response)
   231  	if err != nil {
   232  		return ProductFile{}, err
   233  	}
   234  
   235  	return response.ProductFile, nil
   236  }
   237  
   238  func (p ProductFilesService) Update(productSlug string, productFile ProductFile) (ProductFile, error) {
   239  	url := fmt.Sprintf("/products/%s/product_files/%d", productSlug, productFile.ID)
   240  
   241  	body := createUpdateProductFileBody{
   242  		ProductFile: ProductFile{
   243  			Description:        productFile.Description,
   244  			FileVersion:        productFile.FileVersion,
   245  			SHA256:             productFile.SHA256,
   246  			MD5:                productFile.MD5,
   247  			Name:               productFile.Name,
   248  			DocsURL:            productFile.DocsURL,
   249  			SystemRequirements: productFile.SystemRequirements,
   250  		},
   251  	}
   252  
   253  	b, err := json.Marshal(body)
   254  	if err != nil {
   255  		// Untested as we cannot force an error because we are marshalling
   256  		// a known-good body
   257  		return ProductFile{}, err
   258  	}
   259  
   260  	var response ProductFileResponse
   261  	resp, err := p.client.MakeRequest(
   262  		"PATCH",
   263  		url,
   264  		http.StatusOK,
   265  		bytes.NewReader(b),
   266  	)
   267  	if err != nil {
   268  		return ProductFile{}, err
   269  	}
   270  	defer resp.Body.Close()
   271  
   272  	err = json.NewDecoder(resp.Body).Decode(&response)
   273  	if err != nil {
   274  		return ProductFile{}, err
   275  	}
   276  
   277  	return response.ProductFile, nil
   278  }
   279  
   280  type createUpdateProductFileBody struct {
   281  	ProductFile ProductFile `json:"product_file"`
   282  }
   283  
   284  func (p ProductFilesService) Delete(productSlug string, id int) (ProductFile, error) {
   285  	url := fmt.Sprintf(
   286  		"/products/%s/product_files/%d",
   287  		productSlug,
   288  		id,
   289  	)
   290  
   291  	var response ProductFileResponse
   292  	resp, err := p.client.MakeRequest(
   293  		"DELETE",
   294  		url,
   295  		http.StatusOK,
   296  		nil,
   297  	)
   298  	if err != nil {
   299  		return ProductFile{}, err
   300  	}
   301  	defer resp.Body.Close()
   302  
   303  	err = json.NewDecoder(resp.Body).Decode(&response)
   304  	if err != nil {
   305  		return ProductFile{}, err
   306  	}
   307  
   308  	return response.ProductFile, nil
   309  }
   310  
   311  func (p ProductFilesService) AddToRelease(
   312  	productSlug string,
   313  	releaseID int,
   314  	productFileID int,
   315  ) error {
   316  	url := fmt.Sprintf(
   317  		"/products/%s/releases/%d/add_product_file",
   318  		productSlug,
   319  		releaseID,
   320  	)
   321  
   322  	body := createUpdateProductFileBody{
   323  		ProductFile: ProductFile{
   324  			ID: productFileID,
   325  		},
   326  	}
   327  
   328  	b, err := json.Marshal(body)
   329  	if err != nil {
   330  		// Untested as we cannot force an error because we are marshalling
   331  		// a known-good body
   332  		return err
   333  	}
   334  
   335  	resp, err := p.client.MakeRequest(
   336  		"PATCH",
   337  		url,
   338  		http.StatusNoContent,
   339  		bytes.NewReader(b),
   340  	)
   341  	if err != nil {
   342  		return err
   343  	}
   344  	defer resp.Body.Close()
   345  
   346  	return nil
   347  }
   348  
   349  func (p ProductFilesService) RemoveFromRelease(
   350  	productSlug string,
   351  	releaseID int,
   352  	productFileID int,
   353  ) error {
   354  	url := fmt.Sprintf(
   355  		"/products/%s/releases/%d/remove_product_file",
   356  		productSlug,
   357  		releaseID,
   358  	)
   359  
   360  	body := createUpdateProductFileBody{
   361  		ProductFile: ProductFile{
   362  			ID: productFileID,
   363  		},
   364  	}
   365  
   366  	b, err := json.Marshal(body)
   367  	if err != nil {
   368  		// Untested as we cannot force an error because we are marshalling
   369  		// a known-good body
   370  		return err
   371  	}
   372  
   373  	resp, err := p.client.MakeRequest(
   374  		"PATCH",
   375  		url,
   376  		http.StatusNoContent,
   377  		bytes.NewReader(b),
   378  	)
   379  	if err != nil {
   380  		return err
   381  	}
   382  	defer resp.Body.Close()
   383  
   384  	return nil
   385  }
   386  
   387  func (p ProductFilesService) AddToFileGroup(
   388  	productSlug string,
   389  	fileGroupID int,
   390  	productFileID int,
   391  ) error {
   392  	url := fmt.Sprintf(
   393  		"/products/%s/file_groups/%d/add_product_file",
   394  		productSlug,
   395  		fileGroupID,
   396  	)
   397  
   398  	body := createUpdateProductFileBody{
   399  		ProductFile: ProductFile{
   400  			ID: productFileID,
   401  		},
   402  	}
   403  
   404  	b, err := json.Marshal(body)
   405  	if err != nil {
   406  		// Untested as we cannot force an error because we are marshalling
   407  		// a known-good body
   408  		return err
   409  	}
   410  
   411  	resp, err := p.client.MakeRequest(
   412  		"PATCH",
   413  		url,
   414  		http.StatusNoContent,
   415  		bytes.NewReader(b),
   416  	)
   417  	if err != nil {
   418  		return err
   419  	}
   420  	defer resp.Body.Close()
   421  
   422  	return nil
   423  }
   424  
   425  func (p ProductFilesService) RemoveFromFileGroup(
   426  	productSlug string,
   427  	fileGroupID int,
   428  	productFileID int,
   429  ) error {
   430  	url := fmt.Sprintf(
   431  		"/products/%s/file_groups/%d/remove_product_file",
   432  		productSlug,
   433  		fileGroupID,
   434  	)
   435  
   436  	body := createUpdateProductFileBody{
   437  		ProductFile: ProductFile{
   438  			ID: productFileID,
   439  		},
   440  	}
   441  
   442  	b, err := json.Marshal(body)
   443  	if err != nil {
   444  		// Untested as we cannot force an error because we are marshalling
   445  		// a known-good body
   446  		return err
   447  	}
   448  
   449  	resp, err := p.client.MakeRequest(
   450  		"PATCH",
   451  		url,
   452  		http.StatusNoContent,
   453  		bytes.NewReader(b),
   454  	)
   455  	if err != nil {
   456  		return err
   457  	}
   458  	defer resp.Body.Close()
   459  
   460  	return nil
   461  }
   462  
   463  func (p ProductFilesService) DownloadForRelease(
   464  	location *download.FileInfo,
   465  	productSlug string,
   466  	releaseID int,
   467  	productFileID int,
   468  	progressWriter io.Writer,
   469  ) error {
   470  	pf, err := p.GetForRelease(
   471  		productSlug,
   472  		releaseID,
   473  		productFileID,
   474  	)
   475  	if err != nil {
   476  		return fmt.Errorf("GetForRelease: %s", err)
   477  	}
   478  
   479  	downloadLink, err := pf.DownloadLink()
   480  	if err != nil {
   481  		return fmt.Errorf("DownloadLink: %s", err)
   482  	}
   483  
   484  	p.client.logger.Debug("Downloading file", logger.Data{"downloadLink": downloadLink})
   485  
   486  	productFileDownloadLinkFetcher := NewProductFileLinkFetcher(downloadLink, p.client)
   487  
   488  	p.client.downloader.Bar = download.NewBar()
   489  
   490  	err = p.client.downloader.Get(
   491  		location,
   492  		productFileDownloadLinkFetcher,
   493  		progressWriter,
   494  	)
   495  	if err != nil {
   496  		return fmt.Errorf("Downloader.Get: %s", err)
   497  	}
   498  
   499  	return nil
   500  }