mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09:00 
			
		
		
		
	* Add a storage layer for attachments * Fix some bug * fix test * Fix copyright head and lint * Fix bug * Add setting for minio and flags for migrate-storage * Add documents * fix lint * Add test for minio store type on attachments * fix test * fix test * Apply suggestions from code review Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> * Add warning when storage migrated successfully * Fix drone * fix test * rebase * Fix test * display the error on console * Move minio test to amd64 since minio docker don't support arm64 * refactor the codes * add trace * Fix test * remove log on xorm * Fi download bug * Add a storage layer for attachments * Add setting for minio and flags for migrate-storage * fix lint * Add test for minio store type on attachments * Apply suggestions from code review Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> * Fix drone * fix test * Fix test * display the error on console * Move minio test to amd64 since minio docker don't support arm64 * refactor the codes * add trace * Fix test * Add URL function to serve attachments directly from S3/Minio * Add ability to enable/disable redirection in attachment configuration * Fix typo * Add a storage layer for attachments * Add setting for minio and flags for migrate-storage * fix lint * Add test for minio store type on attachments * Apply suggestions from code review Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> * Fix drone * fix test * Fix test * display the error on console * Move minio test to amd64 since minio docker don't support arm64 * don't change unrelated files * Fix lint * Fix build * update go.mod and go.sum * Use github.com/minio/minio-go/v6 * Remove unused function * Upgrade minio to v7 and some other improvements * fix lint * Fix go mod Co-authored-by: guillep2k <18600385+guillep2k@users.noreply.github.com> Co-authored-by: Tyler <tystuyfzand@gmail.com>
		
			
				
	
	
		
			381 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			381 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| /*
 | |
|  * MinIO Client (C) 2020 MinIO, Inc.
 | |
|  *
 | |
|  * Licensed under the Apache License, Version 2.0 (the "License");
 | |
|  * you may not use this file except in compliance with the License.
 | |
|  * You may obtain a copy of the License at
 | |
|  *
 | |
|  *     http://www.apache.org/licenses/LICENSE-2.0
 | |
|  *
 | |
|  * Unless required by applicable law or agreed to in writing, software
 | |
|  * distributed under the License is distributed on an "AS IS" BASIS,
 | |
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
|  * See the License for the specific language governing permissions and
 | |
|  * limitations under the License.
 | |
|  */
 | |
| 
 | |
| package replication
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"encoding/xml"
 | |
| 	"fmt"
 | |
| 	"strconv"
 | |
| 	"strings"
 | |
| 	"unicode/utf8"
 | |
| 
 | |
| 	"github.com/rs/xid"
 | |
| )
 | |
| 
 | |
| var errInvalidFilter = fmt.Errorf("Invalid filter")
 | |
| 
 | |
| // OptionType specifies operation to be performed on config
 | |
| type OptionType string
 | |
| 
 | |
| const (
 | |
| 	// AddOption specifies addition of rule to config
 | |
| 	AddOption OptionType = "Add"
 | |
| 	// SetOption specifies modification of existing rule to config
 | |
| 	SetOption OptionType = "Set"
 | |
| 
 | |
| 	// RemoveOption specifies rule options are for removing a rule
 | |
| 	RemoveOption OptionType = "Remove"
 | |
| 	// ImportOption is for getting current config
 | |
| 	ImportOption OptionType = "Import"
 | |
| )
 | |
| 
 | |
| // Options represents options to set a replication configuration rule
 | |
| type Options struct {
 | |
| 	Op           OptionType
 | |
| 	ID           string
 | |
| 	Prefix       string
 | |
| 	RuleStatus   string
 | |
| 	Priority     string
 | |
| 	TagString    string
 | |
| 	StorageClass string
 | |
| 	Arn          string
 | |
| }
 | |
| 
 | |
| // Tags returns a slice of tags for a rule
 | |
| func (opts Options) Tags() []Tag {
 | |
| 	var tagList []Tag
 | |
| 	tagTokens := strings.Split(opts.TagString, "&")
 | |
| 	for _, tok := range tagTokens {
 | |
| 		if tok == "" {
 | |
| 			break
 | |
| 		}
 | |
| 		kv := strings.SplitN(tok, "=", 2)
 | |
| 		tagList = append(tagList, Tag{
 | |
| 			Key:   kv[0],
 | |
| 			Value: kv[1],
 | |
| 		})
 | |
| 	}
 | |
| 	return tagList
 | |
| }
 | |
