forgejo/modules/packages/chef/metadata.go
KN4CK3R d987ac6bf1
Add Chef package registry (#22554)
This PR implements a [Chef registry](https://chef.io/) to manage
cookbooks. This package type was a bit complicated because Chef uses RSA
signed requests as authentication with the registry.


![grafik](https://user-images.githubusercontent.com/1666336/213747995-46819fd8-c3d6-45a2-afd4-a4c3c8505a4a.png)


![grafik](https://user-images.githubusercontent.com/1666336/213748145-d01c9e81-d4dd-41e3-a3cc-8241862c3166.png)

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2023-02-06 09:49:21 +08:00

135 lines
3.6 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package chef
import (
"archive/tar"
"compress/gzip"
"io"
"regexp"
"strings"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
)
const (
KeyBits = 4096
SettingPublicPem = "chef.public_pem"
)
var (
ErrMissingMetadataFile = util.NewInvalidArgumentErrorf("metadata.json file is missing")
ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid")
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
namePattern = regexp.MustCompile(`\A\S+\z`)
versionPattern = regexp.MustCompile(`\A\d+\.\d+(?:\.\d+)?\z`)
)
// Package represents a Chef package
type Package struct {
Name string
Version string
Metadata *Metadata
}
// Metadata represents the metadata of a Chef package
type Metadata struct {
Description string `json:"description,omitempty"`
LongDescription string `json:"long_description,omitempty"`
Author string `json:"author,omitempty"`
License string `json:"license,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
Dependencies map[string]string `json:"dependencies,omitempty"`
}
type chefMetadata struct {
Name string `json:"name"`
Description string `json:"description"`
LongDescription string `json:"long_description"`
Maintainer string `json:"maintainer"`
MaintainerEmail string `json:"maintainer_email"`
License string `json:"license"`
Platforms map[string]string `json:"platforms"`
Dependencies map[string]string `json:"dependencies"`
Providing map[string]string `json:"providing"`
Recipes map[string]string `json:"recipes"`
Version string `json:"version"`
SourceURL string `json:"source_url"`
IssuesURL string `json:"issues_url"`
Privacy bool `json:"privacy"`
ChefVersions [][]string `json:"chef_versions"`
Gems [][]string `json:"gems"`
EagerLoadLibraries bool `json:"eager_load_libraries"`
}
// ParsePackage parses the Chef package file
func ParsePackage(r io.Reader) (*Package, error) {
gzr, err := gzip.NewReader(r)
if err != nil {
return nil, err
}
defer gzr.Close()
tr := tar.NewReader(gzr)
for {
hd, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if hd.Typeflag != tar.TypeReg {
continue
}
if strings.Count(hd.Name, "/") != 1 {
continue
}
if hd.FileInfo().Name() == "metadata.json" {
return ParseChefMetadata(tr)
}
}
return nil, ErrMissingMetadataFile
}
// ParseChefMetadata parses a metadata.json file to retrieve the metadata of a Chef package
func ParseChefMetadata(r io.Reader) (*Package, error) {
var cm chefMetadata
if err := json.NewDecoder(r).Decode(&cm); err != nil {
return nil, err
}
if !namePattern.MatchString(cm.Name) {
return nil, ErrInvalidName
}
if !versionPattern.MatchString(cm.Version) {
return nil, ErrInvalidVersion
}
if !validation.IsValidURL(cm.SourceURL) {
cm.SourceURL = ""
}
return &Package{
Name: cm.Name,
Version: cm.Version,
Metadata: &Metadata{
Description: cm.Description,
LongDescription: cm.LongDescription,
Author: cm.Maintainer,
License: cm.License,
RepositoryURL: cm.SourceURL,
Dependencies: cm.Dependencies,
},
}, nil
}