blob: 8c231903e159d726875b8d04ffd2ab80db15372a [file] [log] [blame] [edit]
// Copyright 2020-2024 Buf Technologies, 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 bufmodule
import (
"context"
"errors"
"fmt"
"net/url"
"github.com/bufbuild/buf/private/pkg/slicesext"
"github.com/bufbuild/buf/private/pkg/syserror"
)
var (
// NopUploader is a no-op Uploader.
NopUploader Uploader = nopUploader{}
)
// Uploader uploads ModuleSets.
type Uploader interface {
// Upload uploads the given ModuleSet.
Upload(ctx context.Context, moduleSet ModuleSet, options ...UploadOption) ([]Commit, error)
}
// UploadOption is an option for an Upload.
type UploadOption func(*uploadOptions)
// UploadWithLabels returns a new UploadOption that adds the given labels.
//
// This can be called multiple times. The unique result set of labels will be used.
// We only ever allow one of labels or tags to be set.
func UploadWithLabels(labels ...string) UploadOption {
return func(uploadOptions *uploadOptions) {
uploadOptions.labels = append(uploadOptions.labels, labels...)
}
}
// UploadWithTags returns a new UploadOption that adds the given tags. This is handled
// separately from labels because we need to resolve the default label(s) when uploading.
//
// This can be called multiple times. The unique result set of tags will be used.
// We only ever allow one of labels or tags to be set.
func UploadWithTags(tags ...string) UploadOption {
return func(uploadOptions *uploadOptions) {
uploadOptions.tags = append(uploadOptions.tags, tags...)
}
}
// UploadWithCreateIfNotExist returns a new UploadOption that will result in the
// Modules being created on the registry with the given visibility and default label if they do not exist.
// If the default label name is not provided, the module will be created with the default label "main".
func UploadWithCreateIfNotExist(createModuleVisibility ModuleVisibility, createDefaultLabel string) UploadOption {
return func(uploadOptions *uploadOptions) {
uploadOptions.createIfNotExist = true
uploadOptions.createModuleVisibility = createModuleVisibility
uploadOptions.createDefaultLabel = createDefaultLabel
}
}
// UploadWithSourceControlURL returns a new UploadOption that will set the source control
// url for the module contents uploaded.
func UploadWithSourceControlURL(sourceControlURL string) UploadOption {
return func(uploadOptions *uploadOptions) {
uploadOptions.sourceControlURL = sourceControlURL
}
}
// UploadWithExcludeUnnamed returns a new UploadOption that will exclude unnamed modules.
func UploadWithExcludeUnnamed() UploadOption {
return func(uploadOptions *uploadOptions) {
uploadOptions.excludeUnnamed = true
}
}
// UploadOptions are the possible options for upload.
//
// This is used by Uploader implementations.
type UploadOptions interface {
// Labels returns the unique and sorted set of labels to add.
// Labels are set using the `--label` flag when calling `buf push` and represent the
// labels that are set when uploading module content.
Labels() []string
// CreateIfNotExist says to create Modules if they do not exist on the registry.
CreateIfNotExist() bool
// CreateModuleVisibility returns the visibility to create Modules with.
//
// Will always be present if CreateIfNotExist() is true.
CreateModuleVisibility() ModuleVisibility
// CreateDefaultLabel returns the default label to create Modules with. If this is an
// emptry string, then the Modules will be created with default label "main".
CreateDefaultLabel() string
// Tags returns unique and sorted set of tags to be added as labels.
// Tags are set using the `--tag` flag when calling `buf push`, and represent labels
// that are set **in addition to** the default label when uploading module content.
//
// The `--tag` flag is a legacy flag that we are continuing supporting. We need to
// handle tags differently from labels when uploading because we need to resolve the
// default label for each module.
//
// We disallow the use of `--tag` when the modules we are uploading to do not all have
// the same default label.
Tags() []string
// SourceControlURL returns the source control URL set by the user for the module
// contents uploaded. We set the same source control URL for all module contents.
SourceControlURL() string
// ExcludeUnnamed returns whether to exclude unnamed modules.
ExcludeUnnamed() bool
isUploadOptions()
}
// NewUploadOptions returns a new UploadOptions.
func NewUploadOptions(options []UploadOption) (UploadOptions, error) {
uploadOptions := newUploadOptions()
for _, option := range options {
option(uploadOptions)
}
if err := uploadOptions.validate(); err != nil {
return nil, err
}
return uploadOptions, nil
}
// *** PRIVATE ***
type nopUploader struct{}
func (nopUploader) Upload(context.Context, ModuleSet, ...UploadOption) ([]Commit, error) {
return nil, errors.New("unimplemented: no-op Uploader called")
}
type uploadOptions struct {
labels []string
tags []string
createIfNotExist bool
createModuleVisibility ModuleVisibility
createDefaultLabel string
sourceControlURL string
excludeUnnamed bool
}
func newUploadOptions() *uploadOptions {
return &uploadOptions{}
}
func (u *uploadOptions) Labels() []string {
return slicesext.ToUniqueSorted(u.labels)
}
func (u *uploadOptions) Tags() []string {
return slicesext.ToUniqueSorted(u.tags)
}
func (u *uploadOptions) CreateIfNotExist() bool {
return u.createIfNotExist
}
func (u *uploadOptions) CreateModuleVisibility() ModuleVisibility {
return u.createModuleVisibility
}
func (u *uploadOptions) CreateDefaultLabel() string {
return u.createDefaultLabel
}
func (u *uploadOptions) SourceControlURL() string {
return u.sourceControlURL
}
func (u *uploadOptions) ExcludeUnnamed() bool {
return u.excludeUnnamed
}
func (u *uploadOptions) validate() error {
if u.createIfNotExist && u.createModuleVisibility == 0 {
return errors.New("must set a valid ModuleVisibility if CreateIfNotExist was specified")
}
// We validate that only one of labels or tags is set.
// This is enforced at the flag level, so if more than one is set, we return a syserror.
if len(u.labels) > 0 && len(u.tags) > 0 {
return syserror.New("cannot set both labels and tags")
}
if u.sourceControlURL != "" {
if _, err := url.Parse(u.sourceControlURL); err != nil {
return fmt.Errorf("must set a valid url for the source control url: %w", err)
}
}
return nil
}
func (*uploadOptions) isUploadOptions() {}