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 }