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