mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-26 08:58:24 +00:00 
			
		
		
		
	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.   Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
		
			
				
	
	
		
			135 lines
		
	
	
		
			3.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			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
 | |
| }
 |