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

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package rpm
     5  
     6  import (
     7  	stdctx "context"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"strings"
    13  
    14  	"code.gitea.io/gitea/models/db"
    15  	packages_model "code.gitea.io/gitea/models/packages"
    16  	"code.gitea.io/gitea/modules/context"
    17  	"code.gitea.io/gitea/modules/json"
    18  	packages_module "code.gitea.io/gitea/modules/packages"
    19  	rpm_module "code.gitea.io/gitea/modules/packages/rpm"
    20  	"code.gitea.io/gitea/modules/setting"
    21  	"code.gitea.io/gitea/modules/util"
    22  	"code.gitea.io/gitea/routers/api/packages/helper"
    23  	notify_service "code.gitea.io/gitea/services/notify"
    24  	packages_service "code.gitea.io/gitea/services/packages"
    25  	rpm_service "code.gitea.io/gitea/services/packages/rpm"
    26  )
    27  
    28  func apiError(ctx *context.Context, status int, obj any) {
    29  	helper.LogAndProcessError(ctx, status, obj, func(message string) {
    30  		ctx.PlainText(status, message)
    31  	})
    32  }
    33  
    34  // https://dnf.readthedocs.io/en/latest/conf_ref.html
    35  func GetRepositoryConfig(ctx *context.Context) {
    36  	url := fmt.Sprintf("%sapi/packages/%s/rpm", setting.AppURL, ctx.Package.Owner.Name)
    37  
    38  	ctx.PlainText(http.StatusOK, `[gitea-`+ctx.Package.Owner.LowerName+`]
    39  name=`+ctx.Package.Owner.Name+` - `+setting.AppName+`
    40  baseurl=`+url+`
    41  enabled=1
    42  gpgcheck=1
    43  gpgkey=`+url+`/repository.key`)
    44  }
    45  
    46  // Gets or creates the PGP public key used to sign repository metadata files
    47  func GetRepositoryKey(ctx *context.Context) {
    48  	_, pub, err := rpm_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
    49  	if err != nil {
    50  		apiError(ctx, http.StatusInternalServerError, err)
    51  		return
    52  	}
    53  
    54  	ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{
    55  		ContentType: "application/pgp-keys",
    56  		Filename:    "repository.key",
    57  	})
    58  }
    59  
    60  func CheckRepositoryFileExistence(ctx *context.Context) {
    61  	pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
    62  	if err != nil {
    63  		apiError(ctx, http.StatusInternalServerError, err)
    64  		return
    65  	}
    66  
    67  	pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, ctx.Params("filename"), packages_model.EmptyFileKey)
    68  	if err != nil {
    69  		if errors.Is(err, util.ErrNotExist) {
    70  			ctx.Status(http.StatusNotFound)
    71  		} else {
    72  			apiError(ctx, http.StatusInternalServerError, err)
    73  		}
    74  		return
    75  	}
    76  
    77  	ctx.SetServeHeaders(&context.ServeHeaderOptions{
    78  		Filename:     pf.Name,
    79  		LastModified: pf.CreatedUnix.AsLocalTime(),
    80  	})
    81  	ctx.Status(http.StatusOK)
    82  }
    83  
    84  // Gets a pre-generated repository metadata file
    85  func GetRepositoryFile(ctx *context.Context) {
    86  	pv, err := rpm_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
    87  	if err != nil {
    88  		apiError(ctx, http.StatusInternalServerError, err)
    89  		return
    90  	}
    91  
    92  	s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
    93  		ctx,
    94  		pv,
    95  		&packages_service.PackageFileInfo{
    96  			Filename: ctx.Params("filename"),
    97  		},
    98  	)
    99  	if err != nil {
   100  		if errors.Is(err, util.ErrNotExist) {
   101  			apiError(ctx, http.StatusNotFound, err)
   102  		} else {
   103  			apiError(ctx, http.StatusInternalServerError, err)
   104  		}
   105  		return
   106  	}
   107  
   108  	helper.ServePackageFile(ctx, s, u, pf)
   109  }
   110  
   111  func UploadPackageFile(ctx *context.Context) {
   112  	upload, close, err := ctx.UploadStream()
   113  	if err != nil {
   114  		apiError(ctx, http.StatusInternalServerError, err)
   115  		return
   116  	}
   117  	if close {
   118  		defer upload.Close()
   119  	}
   120  
   121  	buf, err := packages_module.CreateHashedBufferFromReader(upload)
   122  	if err != nil {
   123  		apiError(ctx, http.StatusInternalServerError, err)
   124  		return
   125  	}
   126  	defer buf.Close()
   127  
   128  	pck, err := rpm_module.ParsePackage(buf)
   129  	if err != nil {
   130  		if errors.Is(err, util.ErrInvalidArgument) {
   131  			apiError(ctx, http.StatusBadRequest, err)
   132  		} else {
   133  			apiError(ctx, http.StatusInternalServerError, err)
   134  		}
   135  		return
   136  	}
   137  
   138  	if _, err := buf.Seek(0, io.SeekStart); err != nil {
   139  		apiError(ctx, http.StatusInternalServerError, err)
   140  		return
   141  	}
   142  
   143  	fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
   144  	if err != nil {
   145  		apiError(ctx, http.StatusInternalServerError, err)
   146  		return
   147  	}
   148  
   149  	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
   150  		ctx,
   151  		&packages_service.PackageCreationInfo{
   152  			PackageInfo: packages_service.PackageInfo{
   153  				Owner:       ctx.Package.Owner,
   154  				PackageType: packages_model.TypeRpm,
   155  				Name:        pck.Name,
   156  				Version:     pck.Version,
   157  			},
   158  			Creator:  ctx.Doer,
   159  			Metadata: pck.VersionMetadata,
   160  		},
   161  		&packages_service.PackageFileCreationInfo{
   162  			PackageFileInfo: packages_service.PackageFileInfo{
   163  				Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture),
   164  			},
   165  			Creator: ctx.Doer,
   166  			Data:    buf,
   167  			IsLead:  true,
   168  			Properties: map[string]string{
   169  				rpm_module.PropertyMetadata: string(fileMetadataRaw),
   170  			},
   171  		},
   172  	)
   173  	if err != nil {
   174  		switch err {
   175  		case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
   176  			apiError(ctx, http.StatusConflict, err)
   177  		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   178  			apiError(ctx, http.StatusForbidden, err)
   179  		default:
   180  			apiError(ctx, http.StatusInternalServerError, err)
   181  		}
   182  		return
   183  	}
   184  
   185  	if err := rpm_service.BuildRepositoryFiles(ctx, ctx.Package.Owner.ID); err != nil {
   186  		apiError(ctx, http.StatusInternalServerError, err)
   187  		return
   188  	}
   189  
   190  	ctx.Status(http.StatusCreated)
   191  }
   192  
   193  func DownloadPackageFile(ctx *context.Context) {
   194  	name := ctx.Params("name")
   195  	version := ctx.Params("version")
   196  
   197  	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
   198  		ctx,
   199  		&packages_service.PackageInfo{
   200  			Owner:       ctx.Package.Owner,
   201  			PackageType: packages_model.TypeRpm,
   202  			Name:        name,
   203  			Version:     version,
   204  		},
   205  		&packages_service.PackageFileInfo{
   206  			Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")),
   207  		},
   208  	)
   209  	if err != nil {
   210  		if errors.Is(err, util.ErrNotExist) {
   211  			apiError(ctx, http.StatusNotFound, err)
   212  		} else {
   213  			apiError(ctx, http.StatusInternalServerError, err)
   214  		}
   215  		return
   216  	}
   217  
   218  	helper.ServePackageFile(ctx, s, u, pf)
   219  }
   220  
   221  func DeletePackageFile(webctx *context.Context) {
   222  	name := webctx.Params("name")
   223  	version := webctx.Params("version")
   224  	architecture := webctx.Params("architecture")
   225  
   226  	var pd *packages_model.PackageDescriptor
   227  
   228  	err := db.WithTx(webctx, func(ctx stdctx.Context) error {
   229  		pv, err := packages_model.GetVersionByNameAndVersion(ctx, webctx.Package.Owner.ID, packages_model.TypeRpm, name, version)
   230  		if err != nil {
   231  			return err
   232  		}
   233  
   234  		pf, err := packages_model.GetFileForVersionByName(
   235  			ctx,
   236  			pv.ID,
   237  			fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture),
   238  			packages_model.EmptyFileKey,
   239  		)
   240  		if err != nil {
   241  			return err
   242  		}
   243  
   244  		if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
   245  			return err
   246  		}
   247  
   248  		has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
   249  		if err != nil {
   250  			return err
   251  		}
   252  		if !has {
   253  			pd, err = packages_model.GetPackageDescriptor(ctx, pv)
   254  			if err != nil {
   255  				return err
   256  			}
   257  
   258  			if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
   259  				return err
   260  			}
   261  		}
   262  
   263  		return nil
   264  	})
   265  	if err != nil {
   266  		if errors.Is(err, util.ErrNotExist) {
   267  			apiError(webctx, http.StatusNotFound, err)
   268  		} else {
   269  			apiError(webctx, http.StatusInternalServerError, err)
   270  		}
   271  		return
   272  	}
   273  
   274  	if pd != nil {
   275  		notify_service.PackageDelete(webctx, webctx.Doer, pd)
   276  	}
   277  
   278  	if err := rpm_service.BuildRepositoryFiles(webctx, webctx.Package.Owner.ID); err != nil {
   279  		apiError(webctx, http.StatusInternalServerError, err)
   280  		return
   281  	}
   282  
   283  	webctx.Status(http.StatusNoContent)
   284  }