code.gitea.io/gitea@v1.22.3/routers/api/packages/alpine/alpine.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package alpine 5 6 import ( 7 "crypto/x509" 8 "encoding/hex" 9 "encoding/pem" 10 "errors" 11 "fmt" 12 "io" 13 "net/http" 14 "strings" 15 16 packages_model "code.gitea.io/gitea/models/packages" 17 "code.gitea.io/gitea/modules/json" 18 packages_module "code.gitea.io/gitea/modules/packages" 19 alpine_module "code.gitea.io/gitea/modules/packages/alpine" 20 "code.gitea.io/gitea/modules/util" 21 "code.gitea.io/gitea/routers/api/packages/helper" 22 "code.gitea.io/gitea/services/context" 23 packages_service "code.gitea.io/gitea/services/packages" 24 alpine_service "code.gitea.io/gitea/services/packages/alpine" 25 ) 26 27 func apiError(ctx *context.Context, status int, obj any) { 28 helper.LogAndProcessError(ctx, status, obj, func(message string) { 29 ctx.PlainText(status, message) 30 }) 31 } 32 33 func GetRepositoryKey(ctx *context.Context) { 34 _, pub, err := alpine_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID) 35 if err != nil { 36 apiError(ctx, http.StatusInternalServerError, err) 37 return 38 } 39 40 pubPem, _ := pem.Decode([]byte(pub)) 41 if pubPem == nil { 42 apiError(ctx, http.StatusInternalServerError, "failed to decode private key pem") 43 return 44 } 45 46 pubKey, err := x509.ParsePKIXPublicKey(pubPem.Bytes) 47 if err != nil { 48 apiError(ctx, http.StatusInternalServerError, err) 49 return 50 } 51 52 fingerprint, err := util.CreatePublicKeyFingerprint(pubKey) 53 if err != nil { 54 apiError(ctx, http.StatusInternalServerError, err) 55 return 56 } 57 58 ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ 59 ContentType: "application/x-pem-file", 60 Filename: fmt.Sprintf("%s@%s.rsa.pub", ctx.Package.Owner.LowerName, hex.EncodeToString(fingerprint)), 61 }) 62 } 63 64 func GetRepositoryFile(ctx *context.Context) { 65 pv, err := alpine_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID) 66 if err != nil { 67 apiError(ctx, http.StatusInternalServerError, err) 68 return 69 } 70 71 s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 72 ctx, 73 pv, 74 &packages_service.PackageFileInfo{ 75 Filename: alpine_service.IndexArchiveFilename, 76 CompositeKey: fmt.Sprintf("%s|%s|%s", ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture")), 77 }, 78 ) 79 if err != nil { 80 if errors.Is(err, util.ErrNotExist) { 81 apiError(ctx, http.StatusNotFound, err) 82 } else { 83 apiError(ctx, http.StatusInternalServerError, err) 84 } 85 return 86 } 87 88 helper.ServePackageFile(ctx, s, u, pf) 89 } 90 91 func UploadPackageFile(ctx *context.Context) { 92 branch := strings.TrimSpace(ctx.Params("branch")) 93 repository := strings.TrimSpace(ctx.Params("repository")) 94 if branch == "" || repository == "" { 95 apiError(ctx, http.StatusBadRequest, "invalid branch or repository") 96 return 97 } 98 99 upload, needToClose, err := ctx.UploadStream() 100 if err != nil { 101 apiError(ctx, http.StatusInternalServerError, err) 102 return 103 } 104 if needToClose { 105 defer upload.Close() 106 } 107 108 buf, err := packages_module.CreateHashedBufferFromReader(upload) 109 if err != nil { 110 apiError(ctx, http.StatusInternalServerError, err) 111 return 112 } 113 defer buf.Close() 114 115 pck, err := alpine_module.ParsePackage(buf) 116 if err != nil { 117 if errors.Is(err, util.ErrInvalidArgument) || err == io.EOF { 118 apiError(ctx, http.StatusBadRequest, err) 119 } else { 120 apiError(ctx, http.StatusInternalServerError, err) 121 } 122 return 123 } 124 125 if _, err := buf.Seek(0, io.SeekStart); err != nil { 126 apiError(ctx, http.StatusInternalServerError, err) 127 return 128 } 129 130 fileMetadataRaw, err := json.Marshal(pck.FileMetadata) 131 if err != nil { 132 apiError(ctx, http.StatusInternalServerError, err) 133 return 134 } 135 136 _, _, err = packages_service.CreatePackageOrAddFileToExisting( 137 ctx, 138 &packages_service.PackageCreationInfo{ 139 PackageInfo: packages_service.PackageInfo{ 140 Owner: ctx.Package.Owner, 141 PackageType: packages_model.TypeAlpine, 142 Name: pck.Name, 143 Version: pck.Version, 144 }, 145 Creator: ctx.Doer, 146 Metadata: pck.VersionMetadata, 147 }, 148 &packages_service.PackageFileCreationInfo{ 149 PackageFileInfo: packages_service.PackageFileInfo{ 150 Filename: fmt.Sprintf("%s-%s.apk", pck.Name, pck.Version), 151 CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, pck.FileMetadata.Architecture), 152 }, 153 Creator: ctx.Doer, 154 Data: buf, 155 IsLead: true, 156 Properties: map[string]string{ 157 alpine_module.PropertyBranch: branch, 158 alpine_module.PropertyRepository: repository, 159 alpine_module.PropertyArchitecture: pck.FileMetadata.Architecture, 160 alpine_module.PropertyMetadata: string(fileMetadataRaw), 161 }, 162 }, 163 ) 164 if err != nil { 165 switch err { 166 case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile: 167 apiError(ctx, http.StatusConflict, err) 168 case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 169 apiError(ctx, http.StatusForbidden, err) 170 default: 171 apiError(ctx, http.StatusInternalServerError, err) 172 } 173 return 174 } 175 176 if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, pck.FileMetadata.Architecture); err != nil { 177 apiError(ctx, http.StatusInternalServerError, err) 178 return 179 } 180 181 ctx.Status(http.StatusCreated) 182 } 183 184 func DownloadPackageFile(ctx *context.Context) { 185 branch := ctx.Params("branch") 186 repository := ctx.Params("repository") 187 architecture := ctx.Params("architecture") 188 189 opts := &packages_model.PackageFileSearchOptions{ 190 OwnerID: ctx.Package.Owner.ID, 191 PackageType: packages_model.TypeAlpine, 192 Query: ctx.Params("filename"), 193 CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture), 194 } 195 pfs, _, err := packages_model.SearchFiles(ctx, opts) 196 if err != nil { 197 apiError(ctx, http.StatusInternalServerError, err) 198 return 199 } 200 if len(pfs) == 0 { 201 // Try again with architecture 'noarch' 202 if architecture == alpine_module.NoArch { 203 apiError(ctx, http.StatusNotFound, nil) 204 return 205 } 206 207 opts.CompositeKey = fmt.Sprintf("%s|%s|%s", branch, repository, alpine_module.NoArch) 208 if pfs, _, err = packages_model.SearchFiles(ctx, opts); err != nil { 209 apiError(ctx, http.StatusInternalServerError, err) 210 return 211 } 212 213 if len(pfs) == 0 { 214 apiError(ctx, http.StatusNotFound, nil) 215 return 216 } 217 } 218 219 s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0]) 220 if err != nil { 221 if errors.Is(err, util.ErrNotExist) { 222 apiError(ctx, http.StatusNotFound, err) 223 } else { 224 apiError(ctx, http.StatusInternalServerError, err) 225 } 226 return 227 } 228 229 helper.ServePackageFile(ctx, s, u, pf) 230 } 231 232 func DeletePackageFile(ctx *context.Context) { 233 branch, repository, architecture := ctx.Params("branch"), ctx.Params("repository"), ctx.Params("architecture") 234 235 pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ 236 OwnerID: ctx.Package.Owner.ID, 237 PackageType: packages_model.TypeAlpine, 238 Query: ctx.Params("filename"), 239 CompositeKey: fmt.Sprintf("%s|%s|%s", branch, repository, architecture), 240 }) 241 if err != nil { 242 apiError(ctx, http.StatusInternalServerError, err) 243 return 244 } 245 if len(pfs) != 1 { 246 apiError(ctx, http.StatusNotFound, nil) 247 return 248 } 249 250 if err := packages_service.RemovePackageFileAndVersionIfUnreferenced(ctx, ctx.Doer, pfs[0]); err != nil { 251 if errors.Is(err, util.ErrNotExist) { 252 apiError(ctx, http.StatusNotFound, err) 253 } else { 254 apiError(ctx, http.StatusInternalServerError, err) 255 } 256 return 257 } 258 259 if err := alpine_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, branch, repository, architecture); err != nil { 260 apiError(ctx, http.StatusInternalServerError, err) 261 return 262 } 263 264 ctx.Status(http.StatusNoContent) 265 }