| 
 | |
| // Config - replication configuration specified in
 | |
| // https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
 | |
| type Config struct {
 | |
| 	XMLName xml.Name `xml:"ReplicationConfiguration" json:"-"`
 | |
| 	Rules   []Rule   `xml:"Rule" json:"Rules"`
 | |
| 	Role    string   `xml:"Role" json:"Role"`
 | |
| }
 | |
| 
 | |
| // Empty returns true if config is not set
 | |
| func (c *Config) Empty() bool {
 | |
| 	return len(c.Rules) == 0
 | |
| }
 | |
| 
 | |
| // AddRule adds a new rule to existing replication config. If a rule exists with the
 | |
| // same ID, then the rule is replaced.
 | |
| func (c *Config) AddRule(opts Options) error {
 | |
| 	tags := opts.Tags()
 | |
| 	andVal := And{
 | |
| 		Tags: opts.Tags(),
 | |
| 	}
 | |
| 	filter := Filter{Prefix: opts.Prefix}
 | |
| 	// only a single tag is set.
 | |
| 	if opts.Prefix == "" && len(tags) == 1 {
 | |
| 		filter.Tag = tags[0]
 | |
| 	}
 | |
| 	// both prefix and tag are present
 | |
| 	if len(andVal.Tags) > 1 || opts.Prefix != "" {
 | |
| 		filter.And = andVal
 | |
| 		filter.And.Prefix = opts.Prefix
 | |
| 		filter.Prefix = ""
 | |
| 	}
 | |
| 	if opts.ID == "" {
 | |
| 		opts.ID = xid.New().String()
 | |
| 	}
 | |
| 	var status Status
 | |
| 	// toggle rule status for edit option
 | |
| 	switch opts.RuleStatus {
 | |
| 	case "enable":
 | |
| 		status = Enabled
 | |
| 	case "disable":
 | |
| 		status = Disabled
 | |
| 	}
 | |
| 	arnStr := opts.Arn
 | |
| 	if opts.Arn == "" {
 | |
| 		arnStr = c.Role
 | |
| 	}
 | |
| 	tokens := strings.Split(arnStr, ":")
 | |
| 	if len(tokens) != 6 {
 | |
| 		return fmt.Errorf("invalid format for replication Arn")
 | |
| 	}
 | |
| 	if c.Role == "" { // for new configurations
 | |
| 		c.Role = opts.Arn
 | |
| 	}
 | |
| 	priority, err := strconv.Atoi(opts.Priority)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	newRule := Rule{
 | |
| 		ID:       opts.ID,
 | |
| 		Priority: priority,
 | |
| 		Status:   status,
 | |
| 		Filter:   filter,
 | |
| 		Destination: Destination{
 | |
| 			Bucket:       fmt.Sprintf("arn:aws:s3:::%s", tokens[5]),
 | |
| 			StorageClass: opts.StorageClass,
 | |
| 		},
 | |
| 		DeleteMarkerReplication: DeleteMarkerReplication{Status: Disabled},
 | |
| 	}
 | |
| 
 | |
| 	ruleFound := false
 | |
| 	for i, rule := range c.Rules {
 | |
| 		if rule.Priority == newRule.Priority && rule.ID != newRule.ID {
 | |
| 			return fmt.Errorf("Priority must be unique. Replication configuration already has a rule with this priority")
 | |
| 		}
 | |
| 		if rule.Destination.Bucket != newRule.Destination.Bucket {
 | |
| 			return fmt.Errorf("The destination bucket must be same for all rules")
 | |
| 		}
 | |
| 		if rule.ID != newRule.ID {
 | |
| 			continue
 | |
| 		}
 | |
| 		if opts.Priority == "" && rule.ID == newRule.ID {
 | |
| 			// inherit priority from existing rule, required field on server
 | |
| 			newRule.Priority = rule.Priority
 | |
| 		}
 | |
| 		if opts.RuleStatus == "" {
 | |
| 			newRule.Status = rule.Status
 | |
| 		}
 | |
| 		c.Rules[i] = newRule
 | |
| 		ruleFound = true
 | |
| 		break
 | |
| 	}
 | |
| 	// validate rule after overlaying priority for pre-existing rule being disabled.
 | |
| 	if err := newRule.Validate(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if !ruleFound && opts.Op == SetOption {
 | |
| 		return fmt.Errorf("Rule with ID %s not found in replication configuration", opts.ID)
 | |
| 	}
 | |
| 	if !ruleFound {
 | |
| 		c.Rules = append(c.Rules, newRule)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // RemoveRule removes a rule from replication config.
 | |
| func (c *Config) RemoveRule(opts Options) error {
 | |
| 	var newRules []Rule
 | |
| 	for _, rule := range c.Rules {
 | |
| 		if rule.ID != opts.ID {
 | |
| 			newRules = append(newRules, rule)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if len(newRules) == 0 {
 | |
| 		return fmt.Errorf("Replication configuration should have at least one rule")
 | |
| 	}
 | |
| 	c.Rules = newRules
 | |
| 	return nil
 | |
| 
 | |
| }
 | |
| 
 | |
| // Rule - a rule for replication configuration.
 | |
| type Rule struct {
 | |
| 	XMLName                 xml.Name                `xml:"Rule" json:"-"`
 | |
| 	ID                      string                  `xml:"ID,omitempty"`
 | |
| 	Status                  Status                  `xml:"Status"`
 | |
| 	Priority                int                     `xml:"Priority"`
 | |
| 	DeleteMarkerReplication DeleteMarkerReplication `xml:"DeleteMarkerReplication"`
 | |
| 	Destination             Destination             `xml:"Destination"`
 | |
| 	Filter                  Filter                  `xml:"Filter" json:"Filter"`
 | |
| }
 | |
| 
 | |
| // Validate validates the rule for correctness
 | |
| func (r Rule) Validate() error {
 | |
| 	if err := r.validateID(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := r.validateStatus(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if err := r.validateFilter(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if r.Priority < 0 && r.Status == Enabled {
 | |
| 		return fmt.Errorf("Priority must be set for the rule")
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // validateID - checks if ID is valid or not.
 | |
| func (r Rule) validateID() error {
 | |
| 	// cannot be longer than 255 characters
 | |
| 	if len(r.ID) > 255 {
 | |
| 		return fmt.Errorf("ID must be less than 255 characters")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // validateStatus - checks if status is valid or not.
 | |
| func (r Rule) validateStatus() error {
 | |
| 	// Status can't be empty
 | |
| 	if len(r.Status) == 0 {
 | |
| 		return fmt.Errorf("status cannot be empty")
 | |
| 	}
 | |
| 
 | |
| 	// Status must be one of Enabled or Disabled
 | |
| 	if r.Status != Enabled && r.Status != Disabled {
 | |
| 		return fmt.Errorf("status must be set to either Enabled or Disabled")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (r Rule) validateFilter() error {
 | |
| 	if err := r.Filter.Validate(); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Prefix - a rule can either have prefix under <filter></filter> or under
 | |
| // <filter><and></and></filter>. This method returns the prefix from the
 | |
| // location where it is available
 | |
| func (r Rule) Prefix() string {
 | |
| 	if r.Filter.Prefix != "" {
 | |
| 		return r.Filter.Prefix
 | |
| 	}
 | |
| 	return r.Filter.And.Prefix
 | |
| }
 | |
| 
 | |
| // Tags - a rule can either have tag under <filter></filter> or under
 | |
| // <filter><and></and></filter>. This method returns all the tags from the
 | |
| // rule in the format tag1=value1&tag2=value2
 | |
| func (r Rule) Tags() string {
 | |
| 	if len(r.Filter.And.Tags) != 0 {
 | |
| 		var buf bytes.Buffer
 | |
| 		for _, t := range r.Filter.And.Tags {
 | |
| 			if buf.Len() > 0 {
 | |
| 				buf.WriteString("&")
 | |
| 			}
 | |
| 			buf.WriteString(t.String())
 | |
| 		}
 | |
| 		return buf.String()
 | |
| 	}
 | |
| 	return ""
 | |
| }
 | |
| 
 | |
| // Filter - a filter for a replication configuration Rule.
 | |
| type Filter struct {
 | |
| 	XMLName xml.Name `xml:"Filter" json:"-"`
 | |
| 	Prefix  string   `json:"Prefix,omitempty"`
 | |
| 	And     And      `xml:"And,omitempty" json:"And,omitempty"`
 | |
| 	Tag     Tag      `xml:"Tag,omitempty" json:"Tag,omitempty"`
 | |
| }
 | |
| 
 | |
| // Validate - validates the filter element
 | |
| func (f Filter) Validate() error {
 | |
| 	// A Filter must have exactly one of Prefix, Tag, or And specified.
 | |
| 	if !f.And.isEmpty() {
 | |
| 		if f.Prefix != "" {
 | |
| 			return errInvalidFilter
 | |
| 		}
 | |
| 		if !f.Tag.IsEmpty() {
 | |
| 			return errInvalidFilter
 | |
| 		}
 | |
| 	}
 | |
| 	if f.Prefix != "" {
 | |
| 		if !f.Tag.IsEmpty() {
 | |
| 			return errInvalidFilter
 | |
| 		}
 | |
| 	}
 | |
| 	if !f.Tag.IsEmpty() {
 | |
| 		if err := f.Tag.Validate(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Tag - a tag for a replication configuration Rule filter.
 | |
| type Tag struct {
 | |
| 	XMLName xml.Name `json:"-"`
 | |
| 	Key     string   `xml:"Key,omitempty" json:"Key,omitempty"`
 | |
| 	Value   string   `xml:"Value,omitempty" json:"Value,omitempty"`
 | |
| }
 | |
| 
 | |
| func (tag Tag) String() string {
 | |
| 	return tag.Key + "=" + tag.Value
 | |
| }
 | |
| 
 | |
| // IsEmpty returns whether this tag is empty or not.
 | |
| func (tag Tag) IsEmpty() bool {
 | |
| 	return tag.Key == ""
 | |
| }
 | |
| 
 | |
| // Validate checks this tag.
 | |
| func (tag Tag) Validate() error {
 | |
| 	if len(tag.Key) == 0 || utf8.RuneCountInString(tag.Key) > 128 {
 | |
| 		return fmt.Errorf("Invalid Tag Key")
 | |
| 	}
 | |
| 
 | |
| 	if utf8.RuneCountInString(tag.Value) > 256 {
 | |
| 		return fmt.Errorf("Invalid Tag Value")
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Destination - destination in ReplicationConfiguration.
 | |
| type Destination struct {
 | |
| 	XMLName      xml.Name `xml:"Destination" json:"-"`
 | |
| 	Bucket       string   `xml:"Bucket" json:"Bucket"`
 | |
| 	StorageClass string   `xml:"StorageClass,omitempty" json:"StorageClass,omitempty"`
 | |
| }
 | |
| 
 | |
| // And - a tag to combine a prefix and multiple tags for replication configuration rule.
 | |
| type And struct {
 | |
| 	XMLName xml.Name `xml:"And,omitempty" json:"-"`
 | |
| 	Prefix  string   `xml:"Prefix,omitempty" json:"Prefix,omitempty"`
 | |
| 	Tags    []Tag    `xml:"Tag,omitempty" json:"Tags,omitempty"`
 | |
| }
 | |
| 
 | |
| // isEmpty returns true if Tags field is null
 | |
| func (a And) isEmpty() bool {
 | |
| 	return len(a.Tags) == 0 && a.Prefix == ""
 | |
| }
 | |
| 
 | |
| // Status represents Enabled/Disabled status
 | |
| type Status string
 | |
| 
 | |
| // Supported status types
 | |
| const (
 | |
| 	Enabled  Status = "Enabled"
 | |
| 	Disabled Status = "Disabled"
 | |
| )
 | |
| 
 | |
| // DeleteMarkerReplication - whether delete markers are replicated - https://docs.aws.amazon.com/AmazonS3/latest/dev/replication-add-config.html
 | |
| type DeleteMarkerReplication struct {
 | |
| 	Status Status `xml:"Status" json:"Status"` // should be set to "Disabled" by default
 | |
| }
 | |
| 
 | |
| // IsEmpty returns true if DeleteMarkerReplication is not set
 | |
| func (d DeleteMarkerReplication) IsEmpty() bool {
 | |
| 	return len(d.Status) == 0
 | |
| }
 |