code.gitea.io/gitea@v1.21.7/routers/api/packages/vagrant/vagrant.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package vagrant
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	"net/http"
    10  	"net/url"
    11  	"sort"
    12  	"strings"
    13  
    14  	packages_model "code.gitea.io/gitea/models/packages"
    15  	"code.gitea.io/gitea/modules/context"
    16  	packages_module "code.gitea.io/gitea/modules/packages"
    17  	vagrant_module "code.gitea.io/gitea/modules/packages/vagrant"
    18  	"code.gitea.io/gitea/modules/setting"
    19  	"code.gitea.io/gitea/routers/api/packages/helper"
    20  	packages_service "code.gitea.io/gitea/services/packages"
    21  
    22  	"github.com/hashicorp/go-version"
    23  )
    24  
    25  func apiError(ctx *context.Context, status int, obj any) {
    26  	helper.LogAndProcessError(ctx, status, obj, func(message string) {
    27  		ctx.JSON(status, struct {
    28  			Errors []string `json:"errors"`
    29  		}{
    30  			Errors: []string{
    31  				message,
    32  			},
    33  		})
    34  	})
    35  }
    36  
    37  func CheckAuthenticate(ctx *context.Context) {
    38  	if ctx.Doer == nil {
    39  		apiError(ctx, http.StatusUnauthorized, "Invalid access token")
    40  		return
    41  	}
    42  
    43  	ctx.Status(http.StatusOK)
    44  }
    45  
    46  func CheckBoxAvailable(ctx *context.Context) {
    47  	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeVagrant, ctx.Params("name"))
    48  	if err != nil {
    49  		apiError(ctx, http.StatusInternalServerError, err)
    50  		return
    51  	}
    52  	if len(pvs) == 0 {
    53  		apiError(ctx, http.StatusNotFound, err)
    54  		return
    55  	}
    56  
    57  	ctx.JSON(http.StatusOK, nil) // needs to be Content-Type: application/json
    58  }
    59  
    60  type packageMetadata struct {
    61  	Name             string             `json:"name"`
    62  	Description      string             `json:"description,omitempty"`
    63  	ShortDescription string             `json:"short_description,omitempty"`
    64  	Versions         []*versionMetadata `json:"versions"`
    65  }
    66  
    67  type versionMetadata struct {
    68  	Version             string          `json:"version"`
    69  	Status              string          `json:"status"`
    70  	DescriptionHTML     string          `json:"description_html,omitempty"`
    71  	DescriptionMarkdown string          `json:"description_markdown,omitempty"`
    72  	Providers           []*providerData `json:"providers"`
    73  }
    74  
    75  type providerData struct {
    76  	Name         string `json:"name"`
    77  	URL          string `json:"url"`
    78  	Checksum     string `json:"checksum"`
    79  	ChecksumType string `json:"checksum_type"`
    80  }
    81  
    82  func packageDescriptorToMetadata(baseURL string, pd *packages_model.PackageDescriptor) *versionMetadata {
    83  	versionURL := baseURL + "/" + url.PathEscape(pd.Version.Version)
    84  
    85  	providers := make([]*providerData, 0, len(pd.Files))
    86  
    87  	for _, f := range pd.Files {
    88  		providers = append(providers, &providerData{
    89  			Name:         f.Properties.GetByName(vagrant_module.PropertyProvider),
    90  			URL:          versionURL + "/" + url.PathEscape(f.File.Name),
    91  			Checksum:     f.Blob.HashSHA512,
    92  			ChecksumType: "sha512",
    93  		})
    94  	}
    95  
    96  	return &versionMetadata{
    97  		Status:    "active",
    98  		Version:   pd.Version.Version,
    99  		Providers: providers,
   100  	}
   101  }
   102  
   103  func EnumeratePackageVersions(ctx *context.Context) {
   104  	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeVagrant, ctx.Params("name"))
   105  	if err != nil {
   106  		apiError(ctx, http.StatusInternalServerError, err)
   107  		return
   108  	}
   109  	if len(pvs) == 0 {
   110  		apiError(ctx, http.StatusNotFound, err)
   111  		return
   112  	}
   113  
   114  	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
   115  	if err != nil {
   116  		apiError(ctx, http.StatusInternalServerError, err)
   117  		return
   118  	}
   119  
   120  	sort.Slice(pds, func(i, j int) bool {
   121  		return pds[i].SemVer.LessThan(pds[j].SemVer)
   122  	})
   123  
   124  	baseURL := fmt.Sprintf("%sapi/packages/%s/vagrant/%s", setting.AppURL, url.PathEscape(ctx.Package.Owner.Name), url.PathEscape(pds[0].Package.Name))
   125  
   126  	versions := make([]*versionMetadata, 0, len(pds))
   127  	for _, pd := range pds {
   128  		versions = append(versions, packageDescriptorToMetadata(baseURL, pd))
   129  	}
   130  
   131  	ctx.JSON(http.StatusOK, &packageMetadata{
   132  		Name:        pds[0].Package.Name,
   133  		Description: pds[len(pds)-1].Metadata.(*vagrant_module.Metadata).Description,
   134  		Versions:    versions,
   135  	})
   136  }
   137  
   138  func UploadPackageFile(ctx *context.Context) {
   139  	boxName := ctx.Params("name")
   140  	boxVersion := ctx.Params("version")
   141  	_, err := version.NewSemver(boxVersion)
   142  	if err != nil {
   143  		apiError(ctx, http.StatusBadRequest, err)
   144  		return
   145  	}
   146  	boxProvider := ctx.Params("provider")
   147  	if !strings.HasSuffix(boxProvider, ".box") {
   148  		apiError(ctx, http.StatusBadRequest, err)
   149  		return
   150  	}
   151  
   152  	upload, needsClose, err := ctx.UploadStream()
   153  	if err != nil {
   154  		apiError(ctx, http.StatusInternalServerError, err)
   155  		return
   156  	}
   157  	if needsClose {
   158  		defer upload.Close()
   159  	}
   160  
   161  	buf, err := packages_module.CreateHashedBufferFromReader(upload)
   162  	if err != nil {
   163  		apiError(ctx, http.StatusInternalServerError, err)
   164  		return
   165  	}
   166  	defer buf.Close()
   167  
   168  	metadata, err := vagrant_module.ParseMetadataFromBox(buf)
   169  	if err != nil {
   170  		apiError(ctx, http.StatusInternalServerError, err)
   171  		return
   172  	}
   173  
   174  	if _, err := buf.Seek(0, io.SeekStart); err != nil {
   175  		apiError(ctx, http.StatusInternalServerError, err)
   176  		return
   177  	}
   178  
   179  	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
   180  		ctx,
   181  		&packages_service.PackageCreationInfo{
   182  			PackageInfo: packages_service.PackageInfo{
   183  				Owner:       ctx.Package.Owner,
   184  				PackageType: packages_model.TypeVagrant,
   185  				Name:        boxName,
   186  				Version:     boxVersion,
   187  			},
   188  			SemverCompatible: true,
   189  			Creator:          ctx.Doer,
   190  			Metadata:         metadata,
   191  		},
   192  		&packages_service.PackageFileCreationInfo{
   193  			PackageFileInfo: packages_service.PackageFileInfo{
   194  				Filename: strings.ToLower(boxProvider),
   195  			},
   196  			Creator: ctx.Doer,
   197  			Data:    buf,
   198  			IsLead:  true,
   199  			Properties: map[string]string{
   200  				vagrant_module.PropertyProvider: strings.TrimSuffix(boxProvider, ".box"),
   201  			},
   202  		},
   203  	)
   204  	if err != nil {
   205  		switch err {
   206  		case packages_model.ErrDuplicatePackageFile:
   207  			apiError(ctx, http.StatusConflict, err)
   208  		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   209  			apiError(ctx, http.StatusForbidden, err)
   210  		default:
   211  			apiError(ctx, http.StatusInternalServerError, err)
   212  		}
   213  		return
   214  	}
   215  
   216  	ctx.Status(http.StatusCreated)
   217  }
   218  
   219  func DownloadPackageFile(ctx *context.Context) {
   220  	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
   221  		ctx,
   222  		&packages_service.PackageInfo{
   223  			Owner:       ctx.Package.Owner,
   224  			PackageType: packages_model.TypeVagrant,
   225  			Name:        ctx.Params("name"),
   226  			Version:     ctx.Params("version"),
   227  		},
   228  		&packages_service.PackageFileInfo{
   229  			Filename: ctx.Params("provider"),
   230  		},
   231  	)
   232  	if err != nil {
   233  		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
   234  			apiError(ctx, http.StatusNotFound, err)
   235  			return
   236  		}
   237  		apiError(ctx, http.StatusInternalServerError, err)
   238  		return
   239  	}
   240  
   241  	helper.ServePackageFile(ctx, s, u, pf)
   242  }