Update devfile library (#7240)

* Update go.mod

Signed-off-by: thepetk <thepetk@gmail.com>

* Update vendor

Signed-off-by: thepetk <thepetk@gmail.com>

* Bump up devfile library to 2.2.2

Signed-off-by: thepetk <thepetk@gmail.com>

* Update vendor

Signed-off-by: thepetk <thepetk@gmail.com>

---------

Signed-off-by: thepetk <thepetk@gmail.com>
This commit is contained in:
Theofanis Petkos
2024-04-29 16:21:11 +01:00
committed by GitHub
parent cc028de028
commit 529805fbb3
317 changed files with 14819 additions and 7127 deletions

View File

@@ -25,12 +25,12 @@ import (
"fmt"
"io"
"os"
"os/exec"
"strconv"
"sync"
"github.com/containerd/containerd/log"
"github.com/klauspost/compress/zstd"
exec "golang.org/x/sys/execabs"
)
type (

View File

@@ -108,6 +108,12 @@ type Status struct {
// WalkFunc defines the callback for a blob walk.
type WalkFunc func(Info) error
// InfoReaderProvider provides both info and reader for the specific content.
type InfoReaderProvider interface {
InfoProvider
Provider
}
// InfoProvider provides info for content inspection.
type InfoProvider interface {
// Info will return metadata about content available in the content store.

View File

@@ -332,3 +332,14 @@ func copyWithBuffer(dst io.Writer, src io.Reader) (written int64, err error) {
}
return
}
// Exists returns whether an attempt to access the content would not error out
// with an ErrNotFound error. It will return an encountered error if it was
// different than ErrNotFound.
func Exists(ctx context.Context, provider InfoProvider, desc ocispec.Descriptor) (bool, error) {
_, err := provider.Info(ctx, desc.Digest)
if errdefs.IsNotFound(err) {
return false, nil
}
return err == nil, err
}

View File

@@ -23,7 +23,7 @@ var (
Package = "github.com/containerd/containerd"
// Version holds the complete version number. Filled in at linking time.
Version = "1.7.11+unknown"
Version = "1.7.13+unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package devfile
import (
"github.com/devfile/api/v2/pkg/validation/variables"
"github.com/devfile/library/v2/pkg/devfile/parser"
errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors"
"github.com/devfile/library/v2/pkg/devfile/validate"
)
@@ -121,7 +122,7 @@ func ParseDevfileAndValidate(args parser.ParserArgs) (d parser.DevfileObj, varWa
// generic validation on devfile content
err = validate.ValidateDevfileData(d.Data)
if err != nil {
return d, varWarning, err
return d, varWarning, &errPkg.NonCompliantDevfile{Err: err.Error()}
}
return d, varWarning, err

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -21,7 +21,7 @@ import (
"strings"
"github.com/devfile/library/v2/pkg/devfile/parser/data"
"github.com/pkg/errors"
errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors"
"k8s.io/klog"
)
@@ -32,7 +32,7 @@ func (d *DevfileCtx) SetDevfileAPIVersion() error {
var r map[string]interface{}
err := json.Unmarshal(d.rawContent, &r)
if err != nil {
return errors.Wrapf(err, "failed to decode devfile json")
return &errPkg.NonCompliantDevfile{Err: err.Error()}
}
// Get "schemaVersion" value from map for devfile V2
@@ -47,10 +47,10 @@ func (d *DevfileCtx) SetDevfileAPIVersion() error {
if okSchema {
// SchemaVersion cannot be empty
if schemaVersion.(string) == "" {
return fmt.Errorf("schemaVersion in devfile: %s cannot be empty", devfilePath)
return &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("schemaVersion in devfile: %s cannot be empty", devfilePath)}
}
} else {
return fmt.Errorf("schemaVersion not present in devfile: %s", devfilePath)
return &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("schemaVersion not present in devfile: %s", devfilePath)}
}
// Successful

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@ import (
"bytes"
"unicode"
errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors"
parserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util"
"github.com/devfile/library/v2/pkg/util"
"github.com/pkg/errors"
"k8s.io/klog"
@@ -41,7 +43,7 @@ func YAMLToJSON(data []byte) ([]byte, error) {
// Is YAML, convert to JSON
data, err := yaml.YAMLToJSON(data)
if err != nil {
return data, errors.Wrapf(err, "failed to convert devfile yaml to json")
return data, &errPkg.NonCompliantDevfile{Err: err.Error()}
}
// Successful
@@ -62,7 +64,7 @@ func hasPrefix(buf []byte, prefix []byte) bool {
}
// SetDevfileContent reads devfile and if devfile is in YAML format converts it to JSON
func (d *DevfileCtx) SetDevfileContent() error {
func (d *DevfileCtx) SetDevfileContent(devfileUtilsClient parserUtil.DevfileUtils) error {
var err error
var data []byte
@@ -72,7 +74,7 @@ func (d *DevfileCtx) SetDevfileContent() error {
if d.token != "" {
params.Token = d.token
}
data, err = util.DownloadInMemory(params)
data, err = devfileUtilsClient.DownloadInMemory(params)
if err != nil {
return errors.Wrap(err, "error getting devfile info from url")
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package parser
import (
"net/url"
parserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util"
"github.com/devfile/library/v2/pkg/testingutil/filesystem"
"github.com/devfile/library/v2/pkg/util"
"k8s.io/klog"
@@ -94,7 +95,7 @@ func (d *DevfileCtx) populateDevfile() (err error) {
}
// Populate fills the DevfileCtx struct with relevant context info
func (d *DevfileCtx) Populate() (err error) {
func (d *DevfileCtx) Populate(devfileUtilsClient parserUtil.DevfileUtils) (err error) {
d.relPath, err = lookupDevfileFromPath(d.fs, d.relPath)
if err != nil {
return err
@@ -104,20 +105,20 @@ func (d *DevfileCtx) Populate() (err error) {
}
klog.V(4).Infof("absolute devfile path: '%s'", d.absPath)
// Read and save devfile content
if err = d.SetDevfileContent(); err != nil {
if err = d.SetDevfileContent(devfileUtilsClient); err != nil {
return err
}
return d.populateDevfile()
}
// PopulateFromURL fills the DevfileCtx struct with relevant context info
func (d *DevfileCtx) PopulateFromURL() (err error) {
func (d *DevfileCtx) PopulateFromURL(devfileUtilsClient parserUtil.DevfileUtils) (err error) {
_, err = url.ParseRequestURI(d.url)
if err != nil {
return err
}
// Read and save devfile content
if err := d.SetDevfileContent(); err != nil {
if err := d.SetDevfileContent(devfileUtilsClient); err != nil {
return err
}
return d.populateDevfile()

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@ import (
"fmt"
"github.com/devfile/library/v2/pkg/devfile/parser/data"
errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors"
"github.com/pkg/errors"
"github.com/xeipuuv/gojsonschema"
"k8s.io/klog"
@@ -30,7 +31,7 @@ func (d *DevfileCtx) SetDevfileJSONSchema() error {
// Check if json schema is present for the given apiVersion
jsonSchema, err := data.GetDevfileJSONSchema(d.apiVersion)
if err != nil {
return err
return &errPkg.NonCompliantDevfile{Err: err.Error()}
}
d.jsonSchema = jsonSchema
return nil
@@ -54,7 +55,7 @@ func (d *DevfileCtx) ValidateDevfileSchema() error {
for _, desc := range result.Errors() {
errMsg = errMsg + fmt.Sprintf("- %s\n", desc)
}
return fmt.Errorf(errMsg)
return &errPkg.NonCompliantDevfile{Err: errMsg}
}
// Sucessful

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@ package data
import (
"fmt"
"reflect"
"sort"
"strings"
"k8s.io/klog"
@@ -51,6 +52,7 @@ func GetDevfileJSONSchema(version string) (string, error) {
for version := range devfileApiVersionToJSONSchema {
supportedVersions = append(supportedVersions, string(version))
}
sort.Strings(supportedVersions)
return "", fmt.Errorf("unable to find schema for version %q. The parser supports devfile schema for version %s", version, strings.Join(supportedVersions, ", "))
}
klog.V(4).Infof("devfile apiVersion '%s' is supported", version)

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -15,11 +15,11 @@
package version220
// https://raw.githubusercontent.com/devfile/api/main/schemas/latest/devfile.json
// https://raw.githubusercontent.com/devfile/api/v2.2.0/schemas/latest/devfile.json
const JsonSchema220 = `{
"description": "Devfile describes the structure of a cloud-native devworkspace and development environment.",
"type": "object",
"title": "Devfile schema - Version 2.2.1-alpha",
"title": "Devfile schema - Version 2.2.0",
"required": [
"schemaVersion"
],
@@ -212,7 +212,7 @@ const JsonSchema220 = `{
"additionalProperties": false
},
"hotReloadCapable": {
"description": "Specify whether the command is restarted or not when the source code changes. If set to 'true' the command won't be restarted. A *hotReloadCapable* 'run' or 'debug' command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* 'build' command is expected to be executed only once and won't be executed again. This field is taken into account only for commands 'build', 'run' and 'debug' with 'isDefault' set to 'true'.\n\nDefault value is 'false'",
"description": "Whether the command is capable to reload itself when source code changes. If set to 'true' the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is 'false'",
"type": "boolean"
},
"label": {
@@ -1104,7 +1104,7 @@ const JsonSchema220 = `{
"additionalProperties": false
},
"hotReloadCapable": {
"description": "Specify whether the command is restarted or not when the source code changes. If set to 'true' the command won't be restarted. A *hotReloadCapable* 'run' or 'debug' command is expected to handle file changes on its own and won't be restarted. A *hotReloadCapable* 'build' command is expected to be executed only once and won't be executed again. This field is taken into account only for commands 'build', 'run' and 'debug' with 'isDefault' set to 'true'.\n\nDefault value is 'false'",
"description": "Whether the command is capable to reload itself when source code changes. If set to 'true' the command won't be restarted and it is expected to handle file changes on its own.\n\nDefault value is 'false'",
"type": "boolean"
},
"label": {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,10 +17,11 @@ package v2
import (
"fmt"
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
"reflect"
"strings"
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
)
// GetCommands returns the slice of Command objects parsed from the Devfile

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,9 +17,10 @@ package v2
import (
"fmt"
"strings"
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
"strings"
)
// GetEvents returns the Events Object parsed from devfile

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,10 +17,11 @@ package v2
import (
"fmt"
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
"reflect"
"strings"
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
)
// GetProjects returns the Project Object parsed from devfile

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -22,6 +22,8 @@ import (
v200 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.0.0"
v210 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.1.0"
v220 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.2.0"
v221 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.2.1"
v222 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2/2.2.2"
)
// SupportedApiVersions stores the supported devfile API versions
@@ -32,6 +34,8 @@ const (
APISchemaVersion200 supportedApiVersion = "2.0.0"
APISchemaVersion210 supportedApiVersion = "2.1.0"
APISchemaVersion220 supportedApiVersion = "2.2.0"
APISchemaVersion221 supportedApiVersion = "2.2.1"
APISchemaVersion222 supportedApiVersion = "2.2.2"
APIVersionAlpha2 supportedApiVersion = "v1alpha2"
)
@@ -46,6 +50,8 @@ func init() {
apiVersionToDevfileStruct[APISchemaVersion200] = reflect.TypeOf(v2.DevfileV2{})
apiVersionToDevfileStruct[APISchemaVersion210] = reflect.TypeOf(v2.DevfileV2{})
apiVersionToDevfileStruct[APISchemaVersion220] = reflect.TypeOf(v2.DevfileV2{})
apiVersionToDevfileStruct[APISchemaVersion221] = reflect.TypeOf(v2.DevfileV2{})
apiVersionToDevfileStruct[APISchemaVersion222] = reflect.TypeOf(v2.DevfileV2{})
apiVersionToDevfileStruct[APIVersionAlpha2] = reflect.TypeOf(v2.DevfileV2{})
}
@@ -58,6 +64,8 @@ func init() {
devfileApiVersionToJSONSchema[APISchemaVersion200] = v200.JsonSchema200
devfileApiVersionToJSONSchema[APISchemaVersion210] = v210.JsonSchema210
devfileApiVersionToJSONSchema[APISchemaVersion220] = v220.JsonSchema220
devfileApiVersionToJSONSchema[APISchemaVersion221] = v221.JsonSchema221
devfileApiVersionToJSONSchema[APISchemaVersion222] = v222.JsonSchema222
// should use hightest v2 schema version since it is expected to be backward compatible with the same api version
devfileApiVersionToJSONSchema[APIVersionAlpha2] = v220.JsonSchema220
devfileApiVersionToJSONSchema[APIVersionAlpha2] = v222.JsonSchema222
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -0,0 +1,31 @@
//
// Copyright Red Hat
//
// 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 errors
import "fmt"
// NonCompliantDevfile returns an error if devfile parsing failed due to Non-Compliant Devfile
type NonCompliantDevfile struct {
Err string
}
func (e *NonCompliantDevfile) Error() string {
errMsg := "error parsing devfile because of non-compliant data"
if e.Err != "" {
errMsg = fmt.Sprintf("%s due to %v", errMsg, e.Err)
}
return errMsg
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -19,7 +19,6 @@ import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/url"
"os"
"path"
@@ -30,6 +29,7 @@ import (
devfileCtx "github.com/devfile/library/v2/pkg/devfile/parser/context"
"github.com/devfile/library/v2/pkg/devfile/parser/data"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors"
"github.com/devfile/library/v2/pkg/util"
registryLibrary "github.com/devfile/registry-support/registry-library/library"
"k8s.io/apimachinery/pkg/types"
@@ -37,6 +37,8 @@ import (
"k8s.io/klog"
"sigs.k8s.io/controller-runtime/pkg/client"
parserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util"
v1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
apiOverride "github.com/devfile/api/v2/pkg/utils/overriding"
"github.com/devfile/api/v2/pkg/validation"
@@ -63,7 +65,7 @@ func parseDevfile(d DevfileObj, resolveCtx *resolutionContextTree, tool resolver
// Unmarshal devfile content into devfile struct
err = json.Unmarshal(d.Ctx.GetDevfileContent(), &d.Data)
if err != nil {
return d, errors.Wrapf(err, "failed to decode devfile content")
return d, &errPkg.NonCompliantDevfile{Err: err.Error()}
}
if flattenedDevfile {
@@ -120,7 +122,7 @@ type ParserArgs struct {
// DownloadGitResources downloads the resources from Git repository if true
DownloadGitResources *bool
// DevfileUtilsClient exposes the interface for mock implementation.
DevfileUtilsClient DevfileUtils
DevfileUtilsClient parserUtil.DevfileUtils
}
// ImageSelectorArgs defines the structure to leverage for using image names as selectors after parsing the Devfile.
@@ -154,14 +156,14 @@ func ParseDevfile(args ParserArgs) (d DevfileObj, err error) {
if args.Data != nil {
d.Ctx, err = devfileCtx.NewByteContentDevfileCtx(args.Data)
if err != nil {
return d, errors.Wrap(err, "failed to set devfile content from bytes")
return d, err
}
} else if args.Path != "" {
d.Ctx = devfileCtx.NewDevfileCtx(args.Path)
} else if args.URL != "" {
d.Ctx = devfileCtx.NewURLDevfileCtx(args.URL)
} else {
return d, errors.Wrap(err, "the devfile source is not provided")
return d, fmt.Errorf("the devfile source is not provided")
}
if args.Token != "" {
@@ -169,7 +171,7 @@ func ParseDevfile(args ParserArgs) (d DevfileObj, err error) {
}
if args.DevfileUtilsClient == nil {
args.DevfileUtilsClient = NewDevfileUtilsClient()
args.DevfileUtilsClient = parserUtil.NewDevfileUtilsClient()
}
downloadGitResources := true
@@ -194,7 +196,7 @@ func ParseDevfile(args ParserArgs) (d DevfileObj, err error) {
d, err = populateAndParseDevfile(d, &resolutionContextTree{}, tool, flattenedDevfile)
if err != nil {
return d, errors.Wrap(err, "failed to populateAndParseDevfile")
return d, err
}
setBooleanDefaults := true
@@ -216,7 +218,7 @@ func ParseDevfile(args ParserArgs) (d DevfileObj, err error) {
if convertUriToInlined {
d.Ctx.SetConvertUriToInlined(true)
err = parseKubeResourceFromURI(d)
err = parseKubeResourceFromURI(d, tool.devfileUtilsClient)
if err != nil {
return d, err
}
@@ -241,21 +243,21 @@ type resolverTools struct {
// downloadGitResources downloads the resources from Git repository if true
downloadGitResources bool
// devfileUtilsClient exposes the Git Interface to be able to use mock implementation.
devfileUtilsClient DevfileUtils
devfileUtilsClient parserUtil.DevfileUtils
}
func populateAndParseDevfile(d DevfileObj, resolveCtx *resolutionContextTree, tool resolverTools, flattenedDevfile bool) (DevfileObj, error) {
var err error
if err = resolveCtx.hasCycle(); err != nil {
return DevfileObj{}, err
return DevfileObj{}, &errPkg.NonCompliantDevfile{Err: err.Error()}
}
// Fill the fields of DevfileCtx struct
if d.Ctx.GetURL() != "" {
err = d.Ctx.PopulateFromURL()
err = d.Ctx.PopulateFromURL(tool.devfileUtilsClient)
} else if d.Ctx.GetDevfileContent() != nil {
err = d.Ctx.PopulateFromRaw()
} else {
err = d.Ctx.Populate()
err = d.Ctx.Populate(tool.devfileUtilsClient)
}
if err != nil {
return d, err
@@ -328,7 +330,7 @@ func parseParentAndPlugin(d DevfileObj, resolveCtx *resolutionContextTree, tool
case parent.Kubernetes != nil:
parentDevfileObj, err = parseFromKubeCRD(parent.ImportReference, resolveCtx, tool)
default:
return fmt.Errorf("devfile parent does not define any resources")
err = &errPkg.NonCompliantDevfile{Err: "devfile parent does not define any resources"}
}
if err != nil {
return err
@@ -343,8 +345,9 @@ func parseParentAndPlugin(d DevfileObj, resolveCtx *resolutionContextTree, tool
if err != nil {
return fmt.Errorf("fail to parse version of parent devfile from: %v", resolveImportReference(parent.ImportReference))
}
if parentDevfileVerson.GreaterThan(mainDevfileVersion) {
return fmt.Errorf("the parent devfile version from %v is greater than the child devfile version from %v", resolveImportReference(parent.ImportReference), resolveImportReference(resolveCtx.importReference))
return &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("the parent devfile version from %v is greater than the child devfile version from %v", resolveImportReference(parent.ImportReference), resolveImportReference(resolveCtx.importReference))}
}
}
parentWorkspaceContent := parentDevfileObj.Data.GetDevfileWorkspaceSpecContent()
@@ -389,7 +392,7 @@ func parseParentAndPlugin(d DevfileObj, resolveCtx *resolutionContextTree, tool
case plugin.Kubernetes != nil:
pluginDevfileObj, err = parseFromKubeCRD(plugin.ImportReference, resolveCtx, tool)
default:
return fmt.Errorf("plugin %s does not define any resources", component.Name)
err = &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("plugin %s does not define any resources", component.Name)}
}
if err != nil {
return err
@@ -405,7 +408,7 @@ func parseParentAndPlugin(d DevfileObj, resolveCtx *resolutionContextTree, tool
return fmt.Errorf("fail to parse version of plugin devfile from: %v", resolveImportReference(component.Plugin.ImportReference))
}
if pluginDevfileVerson.GreaterThan(mainDevfileVersion) {
return fmt.Errorf("the plugin devfile version from %v is greater than the child devfile version from %v", resolveImportReference(component.Plugin.ImportReference), resolveImportReference(resolveCtx.importReference))
return &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("the plugin devfile version from %v is greater than the child devfile version from %v", resolveImportReference(component.Plugin.ImportReference), resolveImportReference(resolveCtx.importReference))}
}
}
pluginWorkspaceContent := pluginDevfileObj.Data.GetDevfileWorkspaceSpecContent()
@@ -459,7 +462,7 @@ func parseFromURI(importReference v1.ImportReference, curDevfileCtx devfileCtx.D
newUri = path.Join(path.Dir(curDevfileCtx.GetAbsPath()), uri)
d.Ctx = devfileCtx.NewDevfileCtx(newUri)
if util.ValidateFile(newUri) != nil {
return DevfileObj{}, fmt.Errorf("the provided path is not a valid filepath %s", newUri)
return DevfileObj{}, &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("the provided path is not a valid filepath %s", newUri)}
}
srcDir := path.Dir(newUri)
destDir := path.Dir(curDevfileCtx.GetAbsPath())
@@ -517,7 +520,7 @@ func parseFromRegistry(importReference v1.ImportReference, resolveCtx *resolutio
}
d.Ctx, err = devfileCtx.NewByteContentDevfileCtx(devfileContent)
if err != nil {
return d, errors.Wrap(err, "failed to set devfile content from bytes")
return d, err
}
newResolveCtx := resolveCtx.appendNode(importReference)
@@ -548,7 +551,7 @@ func parseFromRegistry(importReference v1.ImportReference, resolveCtx *resolutio
}
}
} else {
return DevfileObj{}, fmt.Errorf("failed to fetch from registry, registry URL is not provided")
return DevfileObj{}, &errPkg.NonCompliantDevfile{Err: "failed to fetch from registry, registry URL is not provided"}
}
return DevfileObj{}, fmt.Errorf("failed to get id: %s from registry URLs provided", id)
@@ -556,7 +559,7 @@ func parseFromRegistry(importReference v1.ImportReference, resolveCtx *resolutio
func getDevfileFromRegistry(id, registryURL, version string, httpTimeout *int) ([]byte, error) {
if !strings.HasPrefix(registryURL, "http://") && !strings.HasPrefix(registryURL, "https://") {
return nil, fmt.Errorf("the provided registryURL: %s is not a valid URL", registryURL)
return nil, &errPkg.NonCompliantDevfile{Err: fmt.Sprintf("the provided registryURL: %s is not a valid URL", registryURL)}
}
param := util.HTTPRequestParams{
URL: fmt.Sprintf("%s/devfiles/%s/%s", registryURL, id, version),
@@ -569,7 +572,7 @@ func getDevfileFromRegistry(id, registryURL, version string, httpTimeout *int) (
}
func getResourcesFromRegistry(id, registryURL, destDir string) error {
stackDir, err := ioutil.TempDir(os.TempDir(), fmt.Sprintf("registry-resources-%s", id))
stackDir, err := os.MkdirTemp(os.TempDir(), fmt.Sprintf("registry-resources-%s", id))
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
@@ -591,7 +594,7 @@ func getResourcesFromRegistry(id, registryURL, destDir string) error {
func parseFromKubeCRD(importReference v1.ImportReference, resolveCtx *resolutionContextTree, tool resolverTools) (d DevfileObj, err error) {
if tool.k8sClient == nil || tool.context == nil {
return DevfileObj{}, fmt.Errorf("Kubernetes client and context are required to parse from Kubernetes CRD")
return DevfileObj{}, fmt.Errorf("kubernetes client and context are required to parse from Kubernetes CRD")
}
namespace := importReference.Kubernetes.Namespace
@@ -765,7 +768,7 @@ func setEndpoints(endpoints []v1.Endpoint) {
}
// parseKubeResourceFromURI iterate through all kubernetes & openshift components, and parse from uri and update the content to inlined field in devfileObj
func parseKubeResourceFromURI(devObj DevfileObj) error {
func parseKubeResourceFromURI(devObj DevfileObj, devfileUtilsClient parserUtil.DevfileUtils) error {
getKubeCompOptions := common.DevfileOptions{
ComponentOptions: common.ComponentOptions{
ComponentType: v1.KubernetesComponentType,
@@ -787,7 +790,7 @@ func parseKubeResourceFromURI(devObj DevfileObj) error {
for _, kubeComp := range kubeComponents {
if kubeComp.Kubernetes != nil && kubeComp.Kubernetes.Uri != "" {
/* #nosec G601 -- not an issue, kubeComp is de-referenced in sequence*/
err := convertK8sLikeCompUriToInlined(&kubeComp, devObj.Ctx)
err := convertK8sLikeCompUriToInlined(&kubeComp, devObj.Ctx, devfileUtilsClient)
if err != nil {
return errors.Wrapf(err, "failed to convert kubernetes uri to inlined for component '%s'", kubeComp.Name)
}
@@ -800,7 +803,7 @@ func parseKubeResourceFromURI(devObj DevfileObj) error {
for _, openshiftComp := range openshiftComponents {
if openshiftComp.Openshift != nil && openshiftComp.Openshift.Uri != "" {
/* #nosec G601 -- not an issue, openshiftComp is de-referenced in sequence*/
err := convertK8sLikeCompUriToInlined(&openshiftComp, devObj.Ctx)
err := convertK8sLikeCompUriToInlined(&openshiftComp, devObj.Ctx, devfileUtilsClient)
if err != nil {
return errors.Wrapf(err, "failed to convert openshift uri to inlined for component '%s'", openshiftComp.Name)
}
@@ -814,14 +817,14 @@ func parseKubeResourceFromURI(devObj DevfileObj) error {
}
// convertK8sLikeCompUriToInlined read in kubernetes resources definition from uri and converts to kubernetest inlined field
func convertK8sLikeCompUriToInlined(component *v1.Component, d devfileCtx.DevfileCtx) error {
func convertK8sLikeCompUriToInlined(component *v1.Component, d devfileCtx.DevfileCtx, devfileUtilsClient parserUtil.DevfileUtils) error {
var uri string
if component.Kubernetes != nil {
uri = component.Kubernetes.Uri
} else if component.Openshift != nil {
uri = component.Openshift.Uri
}
data, err := getKubernetesDefinitionFromUri(uri, d)
data, err := getKubernetesDefinitionFromUri(uri, d, devfileUtilsClient)
if err != nil {
return err
}
@@ -841,7 +844,7 @@ func convertK8sLikeCompUriToInlined(component *v1.Component, d devfileCtx.Devfil
}
// getKubernetesDefinitionFromUri read in kubernetes resources definition from uri and returns the raw content
func getKubernetesDefinitionFromUri(uri string, d devfileCtx.DevfileCtx) ([]byte, error) {
func getKubernetesDefinitionFromUri(uri string, d devfileCtx.DevfileCtx, devfileUtilsClient parserUtil.DevfileUtils) ([]byte, error) {
// validate URI
err := validation.ValidateURI(uri)
if err != nil {
@@ -876,7 +879,7 @@ func getKubernetesDefinitionFromUri(uri string, d devfileCtx.DevfileCtx) ([]byte
if d.GetToken() != "" {
params.Token = d.GetToken()
}
data, err = util.DownloadInMemory(params)
data, err = devfileUtilsClient.DownloadInMemory(params)
if err != nil {
return nil, errors.Wrapf(err, "error getting kubernetes resources definition information")
}

View File

@@ -1,75 +0,0 @@
//
// Copyright 2023 Red Hat, 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 parser
import (
"fmt"
"github.com/devfile/library/v2/pkg/util"
"os"
"strings"
)
type MockDevfileUtilsClient struct {
ParentURLAlias string // Specify a valid git URL as an alias if using a localhost HTTP server in order to pass validation.
MockGitURL util.MockGitUrl
GitTestToken string // Mock Git token. Specify the string "valid-token" for the mock CloneGitRepo to pass
}
func NewMockDevfileUtilsClient() MockDevfileUtilsClient {
return MockDevfileUtilsClient{}
}
func (gc MockDevfileUtilsClient) DownloadGitRepoResources(url string, destDir string, token string) error {
//the url parameter that gets passed in will be the localhost IP of the test server, so it will fail all the validation checks. We will use the global testURL variable instead
//skip the Git Provider check since it'll fail
if util.IsGitProviderRepo(gc.ParentURLAlias) {
// this converts the test git URL to a mock URL
mockGitUrl := gc.MockGitURL
mockGitUrl.Token = gc.GitTestToken
if !mockGitUrl.IsFile || mockGitUrl.Revision == "" || !strings.Contains(mockGitUrl.Path, OutputDevfileYamlPath) {
return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url+"/"+mockGitUrl.Path)
}
stackDir, err := os.MkdirTemp("", fmt.Sprintf("git-resources"))
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
err = fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
}(stackDir)
err = mockGitUrl.CloneGitRepo(stackDir)
if err != nil {
return err
}
err = util.CopyAllDirFiles(stackDir, destDir)
if err != nil {
return err
}
} else {
return fmt.Errorf("Failed to download resources from parent devfile. Unsupported Git Provider for %s ", gc.ParentURLAlias)
}
return nil
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -20,6 +20,8 @@ import (
"fmt"
"io"
errPkg "github.com/devfile/library/v2/pkg/devfile/parser/errors"
parserUtil "github.com/devfile/library/v2/pkg/devfile/parser/util"
"github.com/devfile/library/v2/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/afero"
@@ -38,6 +40,8 @@ type YamlSrc struct {
Path string
// URL of the yaml file
URL string
// Token to access a private URL like a private repository
Token string
// Data is the yaml content in []byte format
Data []byte
}
@@ -56,15 +60,20 @@ type KubernetesResources struct {
// It returns all the parsed Kubernetes objects as an array of interface.
// Consumers interested in the Kubernetes resources are expected to Unmarshal
// it to the struct of the respective Kubernetes resource. If a Path is being passed,
// provide a filesystem, otherwise nil can be passed in
func ReadKubernetesYaml(src YamlSrc, fs *afero.Afero) ([]interface{}, error) {
// provide a filesystem, otherwise nil can be passed in.
// Pass in an optional client to use either the actual implementation or a mock implementation of the interface.
func ReadKubernetesYaml(src YamlSrc, fs *afero.Afero, devfileUtilsClient parserUtil.DevfileUtils) ([]interface{}, error) {
var data []byte
var err error
if src.URL != "" {
params := util.HTTPRequestParams{URL: src.URL}
data, err = util.DownloadInMemory(params)
if devfileUtilsClient == nil {
devfileUtilsClient = parserUtil.NewDevfileUtilsClient()
}
params := util.HTTPRequestParams{URL: src.URL, Token: src.Token}
data, err = devfileUtilsClient.DownloadInMemory(params)
if err != nil {
return nil, errors.Wrapf(err, "failed to download file %q", src.URL)
}
@@ -124,7 +133,7 @@ func ParseKubernetesYaml(values []interface{}) (KubernetesResources, error) {
var kubernetesMap map[string]interface{}
err = k8yaml.Unmarshal(byteData, &kubernetesMap)
if err != nil {
return KubernetesResources{}, err
return KubernetesResources{}, &errPkg.NonCompliantDevfile{Err: err.Error()}
}
kind := kubernetesMap["kind"]
@@ -147,7 +156,7 @@ func ParseKubernetesYaml(values []interface{}) (KubernetesResources, error) {
}
if err != nil {
return KubernetesResources{}, err
return KubernetesResources{}, &errPkg.NonCompliantDevfile{Err: err.Error()}
}
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -0,0 +1,23 @@
//
// Copyright Red Hat
//
// 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 util
import "github.com/devfile/library/v2/pkg/util"
type DevfileUtils interface {
DownloadGitRepoResources(url string, destDir string, token string) error
DownloadInMemory(params util.HTTPRequestParams) ([]byte, error)
}

View File

@@ -0,0 +1,147 @@
//
// Copyright Red Hat
//
// 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 util
import (
"fmt"
"net/http"
"os"
"github.com/devfile/library/v2/pkg/util"
)
// Default filenames for create devfile to be used in mocks
const (
OutputDevfileYamlPath = "devfile.yaml"
)
type MockDevfileUtilsClient struct {
// Specify a valid git URL as an alias if using a localhost HTTP server in order to pass validation.
ParentURLAlias string
// MockGitUrl struct for mocking git related ops
MockGitURL util.MockGitUrl
// Mock Git token. Specify the string "valid-token" for the mock CloneGitRepo to pass
GitTestToken string
// Options to specify what file download needs to be mocked
DownloadOptions util.MockDownloadOptions
}
func NewMockDevfileUtilsClient() MockDevfileUtilsClient {
return MockDevfileUtilsClient{}
}
func (gc *MockDevfileUtilsClient) DownloadInMemory(params util.HTTPRequestParams) ([]byte, error) {
var httpClient = &http.Client{Transport: &http.Transport{
ResponseHeaderTimeout: util.HTTPRequestResponseTimeout,
}, Timeout: util.HTTPRequestResponseTimeout}
if gc.MockGitURL.Host != "" {
if util.IsGitProviderRepo(gc.MockGitURL.Host) {
gc.MockGitURL.Token = gc.GitTestToken
}
} else if params.URL != "" {
// Not all clients have the ability to pass in mock data
// So we should be adaptable and use the function params
// and mock the output
if util.IsGitProviderRepo(params.URL) {
gc.MockGitURL.Host = params.URL
gc.MockGitURL.Token = params.Token
}
}
if gc.DownloadOptions.MockParent == nil {
gc.DownloadOptions.MockParent = &util.MockParent{}
}
file, err := gc.MockGitURL.DownloadInMemoryWithClient(params, httpClient, gc.DownloadOptions)
if gc.DownloadOptions.MockParent != nil && gc.DownloadOptions.MockParent.IsMainDevfileDownloaded && gc.DownloadOptions.MockParent.IsParentDevfileDownloaded {
// Since gc is a pointer, if both the main and parent devfiles are downloaded, reset the flag.
// So that other tests can use the Mock Parent Devfile download if required.
gc.DownloadOptions.MockParent.IsMainDevfileDownloaded = false
gc.DownloadOptions.MockParent.IsParentDevfileDownloaded = false
}
if gc.MockGitURL.Host != "" && params.URL != "" {
// Since gc is a pointer, reset the mock data if both the URL and Host are present
gc.MockGitURL.Host = ""
gc.MockGitURL.Token = ""
}
return file, err
}
func (gc MockDevfileUtilsClient) DownloadGitRepoResources(url string, destDir string, token string) error {
// if mock data is unavailable as certain clients cant provide mock data
// then adapt and create mock data from actual params
if gc.ParentURLAlias == "" {
gc.ParentURLAlias = url
gc.MockGitURL.IsFile = true
gc.MockGitURL.Revision = "main"
gc.MockGitURL.Path = OutputDevfileYamlPath
gc.MockGitURL.Host = "github.com"
gc.MockGitURL.Protocol = "https"
gc.MockGitURL.Owner = "devfile"
gc.MockGitURL.Repo = "library"
}
if gc.GitTestToken == "" {
gc.GitTestToken = token
}
//the url parameter that gets passed in will be the localhost IP of the test server, so it will fail all the validation checks. We will use the global testURL variable instead
//skip the Git Provider check since it'll fail
if util.IsGitProviderRepo(gc.ParentURLAlias) {
// this converts the test git URL to a mock URL
mockGitUrl := gc.MockGitURL
mockGitUrl.Token = gc.GitTestToken
if !mockGitUrl.IsFile || mockGitUrl.Revision == "" || !ValidateDevfileExistence((mockGitUrl.Path)) {
return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url+"/"+mockGitUrl.Path)
}
stackDir, err := os.MkdirTemp("", "git-resources")
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
defer func(path string) {
err = os.RemoveAll(path)
if err != nil {
err = fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
}(stackDir)
err = mockGitUrl.CloneGitRepo(stackDir)
if err != nil {
return err
}
err = util.CopyAllDirFiles(stackDir, destDir)
if err != nil {
return err
}
} else {
return fmt.Errorf("failed to download resources from parent devfile. Unsupported Git Provider for %s ", gc.ParentURLAlias)
}
return nil
}

View File

@@ -0,0 +1,98 @@
//
// Copyright Red Hat
//
// 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 util
import (
"fmt"
"os"
"path"
"strings"
"github.com/devfile/library/v2/pkg/util"
"github.com/hashicorp/go-multierror"
)
// Contains common naming conventions for devfiles to look for when downloading resources
var DevfilePossibilities = [...]string{"devfile.yaml", ".devfile.yaml", "devfile.yml", ".devfile.yml"}
type DevfileUtilsClient struct {
}
func NewDevfileUtilsClient() DevfileUtilsClient {
return DevfileUtilsClient{}
}
// DownloadInMemory is a wrapper to the util.DownloadInMemory() call.
// This is done to help devfile/library clients invoke this function with a client.
func (c DevfileUtilsClient) DownloadInMemory(params util.HTTPRequestParams) ([]byte, error) {
return util.DownloadInMemory(params)
}
// DownloadGitRepoResources downloads the git repository resources
func (c DevfileUtilsClient) DownloadGitRepoResources(url string, destDir string, token string) error {
var returnedErr error
if util.IsGitProviderRepo(url) {
gitUrl, err := util.NewGitURL(url, token)
if err != nil {
return err
}
if !gitUrl.IsFile || gitUrl.Revision == "" || !ValidateDevfileExistence((gitUrl.Path)) {
return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url)
}
stackDir, err := os.MkdirTemp("", "git-resources")
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
}
}(stackDir)
gitUrl.Token = token
err = gitUrl.CloneGitRepo(stackDir)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}
dir := path.Dir(path.Join(stackDir, gitUrl.Path))
err = util.CopyAllDirFiles(dir, destDir)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}
} else {
return fmt.Errorf("failed to download resources from parent devfile. Unsupported Git Provider for %s ", url)
}
return nil
}
// ValidateDevfileExistence verifies if any of the naming possibilities for devfile are present in the url path
func ValidateDevfileExistence(path string) bool {
for _, devfile := range DevfilePossibilities {
if strings.Contains(path, devfile) {
return true
}
}
return false
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,75 +17,13 @@ package parser
import (
"fmt"
"github.com/devfile/library/v2/pkg/util"
"github.com/hashicorp/go-multierror"
"os"
"path"
"reflect"
"strings"
devfilev1 "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
"github.com/devfile/library/v2/pkg/devfile/parser/data"
"github.com/devfile/library/v2/pkg/devfile/parser/data/v2/common"
)
type DevfileUtilsClient struct {
}
func NewDevfileUtilsClient() DevfileUtilsClient {
return DevfileUtilsClient{}
}
type DevfileUtils interface {
DownloadGitRepoResources(url string, destDir string, token string) error
}
// DownloadGitRepoResources mock implementation of the real method.
func (gc DevfileUtilsClient) DownloadGitRepoResources(url string, destDir string, token string) error {
var returnedErr error
if util.IsGitProviderRepo(url) {
gitUrl, err := util.NewGitURL(url, token)
if err != nil {
return err
}
if !gitUrl.IsFile || gitUrl.Revision == "" || !strings.Contains(gitUrl.Path, OutputDevfileYamlPath) {
return fmt.Errorf("error getting devfile from url: failed to retrieve %s", url)
}
stackDir, err := os.MkdirTemp("", fmt.Sprintf("git-resources"))
if err != nil {
return fmt.Errorf("failed to create dir: %s, error: %v", stackDir, err)
}
defer func(path string) {
err := os.RemoveAll(path)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
}
}(stackDir)
gitUrl.Token = token
err = gitUrl.CloneGitRepo(stackDir)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}
dir := path.Dir(path.Join(stackDir, gitUrl.Path))
err = util.CopyAllDirFiles(dir, destDir)
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
return returnedErr
}
} else {
return fmt.Errorf("Failed to download resources from parent devfile. Unsupported Git Provider for %s ", url)
}
return nil
}
// GetDeployComponents gets the default deploy command associated components
func GetDeployComponents(devfileData data.DevfileData) (map[string]string, error) {
deployCommandFilter := common.DevfileOptions{

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -17,6 +17,7 @@ package validate
import (
"fmt"
v2Validation "github.com/devfile/api/v2/pkg/validation"
devfileData "github.com/devfile/library/v2/pkg/devfile/parser/data"
v2 "github.com/devfile/library/v2/pkg/devfile/parser/data/v2"

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -22,13 +22,12 @@ limitations under the License.
package filesystem
import (
"io/ioutil"
"os"
"path/filepath"
"time"
)
// DefaultFs implements Filesystem using same-named functions from "os" and "io/ioutil"
// DefaultFs implements Filesystem using same-named functions from "os"
type DefaultFs struct{}
var _ Filesystem = DefaultFs{}
@@ -97,33 +96,64 @@ func (DefaultFs) Getwd() (dir string, err error) {
return os.Getwd()
}
// ReadFile via ioutil.ReadFile
// ReadFile via os.ReadFile
func (DefaultFs) ReadFile(filename string) ([]byte, error) {
return ioutil.ReadFile(filename)
return os.ReadFile(filename)
}
// WriteFile via ioutil.WriteFile
// WriteFile via os.WriteFile
func (DefaultFs) WriteFile(filename string, data []byte, perm os.FileMode) error {
return ioutil.WriteFile(filename, data, perm)
return os.WriteFile(filename, data, perm)
}
// MkdirTemp via os.MkdirTemp
func (DefaultFs) MkdirTemp(dir, prefix string) (string, error) {
return os.MkdirTemp(dir, prefix)
}
// TempDir via ioutil.TempDir
func (DefaultFs) TempDir(dir, prefix string) (string, error) {
return ioutil.TempDir(dir, prefix)
// Deprecated: as ioutil.TempDir is deprecated TempDir is replaced by MkdirTemp which uses os.MkdirTemp.
// TempDir now uses MkdirTemp.
func (fs DefaultFs) TempDir(dir, prefix string) (string, error) {
return fs.MkdirTemp(dir, prefix)
}
// TempFile via ioutil.TempFile
func (DefaultFs) TempFile(dir, prefix string) (File, error) {
file, err := ioutil.TempFile(dir, prefix)
// CreateTemp via os.CreateTemp
func (DefaultFs) CreateTemp(dir, prefix string) (File, error) {
file, err := os.CreateTemp(dir, prefix)
if err != nil {
return nil, err
}
return &defaultFile{file}, nil
}
// ReadDir via ioutil.ReadDir
// TempFile via ioutil.TempFile
// Deprecated: as ioutil.TempFile is deprecated TempFile is replaced by CreateTemp which uses os.CreateTemp.
// TempFile now uses CreateTemp.
func (fs DefaultFs) TempFile(dir, prefix string) (File, error) {
return fs.CreateTemp(dir, prefix)
}
// ReadDir via os.ReadDir
func (DefaultFs) ReadDir(dirname string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dirname)
dirEntries, err := os.ReadDir(dirname)
if err != nil {
return []os.FileInfo{}, err
}
dirsInfo := make([]os.FileInfo, 0, len(dirEntries))
for _, dirEntry := range dirEntries {
info, err := dirEntry.Info()
if err != nil {
return dirsInfo, err
}
dirsInfo = append(dirsInfo, info)
}
return dirsInfo, nil
}
// Walk via filepath.Walk

View File

@@ -41,13 +41,13 @@ type Filesystem interface {
Remove(name string) error
Chmod(name string, mode os.FileMode) error
Getwd() (dir string, err error)
// from "io/ioutil"
ReadFile(filename string) ([]byte, error)
WriteFile(filename string, data []byte, perm os.FileMode) error
TempDir(dir, prefix string) (string, error)
TempFile(dir, prefix string) (File, error)
ReadDir(dirname string) ([]os.FileInfo, error)
// from "filepath"
Walk(root string, walkFn filepath.WalkFunc) error
}

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
package util
import (
"io/ioutil"
"os"
"path/filepath"
"time"
@@ -26,11 +25,21 @@ import (
// cleanHttpCache checks cacheDir and deletes all files that were modified more than cacheTime back
func cleanHttpCache(cacheDir string, cacheTime time.Duration) error {
cacheFiles, err := ioutil.ReadDir(cacheDir)
cacheEntries, err := os.ReadDir(cacheDir)
if err != nil {
return err
}
cacheFiles := make([]os.FileInfo, 0, len(cacheEntries))
for _, cacheEntry := range cacheEntries {
info, err := cacheEntry.Info()
if err != nil {
return err
}
cacheFiles = append(cacheFiles, info)
}
for _, f := range cacheFiles {
if f.ModTime().Add(cacheTime).Before(time.Now()) {
klog.V(4).Infof("Removing cache file %s, because it is older than %s", f.Name(), cacheTime.String())

View File

@@ -1,5 +1,5 @@
//
// Copyright 2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -33,6 +33,18 @@ type MockGitUrl struct {
IsFile bool // defines if the URL points to a file in the repo
}
type MockDownloadOptions struct {
MockDevfile bool
MockDockerfile bool
MockFile string
MockParent *MockParent
}
type MockParent struct {
IsMainDevfileDownloaded bool
IsParentDevfileDownloaded bool
}
func (m *MockGitUrl) GetToken() string {
return m.Token
}
@@ -51,6 +63,8 @@ var mockExecute = func(baseDir string, cmd CommandType, args ...string) ([]byte,
// private repository
if hasPassword {
switch password {
case "parent-devfile":
fallthrough
case "valid-token":
_, err := resourceFile.WriteString("private repo\n")
if err != nil {
@@ -130,6 +144,236 @@ func (m *MockGitUrl) CloneGitRepo(destDir string) error {
return nil
}
var mockDevfile = `
schemaVersion: 2.2.0
metadata:
displayName: Go Mock Runtime
icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg
language: go
name: go
projectType: go
tags:
- Go
version: 1.0.0
components:
- container:
image: golang:latest
memoryLimit: 1024Mi
mountSources: true
sourceMapping: /project
name: runtime
- name: image-build
image:
imageName: go-image:latest
dockerfile:
uri: docker/Dockerfile
buildContext: .
rootRequired: false
- name: kubernetes-deploy
kubernetes:
inlined: |-
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
test: test
name: deploy-sample
endpoints:
- name: http-8081
targetPort: 8081
path: /
commands:
- exec:
commandLine: GOCACHE=/project/.cache go build main.go
component: runtime
group:
kind: build
workingDir: /project
id: build
- exec:
commandLine: ./main
component: runtime
group:
kind: run
workingDir: /project
id: run
- id: build-image
apply:
component: image-build
- id: deployk8s
apply:
component: kubernetes-deploy
- id: deploy
composite:
commands:
- build-image
- deployk8s
group:
kind: deploy
isDefault: true
`
var MockDevfileWithParentRef = `
schemaVersion: 2.2.0
metadata:
displayName: Go Mock Runtime
icon: https://raw.githubusercontent.com/devfile-samples/devfile-stack-icons/main/golang.svg
language: go
name: go
projectType: go
tags:
- Go
version: 1.0.0
parent:
uri: https://github.com/private-url-devfile
components:
- container:
image: golang:latest
memoryLimit: 1024Mi
mountSources: true
sourceMapping: /project
name: runtime
- name: image-build
image:
imageName: go-image:latest
dockerfile:
uri: docker/Dockerfile
buildContext: .
rootRequired: false
- name: kubernetes-deploy
kubernetes:
inlined: |-
apiVersion: apps/v1
kind: Deployment
metadata:
creationTimestamp: null
labels:
test: test
name: deploy-sample
endpoints:
- name: http-8081
targetPort: 8081
path: /
commands:
- exec:
commandLine: GOCACHE=/project/.cache go build main.go
component: runtime
group:
kind: build
workingDir: /project
id: build
- exec:
commandLine: ./main
component: runtime
group:
kind: run
workingDir: /project
id: run
- id: build-image
apply:
component: image-build
- id: deployk8s
apply:
component: kubernetes-deploy
- id: deploy
composite:
commands:
- build-image
- deployk8s
group:
kind: deploy
isDefault: true
`
var MockParentDevfile = `
schemaVersion: 2.2.0
metadata:
displayName: Go Mock Parent
language: go
name: goparent
projectType: go
tags:
- Go
version: 1.0.0
components:
- container:
endpoints:
- name: http
targetPort: 8080
image: golang:latest
memoryLimit: 1024Mi
mountSources: true
sourceMapping: /project
name: runtime2
commands:
- exec:
commandLine: GOCACHE=/project/.cache go build main.go
component: runtime2
group:
isDefault: true
kind: build
workingDir: /project
id: build2
- exec:
commandLine: ./main
component: runtime2
group:
isDefault: true
kind: run
workingDir: /project
id: run2
`
var mockDockerfile = `
FROM python:slim
WORKDIR /projects
RUN python3 -m venv venv
RUN . venv/bin/activate
# optimize image caching
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8081
CMD [ "waitress-serve", "--port=8081", "app:app"]
`
func (m MockGitUrl) DownloadInMemoryWithClient(params HTTPRequestParams, httpClient HTTPClient, options MockDownloadOptions) ([]byte, error) {
if m.GetToken() == "valid-token" {
switch {
case options.MockDevfile:
return []byte(mockDevfile), nil
case options.MockDockerfile:
return []byte(mockDockerfile), nil
case len(options.MockFile) > 0:
return []byte(options.MockFile), nil
default:
return []byte(mockDevfile), nil
}
} else if m.GetToken() == "parent-devfile" {
if options.MockParent != nil && !options.MockParent.IsMainDevfileDownloaded {
options.MockParent.IsMainDevfileDownloaded = true
return []byte(MockDevfileWithParentRef), nil
}
if options.MockParent != nil && !options.MockParent.IsParentDevfileDownloaded {
options.MockParent.IsParentDevfileDownloaded = true
return []byte(MockParentDevfile), nil
}
} else if m.GetToken() == "" {
// if no token is provided, assume normal operation
return DownloadInMemory(params)
}
return nil, fmt.Errorf("failed to retrieve %s", params.URL)
}
func (m *MockGitUrl) SetToken(token string) error {
m.Token = token
return nil

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022-2023 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -21,12 +21,7 @@ import (
"bytes"
"crypto/rand"
"fmt"
gitpkg "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/gregjones/httpcache"
"github.com/gregjones/httpcache/diskcache"
"io"
"io/ioutil"
"math/big"
"net"
"net/http"
@@ -45,6 +40,11 @@ import (
"syscall"
"time"
gitpkg "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/gregjones/httpcache"
"github.com/gregjones/httpcache/diskcache"
"github.com/devfile/library/v2/pkg/testingutil/filesystem"
"github.com/fatih/color"
"github.com/gobwas/glob"
@@ -819,7 +819,7 @@ func HTTPGetRequest(request HTTPRequestParams, cacheFor int) ([]byte, error) {
}
// Process http response
bytes, err := ioutil.ReadAll(resp.Body)
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@@ -855,7 +855,11 @@ func FilterIgnores(filesChanged, filesDeleted, absIgnoreRules []string) (filesCh
// IsValidProjectDir checks that the folder to download the project from devfile is
// either empty or only contains the devfile used.
func IsValidProjectDir(path string, devfilePath string) error {
files, err := ioutil.ReadDir(path)
return isValidProjectDirOnFS(path, devfilePath, filesystem.DefaultFs{})
}
func isValidProjectDirOnFS(path string, devfilePath string, fs filesystem.Filesystem) error {
files, err := fs.ReadDir(path)
if err != nil {
return err
}
@@ -1090,29 +1094,31 @@ func DownloadFileInMemory(url string) ([]byte, error) {
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
return io.ReadAll(resp.Body)
}
// DownloadInMemory uses HTTPRequestParams to download the file and return bytes
// DownloadInMemory uses HTTPRequestParams to download the file and return bytes.
// Use the pkg/devfile/parser/utils.go DownloadInMemory() invocation if you want to
// call with a client instead.
func DownloadInMemory(params HTTPRequestParams) ([]byte, error) {
var httpClient = &http.Client{Transport: &http.Transport{
ResponseHeaderTimeout: HTTPRequestResponseTimeout,
}, Timeout: HTTPRequestResponseTimeout}
var g GitUrl
var g *GitUrl
var err error
if IsGitProviderRepo(params.URL) {
g, err = NewGitUrlWithURL(params.URL)
g, err = NewGitURL(params.URL, params.Token)
if err != nil {
return nil, errors.Errorf("failed to parse git repo. error: %v", err)
}
}
return downloadInMemoryWithClient(params, httpClient, g)
return g.downloadInMemoryWithClient(params, httpClient)
}
func downloadInMemoryWithClient(params HTTPRequestParams, httpClient HTTPClient, g GitUrl) ([]byte, error) {
func (g *GitUrl) downloadInMemoryWithClient(params HTTPRequestParams, httpClient HTTPClient) ([]byte, error) {
var url string
url = params.URL
req, err := http.NewRequest("GET", url, nil)
@@ -1144,7 +1150,7 @@ func downloadInMemoryWithClient(params HTTPRequestParams, httpClient HTTPClient,
}
defer resp.Body.Close()
return ioutil.ReadAll(resp.Body)
return io.ReadAll(resp.Body)
}
// ValidateK8sResourceName sanitizes kubernetes resource name with the following requirements:

View File

@@ -1,5 +1,5 @@
//
// Copyright 2022 Red Hat, Inc.
// Copyright Red Hat
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.

View File

@@ -1,17 +1,17 @@
/* Copyright 2020-2022 Red Hat, 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.
*/
//
// Copyright Red Hat
//
// 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 library
@@ -20,7 +20,6 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"os"
@@ -66,7 +65,7 @@ type Registry struct {
err error
}
//TelemetryData structure to pass in client telemetry information
// TelemetryData structure to pass in client telemetry information
// The User and Locale fields should be passed in by clients if telemetry opt-in is enabled
// the generic Client name will be passed in regardless of opt-in/out choice. The value
// will be assigned to the UserId field for opt-outs
@@ -179,7 +178,7 @@ func GetRegistryIndex(registryURL string, options RegistryOptions, devfileTypes
if err != nil {
return nil, err
}
bytes, err := ioutil.ReadAll(resp.Body)
bytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
@@ -454,7 +453,7 @@ func DownloadStarterProjectAsBytes(registryURL string, stack string, starterProj
}
// Return downloaded starter project as bytes or error if unsuccessful.
return ioutil.ReadAll(resp.Body)
return io.ReadAll(resp.Body)
}
// IsStarterProjectExists checks if starter project exists for a given stack

View File

@@ -1,17 +1,17 @@
/* Copyright 2020-2022 Red Hat, 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.
*/
//
// Copyright Red Hat
//
// 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 library
@@ -20,7 +20,6 @@ import (
"compress/gzip"
"crypto/tls"
"fmt"
"github.com/hashicorp/go-multierror"
"io"
"log"
"net/http"
@@ -30,6 +29,8 @@ import (
"regexp"
"strings"
"time"
"github.com/hashicorp/go-multierror"
)
// SplitVersionFromStack takes a stack/version tag and splits the stack name from the version
@@ -112,7 +113,7 @@ func decompress(targetDir string, tarFile string, excludeFiles []string) error {
continue
}
target := path.Join(targetDir, filepath.Clean(header.Name))
target := CleanFilepath(targetDir, header.Name)
switch header.Typeflag {
case tar.TypeDir:
err = os.MkdirAll(target, os.FileMode(header.Mode))
@@ -121,7 +122,6 @@ func decompress(targetDir string, tarFile string, excludeFiles []string) error {
return returnedErr
}
case tar.TypeReg:
/* #nosec G304 -- target is produced using path.Join which cleans the dir path */
w, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
returnedErr = multierror.Append(returnedErr, err)
@@ -156,7 +156,7 @@ func isExcluded(name string, excludeFiles []string) bool {
return false
}
//setHeaders sets the request headers
// setHeaders sets the request headers
func setHeaders(headers *http.Header, options RegistryOptions) {
t := options.Telemetry
if t.User != "" {
@@ -170,7 +170,7 @@ func setHeaders(headers *http.Header, options RegistryOptions) {
}
}
//getHTTPClient returns a new http client object
// getHTTPClient returns a new http client object
func getHTTPClient(options RegistryOptions) *http.Client {
overriddenTimeout := httpRequestResponseTimeout
@@ -191,3 +191,10 @@ func getHTTPClient(options RegistryOptions) *http.Client {
Timeout: overriddenTimeout,
}
}
// Cleans a child path to ensure that there is no escaping from the parent directory with the use of ../ escape methods
// Ensures that the child path is always contained and absolutely pathed from the parent
func CleanFilepath(parent string, child string)string{
target := path.Join(parent, filepath.Clean("/"+child))
return target
}

View File

@@ -32,7 +32,7 @@ func FamiliarString(ref Reference) string {
}
// FamiliarMatch reports whether ref matches the specified pattern.
// See https://godoc.org/path#Match for supported patterns.
// See [path.Match] for supported patterns.
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
matched, err := path.Match(pattern, FamiliarString(ref))
if namedRef, isNamed := ref.(Named); isNamed && !matched {

View File

@@ -4,15 +4,39 @@ import (
"fmt"
"strings"
"github.com/distribution/distribution/v3/digestset"
"github.com/opencontainers/go-digest"
)
var (
const (
// legacyDefaultDomain is the legacy domain for Docker Hub (which was
// originally named "the Docker Index"). This domain is still used for
// authentication and image search, which were part of the "v1" Docker
// registry specification.
//
// This domain will continue to be supported, but there are plans to consolidate
// legacy domains to new "canonical" domains. Once those domains are decided
// on, we must update the normalization functions, but preserve compatibility
// with existing installs, clients, and user configuration.
legacyDefaultDomain = "index.docker.io"
defaultDomain = "docker.io"
officialRepoName = "library"
defaultTag = "latest"
// defaultDomain is the default domain used for images on Docker Hub.
// It is used to normalize "familiar" names to canonical names, for example,
// to convert "ubuntu" to "docker.io/library/ubuntu:latest".
//
// Note that actual domain of Docker Hub's registry is registry-1.docker.io.
// This domain will continue to be supported, but there are plans to consolidate
// legacy domains to new "canonical" domains. Once those domains are decided
// on, we must update the normalization functions, but preserve compatibility
// with existing installs, clients, and user configuration.
defaultDomain = "docker.io"
// officialRepoPrefix is the namespace used for official images on Docker Hub.
// It is used to normalize "familiar" names to canonical names, for example,
// to convert "ubuntu" to "docker.io/library/ubuntu:latest".
officialRepoPrefix = "library/"
// defaultTag is the default tag if no tag is provided.
defaultTag = "latest"
)
// normalizedNamed represents a name which has been
@@ -34,14 +58,14 @@ func ParseNormalizedNamed(s string) (Named, error) {
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
}
domain, remainder := splitDockerDomain(s)
var remoteName string
var remote string
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
remoteName = remainder[:tagSep]
remote = remainder[:tagSep]
} else {
remoteName = remainder
remote = remainder
}
if strings.ToLower(remoteName) != remoteName {
return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remoteName)
if strings.ToLower(remote) != remote {
return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remote)
}
ref, err := Parse(domain + "/" + remainder)
@@ -55,41 +79,53 @@ func ParseNormalizedNamed(s string) (Named, error) {
return named, nil
}
// ParseDockerRef normalizes the image reference following the docker convention. This is added
// mainly for backward compatibility.
// The reference returned can only be either tagged or digested. For reference contains both tag
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
// namedTaggedDigested is a reference that has both a tag and a digest.
type namedTaggedDigested interface {
NamedTagged
Digested
}
// ParseDockerRef normalizes the image reference following the docker convention,
// which allows for references to contain both a tag and a digest. It returns a
// reference that is either tagged or digested. For references containing both
// a tag and a digest, it returns a digested reference. For example, the following
// reference:
//
// docker.io/library/busybox:latest@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
//
// Is returned as a digested reference (with the ":latest" tag removed):
//
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
//
// References that are already "tagged" or "digested" are returned unmodified:
//
// // Already a digested reference
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
//
// // Already a named reference
// docker.io/library/busybox:latest
func ParseDockerRef(ref string) (Named, error) {
named, err := ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, ok := named.(NamedTagged); ok {
if canonical, ok := named.(Canonical); ok {
// The reference is both tagged and digested, only
// return digested.
newNamed, err := WithName(canonical.Name())
if err != nil {
return nil, err
}
newCanonical, err := WithDigest(newNamed, canonical.Digest())
if err != nil {
return nil, err
}
return newCanonical, nil
if canonical, ok := named.(namedTaggedDigested); ok {
// The reference is both tagged and digested; only return digested.
newNamed, err := WithName(canonical.Name())
if err != nil {
return nil, err
}
return WithDigest(newNamed, canonical.Digest())
}
return TagNameOnly(named), nil
}
// splitDockerDomain splits a repository name to domain and remotename string.
// splitDockerDomain splits a repository name to domain and remote-name.
// If no valid domain is found, the default domain is used. Repository name
// needs to be already validated before.
func splitDockerDomain(name string) (domain, remainder string) {
i := strings.IndexRune(name, '/')
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost" && strings.ToLower(name[:i]) == name[:i]) {
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != localhost && strings.ToLower(name[:i]) == name[:i]) {
domain, remainder = defaultDomain, name
} else {
domain, remainder = name[:i], name[i+1:]
@@ -98,7 +134,7 @@ func splitDockerDomain(name string) (domain, remainder string) {
domain = defaultDomain
}
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
remainder = officialRepoName + "/" + remainder
remainder = officialRepoPrefix + remainder
}
return
}
@@ -118,8 +154,15 @@ func familiarizeName(named namedRepository) repository {
if repo.domain == defaultDomain {
repo.domain = ""
// Handle official repositories which have the pattern "library/<official repo name>"
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
repo.path = split[1]
if strings.HasPrefix(repo.path, officialRepoPrefix) {
// TODO(thaJeztah): this check may be too strict, as it assumes the
// "library/" namespace does not have nested namespaces. While this
// is true (currently), technically it would be possible for Docker
// Hub to use those (e.g. "library/distros/ubuntu:latest").
// See https://github.com/distribution/distribution/pull/3769#issuecomment-1302031785.
if remainder := strings.TrimPrefix(repo.path, officialRepoPrefix); !strings.ContainsRune(remainder, '/') {
repo.path = remainder
}
}
}
return repo
@@ -179,20 +222,3 @@ func ParseAnyReference(ref string) (Reference, error) {
return ParseNormalizedNamed(ref)
}
// ParseAnyReferenceWithSet parses a reference string as a possible short
// identifier to be matched in a digest set, a full digest, or familiar name.
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
dgst, err := ds.Lookup(ref)
if err == nil {
return digestReference(dgst), nil
}
} else {
if dgst, err := digest.Parse(ref); err == nil {
return digestReference(dgst), nil
}
}
return ParseNormalizedNamed(ref)
}

View File

@@ -3,13 +3,16 @@
//
// Grammar
//
// reference := name [ ":" tag ] [ "@" digest ]
// name := [domain '/'] path-component ['/' path-component]*
// domain := domain-component ['.' domain-component]* [':' port-number]
// reference := name [ ":" tag ] [ "@" digest ]
// name := [domain '/'] remote-name
// domain := host [':' port-number]
// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A
// domain-name := domain-component ['.' domain-component]*
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// path-component := alpha-numeric [separator alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/
// path (or "remote-name") := path-component ['/' path-component]*
// alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/
//
// tag := /[\w][\w.-]{0,127}/
@@ -21,7 +24,6 @@
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
//
// identifier := /[a-f0-9]{64}/
// short-identifier := /[a-f0-9]{6,64}/
package reference
import (
@@ -145,7 +147,7 @@ type namedRepository interface {
Path() string
}
// Domain returns the domain part of the Named reference
// Domain returns the domain part of the [Named] reference.
func Domain(named Named) string {
if r, ok := named.(namedRepository); ok {
return r.Domain()
@@ -154,7 +156,7 @@ func Domain(named Named) string {
return domain
}
// Path returns the name without the domain part of the Named reference
// Path returns the name without the domain part of the [Named] reference.
func Path(named Named) (name string) {
if r, ok := named.(namedRepository); ok {
return r.Path()
@@ -175,7 +177,8 @@ func splitDomain(name string) (string, string) {
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
// DEPRECATED: Use Domain or Path
//
// Deprecated: Use [Domain] or [Path].
func SplitHostname(named Named) (string, string) {
if r, ok := named.(namedRepository); ok {
return r.Domain(), r.Path()
@@ -185,7 +188,6 @@ func SplitHostname(named Named) (string, string) {
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {
matches := ReferenceRegexp.FindStringSubmatch(s)
if matches == nil {
@@ -237,7 +239,6 @@ func Parse(s string) (Reference, error) {
// the Named interface. The reference must have a name and be in the canonical
// form, otherwise an error is returned.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
named, err := ParseNormalizedNamed(s)
if err != nil {
@@ -320,11 +321,13 @@ func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
// TrimNamed removes any tag or digest from the named reference.
func TrimNamed(ref Named) Named {
domain, path := SplitHostname(ref)
return repository{
domain: domain,
path: path,
repo := repository{}
if r, ok := ref.(namedRepository); ok {
repo.domain, repo.path = r.Domain(), r.Path()
} else {
repo.domain, repo.path = splitDomain(ref.Name())
}
return repo
}
func getBestReferenceType(ref reference) Reference {

View File

@@ -1,147 +1,163 @@
package reference
import "regexp"
import (
"regexp"
"strings"
)
var (
// alphaNumericRegexp defines the alpha numeric atom, typically a
// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
var DigestRegexp = regexp.MustCompile(digestPat)
// DomainRegexp matches hostname or IP-addresses, optionally including a port
// number. It defines the structure of potential domain components that may be
// part of image names. This is purposely a subset of what is allowed by DNS to
// ensure backwards compatibility with Docker image names. It may be a subset of
// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between
// square brackets (excluding zone identifiers as defined by [RFC 6874] or special
// addresses such as IPv4-Mapped).
//
// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874.
var DomainRegexp = regexp.MustCompile(domainAndPort)
// IdentifierRegexp is the format for string identifier used as a
// content addressable identifier using sha256. These identifiers
// are like digests without the algorithm, since sha256 is used.
var IdentifierRegexp = regexp.MustCompile(identifier)
// NameRegexp is the format for the name component of references, including
// an optional domain and port, but without tag or digest suffix.
var NameRegexp = regexp.MustCompile(namePat)
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
var ReferenceRegexp = regexp.MustCompile(referencePat)
// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
//
// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28
var TagRegexp = regexp.MustCompile(tag)
const (
// alphanumeric defines the alphanumeric atom, typically a
// component of names. This only allows lower case characters and digits.
alphaNumericRegexp = match(`[a-z0-9]+`)
alphanumeric = `[a-z0-9]+`
// separatorRegexp defines the separators allowed to be embedded in name
// components. This allow one period, one or two underscore and multiple
// separator defines the separators allowed to be embedded in name
// components. This allows one period, one or two underscore and multiple
// dashes. Repeated dashes and underscores are intentionally treated
// differently. In order to support valid hostnames as name components,
// supporting repeated dash was added. Additionally double underscore is
// now allowed as a separator to loosen the restriction for previously
// supported names.
separatorRegexp = match(`(?:[._]|__|[-]*)`)
separator = `(?:[._]|__|[-]*)`
// nameComponentRegexp restricts registry path component names to start
// with at least one letter or number, with following parts able to be
// separated by one period, one or two underscore and multiple dashes.
nameComponentRegexp = expression(
alphaNumericRegexp,
optional(repeated(separatorRegexp, alphaNumericRegexp)))
// localhost is treated as a special value for domain-name. Any other
// domain-name without a "." or a ":port" are considered a path component.
localhost = `localhost`
// domainComponentRegexp restricts the registry domain component of a
// repository name to start with a component as defined by DomainRegexp
// and followed by an optional port.
domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`)
// domainNameComponent restricts the registry domain component of a
// repository name to start with a component as defined by DomainRegexp.
domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
// DomainRegexp defines the structure of potential domain components
// optionalPort matches an optional port-number including the port separator
// (e.g. ":80").
optionalPort = `(?::[0-9]+)?`
// tag matches valid tag names. From docker/docker:graph/tags.go.
tag = `[\w][\w.-]{0,127}`
// digestPat matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
//
// TODO(thaJeztah): this should follow the same rules as https://pkg.go.dev/github.com/opencontainers/go-digest@v1.0.0#DigestRegexp
// so that go-digest defines the canonical format. Note that the go-digest is
// more relaxed:
// - it allows multiple algorithms (e.g. "sha256+b64:<encoded>") to allow
// future expansion of supported algorithms.
// - it allows the "<encoded>" value to use urlsafe base64 encoding as defined
// in [rfc4648, section 5].
//
// [rfc4648, section 5]: https://www.rfc-editor.org/rfc/rfc4648#section-5.
digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`
// identifier is the format for a content addressable identifier using sha256.
// These identifiers are like digests without the algorithm, since sha256 is used.
identifier = `([a-f0-9]{64})`
// ipv6address are enclosed between square brackets and may be represented
// in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format
// are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as
// IPv4-Mapped are deliberately excluded.
ipv6address = `\[(?:[a-fA-F0-9:]+)\]`
)
var (
// domainName defines the structure of potential domain components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names.
DomainRegexp = expression(
domainComponentRegexp,
optional(repeated(literal(`.`), domainComponentRegexp)),
optional(literal(`:`), match(`[0-9]+`)))
// names. This includes IPv4 addresses on decimal format.
domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent)
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go.
TagRegexp = match(`[\w][\w.-]{0,127}`)
// host defines the structure of potential domains based on the URI
// Host subcomponent on rfc3986. It may be a subset of DNS domain name,
// or an IPv4 address in decimal format, or an IPv6 address between square
// brackets (excluding zone identifiers as defined by rfc6874 or special
// addresses such as IPv4-Mapped).
host = `(?:` + domainName + `|` + ipv6address + `)`
// allowed by the URI Host subcomponent on rfc3986 to ensure backwards
// compatibility with Docker image names.
domainAndPort = host + optionalPort
// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = anchored(TagRegexp)
// DigestRegexp matches valid digests.
DigestRegexp = match(`[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`)
anchoredTagRegexp = regexp.MustCompile(anchored(tag))
// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = anchored(DigestRegexp)
anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat))
// NameRegexp is the format for the name component of references. The
// regexp has capturing groups for the domain and name part omitting
// the separating forward slash from either.
NameRegexp = expression(
optional(DomainRegexp, literal(`/`)),
nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp)))
// pathComponent restricts path-components to start with an alphanumeric
// character, with following parts able to be separated by a separator
// (one period, one or two underscore and multiple dashes).
pathComponent = alphanumeric + anyTimes(separator+alphanumeric)
// remoteName matches the remote-name of a repository. It consists of one
// or more forward slash (/) delimited path-components:
//
// pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu"
remoteName = pathComponent + anyTimes(`/`+pathComponent)
namePat = optional(domainAndPort+`/`) + remoteName
// anchoredNameRegexp is used to parse a name value, capturing the
// domain and trailing components.
anchoredNameRegexp = anchored(
optional(capture(DomainRegexp), literal(`/`)),
capture(nameComponentRegexp,
optional(repeated(literal(`/`), nameComponentRegexp))))
anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName)))
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
ReferenceRegexp = anchored(capture(NameRegexp),
optional(literal(":"), capture(TagRegexp)),
optional(literal("@"), capture(DigestRegexp)))
// IdentifierRegexp is the format for string identifier used as a
// content addressable identifier using sha256. These identifiers
// are like digests without the algorithm, since sha256 is used.
IdentifierRegexp = match(`([a-f0-9]{64})`)
// ShortIdentifierRegexp is the format used to represent a prefix
// of an identifier. A prefix may be used to match a sha256 identifier
// within a list of trusted identifiers.
ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`)
referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat)))
// anchoredIdentifierRegexp is used to check or match an
// identifier value, anchored at start and end of string.
anchoredIdentifierRegexp = anchored(IdentifierRegexp)
// anchoredShortIdentifierRegexp is used to check if a value
// is a possible identifier prefix, anchored at start and end
// of string.
anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp)
anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier))
)
// match compiles the string to a regular expression.
var match = regexp.MustCompile
// literal compiles s into a literal regular expression, escaping any regexp
// reserved characters.
func literal(s string) *regexp.Regexp {
re := match(regexp.QuoteMeta(s))
if _, complete := re.LiteralPrefix(); !complete {
panic("must be a literal")
}
return re
}
// expression defines a full expression, where each regular expression must
// follow the previous.
func expression(res ...*regexp.Regexp) *regexp.Regexp {
var s string
for _, re := range res {
s += re.String()
}
return match(s)
}
// optional wraps the expression in a non-capturing group and makes the
// production optional.
func optional(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `?`)
func optional(res ...string) string {
return `(?:` + strings.Join(res, "") + `)?`
}
// repeated wraps the regexp in a non-capturing group to get one or more
// matches.
func repeated(res ...*regexp.Regexp) *regexp.Regexp {
return match(group(expression(res...)).String() + `+`)
}
// group wraps the regexp in a non-capturing group.
func group(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(?:` + expression(res...).String() + `)`)
// anyTimes wraps the expression in a non-capturing group that can occur
// any number of times.
func anyTimes(res ...string) string {
return `(?:` + strings.Join(res, "") + `)*`
}
// capture wraps the expression in a capturing group.
func capture(res ...*regexp.Regexp) *regexp.Regexp {
return match(`(` + expression(res...).String() + `)`)
func capture(res ...string) string {
return `(` + strings.Join(res, "") + `)`
}
// anchored anchors the regular expression by adding start and end delimiters.
func anchored(res ...*regexp.Regexp) *regexp.Regexp {
return match(`^` + expression(res...).String() + `$`)
func anchored(res ...string) string {
return `^` + strings.Join(res, "") + `$`
}

View File

@@ -0,0 +1,75 @@
/*
Copyright The containerd Authors.
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 reference
import (
"sort"
)
// Sort sorts string references preferring higher information references.
//
// The precedence is as follows:
//
// 1. [Named] + [Tagged] + [Digested] (e.g., "docker.io/library/busybox:latest@sha256:<digest>")
// 2. [Named] + [Tagged] (e.g., "docker.io/library/busybox:latest")
// 3. [Named] + [Digested] (e.g., "docker.io/library/busybo@sha256:<digest>")
// 4. [Named] (e.g., "docker.io/library/busybox")
// 5. [Digested] (e.g., "docker.io@sha256:<digest>")
// 6. Parse error
func Sort(references []string) []string {
var prefs []Reference
var bad []string
for _, ref := range references {
pref, err := ParseAnyReference(ref)
if err != nil {
bad = append(bad, ref)
} else {
prefs = append(prefs, pref)
}
}
sort.Slice(prefs, func(a, b int) bool {
ar := refRank(prefs[a])
br := refRank(prefs[b])
if ar == br {
return prefs[a].String() < prefs[b].String()
}
return ar < br
})
sort.Strings(bad)
var refs []string
for _, pref := range prefs {
refs = append(refs, pref.String())
}
return append(refs, bad...)
}
func refRank(ref Reference) uint8 {
if _, ok := ref.(Named); ok {
if _, ok = ref.(Tagged); ok {
if _, ok = ref.(Digested); ok {
return 1
}
return 2
}
if _, ok = ref.(Digested); ok {
return 3
}
return 4
}
return 5
}

View File

@@ -0,0 +1 @@
*.go text eol=lf

2
vendor/github.com/distribution/reference/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,2 @@
# Cover profiles
*.out

18
vendor/github.com/distribution/reference/.golangci.yml generated vendored Normal file
View File

@@ -0,0 +1,18 @@
linters:
enable:
- bodyclose
- dupword # Checks for duplicate words in the source code
- gofmt
- goimports
- ineffassign
- misspell
- revive
- staticcheck
- unconvert
- unused
- vet
disable:
- errcheck
run:
deadline: 2m

View File

@@ -0,0 +1,5 @@
# Code of Conduct
We follow the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
Please contact the [CNCF Code of Conduct Committee](mailto:conduct@cncf.io) in order to report violations of the Code of Conduct.

View File

@@ -0,0 +1,114 @@
# Contributing to the reference library
## Community help
If you need help, please ask in the [#distribution](https://cloud-native.slack.com/archives/C01GVR8SY4R) channel on CNCF community slack.
[Click here for an invite to the CNCF community slack](https://slack.cncf.io/)
## Reporting security issues
The maintainers take security seriously. If you discover a security
issue, please bring it to their attention right away!
Please **DO NOT** file a public issue, instead send your report privately to
[cncf-distribution-security@lists.cncf.io](mailto:cncf-distribution-security@lists.cncf.io).
## Reporting an issue properly
By following these simple rules you will get better and faster feedback on your issue.
- search the bugtracker for an already reported issue
### If you found an issue that describes your problem:
- please read other user comments first, and confirm this is the same issue: a given error condition might be indicative of different problems - you may also find a workaround in the comments
- please refrain from adding "same thing here" or "+1" comments
- you don't need to comment on an issue to get notified of updates: just hit the "subscribe" button
- comment if you have some new, technical and relevant information to add to the case
- __DO NOT__ comment on closed issues or merged PRs. If you think you have a related problem, open up a new issue and reference the PR or issue.
### If you have not found an existing issue that describes your problem:
1. create a new issue, with a succinct title that describes your issue:
- bad title: "It doesn't work with my docker"
- good title: "Private registry push fail: 400 error with E_INVALID_DIGEST"
2. copy the output of (or similar for other container tools):
- `docker version`
- `docker info`
- `docker exec <registry-container> registry --version`
3. copy the command line you used to launch your Registry
4. restart your docker daemon in debug mode (add `-D` to the daemon launch arguments)
5. reproduce your problem and get your docker daemon logs showing the error
6. if relevant, copy your registry logs that show the error
7. provide any relevant detail about your specific Registry configuration (e.g., storage backend used)
8. indicate if you are using an enterprise proxy, Nginx, or anything else between you and your Registry
## Contributing Code
Contributions should be made via pull requests. Pull requests will be reviewed
by one or more maintainers or reviewers and merged when acceptable.
You should follow the basic GitHub workflow:
1. Use your own [fork](https://help.github.com/en/articles/about-forks)
2. Create your [change](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
3. Test your code
4. [Commit](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages) your work, always [sign your commits](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#commit-messages)
5. Push your change to your fork and create a [Pull Request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork)
Refer to [containerd's contribution guide](https://github.com/containerd/project/blob/master/CONTRIBUTING.md#successful-changes)
for tips on creating a successful contribution.
## Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.

144
vendor/github.com/distribution/reference/GOVERNANCE.md generated vendored Normal file
View File

@@ -0,0 +1,144 @@
# distribution/reference Project Governance
Distribution [Code of Conduct](./CODE-OF-CONDUCT.md) can be found here.
For specific guidance on practical contribution steps please
see our [CONTRIBUTING.md](./CONTRIBUTING.md) guide.
## Maintainership
There are different types of maintainers, with different responsibilities, but
all maintainers have 3 things in common:
1) They share responsibility in the project's success.
2) They have made a long-term, recurring time investment to improve the project.
3) They spend that time doing whatever needs to be done, not necessarily what
is the most interesting or fun.
Maintainers are often under-appreciated, because their work is harder to appreciate.
It's easy to appreciate a really cool and technically advanced feature. It's harder
to appreciate the absence of bugs, the slow but steady improvement in stability,
or the reliability of a release process. But those things distinguish a good
project from a great one.
## Reviewers
A reviewer is a core role within the project.
They share in reviewing issues and pull requests and their LGTM counts towards the
required LGTM count to merge a code change into the project.
Reviewers are part of the organization but do not have write access.
Becoming a reviewer is a core aspect in the journey to becoming a maintainer.
## Adding maintainers
Maintainers are first and foremost contributors that have shown they are
committed to the long term success of a project. Contributors wanting to become
maintainers are expected to be deeply involved in contributing code, pull
request review, and triage of issues in the project for more than three months.
Just contributing does not make you a maintainer, it is about building trust
with the current maintainers of the project and being a person that they can
depend on and trust to make decisions in the best interest of the project.
Periodically, the existing maintainers curate a list of contributors that have
shown regular activity on the project over the prior months. From this list,
maintainer candidates are selected and proposed in a pull request or a
maintainers communication channel.
After a candidate has been announced to the maintainers, the existing
maintainers are given five business days to discuss the candidate, raise
objections and cast their vote. Votes may take place on the communication
channel or via pull request comment. Candidates must be approved by at least 66%
of the current maintainers by adding their vote on the mailing list. The
reviewer role has the same process but only requires 33% of current maintainers.
Only maintainers of the repository that the candidate is proposed for are
allowed to vote.
If a candidate is approved, a maintainer will contact the candidate to invite
the candidate to open a pull request that adds the contributor to the
MAINTAINERS file. The voting process may take place inside a pull request if a
maintainer has already discussed the candidacy with the candidate and a
maintainer is willing to be a sponsor by opening the pull request. The candidate
becomes a maintainer once the pull request is merged.
## Stepping down policy
Life priorities, interests, and passions can change. If you're a maintainer but
feel you must remove yourself from the list, inform other maintainers that you
intend to step down, and if possible, help find someone to pick up your work.
At the very least, ensure your work can be continued where you left off.
After you've informed other maintainers, create a pull request to remove
yourself from the MAINTAINERS file.
## Removal of inactive maintainers
Similar to the procedure for adding new maintainers, existing maintainers can
be removed from the list if they do not show significant activity on the
project. Periodically, the maintainers review the list of maintainers and their
activity over the last three months.
If a maintainer has shown insufficient activity over this period, a neutral
person will contact the maintainer to ask if they want to continue being
a maintainer. If the maintainer decides to step down as a maintainer, they
open a pull request to be removed from the MAINTAINERS file.
If the maintainer wants to remain a maintainer, but is unable to perform the
required duties they can be removed with a vote of at least 66% of the current
maintainers. In this case, maintainers should first propose the change to
maintainers via the maintainers communication channel, then open a pull request
for voting. The voting period is five business days. The voting pull request
should not come as a surpise to any maintainer and any discussion related to
performance must not be discussed on the pull request.
## How are decisions made?
Docker distribution is an open-source project with an open design philosophy.
This means that the repository is the source of truth for EVERY aspect of the
project, including its philosophy, design, road map, and APIs. *If it's part of
the project, it's in the repo. If it's in the repo, it's part of the project.*
As a result, all decisions can be expressed as changes to the repository. An
implementation change is a change to the source code. An API change is a change
to the API specification. A philosophy change is a change to the philosophy
manifesto, and so on.
All decisions affecting distribution, big and small, follow the same 3 steps:
* Step 1: Open a pull request. Anyone can do this.
* Step 2: Discuss the pull request. Anyone can do this.
* Step 3: Merge or refuse the pull request. Who does this depends on the nature
of the pull request and which areas of the project it affects.
## Helping contributors with the DCO
The [DCO or `Sign your work`](./CONTRIBUTING.md#sign-your-work)
requirement is not intended as a roadblock or speed bump.
Some contributors are not as familiar with `git`, or have used a web
based editor, and thus asking them to `git commit --amend -s` is not the best
way forward.
In this case, maintainers can update the commits based on clause (c) of the DCO.
The most trivial way for a contributor to allow the maintainer to do this, is to
add a DCO signature in a pull requests's comment, or a maintainer can simply
note that the change is sufficiently trivial that it does not substantially
change the existing contribution - i.e., a spelling change.
When you add someone's DCO, please also add your own to keep a log.
## I'm a maintainer. Should I make pull requests too?
Yes. Nobody should ever push to master directly. All changes should be
made through a pull request.
## Conflict Resolution
If you have a technical dispute that you feel has reached an impasse with a
subset of the community, any contributor may open an issue, specifically
calling for a resolution vote of the current core maintainers to resolve the
dispute. The same voting quorums required (2/3) for adding and removing
maintainers will apply to conflict resolution.

202
vendor/github.com/distribution/reference/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
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.

26
vendor/github.com/distribution/reference/MAINTAINERS generated vendored Normal file
View File

@@ -0,0 +1,26 @@
# Distribution project maintainers & reviewers
#
# See GOVERNANCE.md for maintainer versus reviewer roles
#
# MAINTAINERS (cncf-distribution-maintainers@lists.cncf.io)
# GitHub ID, Name, Email address
"chrispat","Chris Patterson","chrispat@github.com"
"clarkbw","Bryan Clark","clarkbw@github.com"
"corhere","Cory Snider","csnider@mirantis.com"
"deleteriousEffect","Hayley Swimelar","hswimelar@gitlab.com"
"heww","He Weiwei","hweiwei@vmware.com"
"joaodrp","João Pereira","jpereira@gitlab.com"
"justincormack","Justin Cormack","justin.cormack@docker.com"
"squizzi","Kyle Squizzato","ksquizzato@mirantis.com"
"milosgajdos","Milos Gajdos","milosthegajdos@gmail.com"
"sargun","Sargun Dhillon","sargun@sargun.me"
"wy65701436","Wang Yan","wangyan@vmware.com"
"stevelasker","Steve Lasker","steve.lasker@microsoft.com"
#
# REVIEWERS
# GitHub ID, Name, Email address
"dmcgowan","Derek McGowan","derek@mcgstyle.net"
"stevvooe","Stephen Day","stevvooe@gmail.com"
"thajeztah","Sebastiaan van Stijn","github@gone.nl"
"DavidSpek", "David van der Spek", "vanderspek.david@gmail.com"
"Jamstah", "James Hewitt", "james.hewitt@gmail.com"

25
vendor/github.com/distribution/reference/Makefile generated vendored Normal file
View File

@@ -0,0 +1,25 @@
# Project packages.
PACKAGES=$(shell go list ./...)
# Flags passed to `go test`
BUILDFLAGS ?=
TESTFLAGS ?=
.PHONY: all build test coverage
.DEFAULT: all
all: build
build: ## no binaries to build, so just check compilation suceeds
go build ${BUILDFLAGS} ./...
test: ## run tests
go test ${TESTFLAGS} ./...
coverage: ## generate coverprofiles from the unit tests
rm -f coverage.txt
go test ${TESTFLAGS} -cover -coverprofile=cover.out ./...
.PHONY: help
help:
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_\/%-]+:.*?##/ { printf " \033[36m%-27s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)

30
vendor/github.com/distribution/reference/README.md generated vendored Normal file
View File

@@ -0,0 +1,30 @@
# Distribution reference
Go library to handle references to container images.
<img src="/distribution-logo.svg" width="200px" />
[![Build Status](https://github.com/distribution/reference/actions/workflows/test.yml/badge.svg?branch=main&event=push)](https://github.com/distribution/reference/actions?query=workflow%3ACI)
[![GoDoc](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/distribution/reference)
[![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
[![codecov](https://codecov.io/gh/distribution/reference/branch/main/graph/badge.svg)](https://codecov.io/gh/distribution/reference)
[![FOSSA Status](https://app.fossa.com/api/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference.svg?type=shield)](https://app.fossa.com/projects/custom%2B162%2Fgithub.com%2Fdistribution%2Freference?ref=badge_shield)
This repository contains a library for handling refrences to container images held in container registries. Please see [godoc](https://pkg.go.dev/github.com/distribution/reference) for details.
## Contribution
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
issues, fixes, and patches to this project.
## Communication
For async communication and long running discussions please use issues and pull requests on the github repo.
This will be the best place to discuss design and implementation.
For sync communication we have a #distribution channel in the [CNCF Slack](https://slack.cncf.io/)
that everyone is welcome to join and chat about development.
## Licenses
The distribution codebase is released under the [Apache 2.0 license](LICENSE).

7
vendor/github.com/distribution/reference/SECURITY.md generated vendored Normal file
View File

@@ -0,0 +1,7 @@
# Security Policy
## Reporting a Vulnerability
The maintainers take security seriously. If you discover a security issue, please bring it to their attention right away!
Please DO NOT file a public issue, instead send your report privately to cncf-distribution-security@lists.cncf.io.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -32,7 +32,7 @@ func FamiliarString(ref Reference) string {
}
// FamiliarMatch reports whether ref matches the specified pattern.
// See https://godoc.org/path#Match for supported patterns.
// See [path.Match] for supported patterns.
func FamiliarMatch(pattern string, ref Reference) (bool, error) {
matched, err := path.Match(pattern, FamiliarString(ref))
if namedRef, isNamed := ref.(Named); isNamed && !matched {

View File

@@ -1,19 +1,42 @@
package reference
import (
"errors"
"fmt"
"strings"
"github.com/docker/distribution/digestset"
"github.com/opencontainers/go-digest"
)
var (
const (
// legacyDefaultDomain is the legacy domain for Docker Hub (which was
// originally named "the Docker Index"). This domain is still used for
// authentication and image search, which were part of the "v1" Docker
// registry specification.
//
// This domain will continue to be supported, but there are plans to consolidate
// legacy domains to new "canonical" domains. Once those domains are decided
// on, we must update the normalization functions, but preserve compatibility
// with existing installs, clients, and user configuration.
legacyDefaultDomain = "index.docker.io"
defaultDomain = "docker.io"
officialRepoName = "library"
defaultTag = "latest"
// defaultDomain is the default domain used for images on Docker Hub.
// It is used to normalize "familiar" names to canonical names, for example,
// to convert "ubuntu" to "docker.io/library/ubuntu:latest".
//
// Note that actual domain of Docker Hub's registry is registry-1.docker.io.
// This domain will continue to be supported, but there are plans to consolidate
// legacy domains to new "canonical" domains. Once those domains are decided
// on, we must update the normalization functions, but preserve compatibility
// with existing installs, clients, and user configuration.
defaultDomain = "docker.io"
// officialRepoPrefix is the namespace used for official images on Docker Hub.
// It is used to normalize "familiar" names to canonical names, for example,
// to convert "ubuntu" to "docker.io/library/ubuntu:latest".
officialRepoPrefix = "library/"
// defaultTag is the default tag if no tag is provided.
defaultTag = "latest"
)
// normalizedNamed represents a name which has been
@@ -35,14 +58,14 @@ func ParseNormalizedNamed(s string) (Named, error) {
return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s)
}
domain, remainder := splitDockerDomain(s)
var remoteName string
var remote string
if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 {
remoteName = remainder[:tagSep]
remote = remainder[:tagSep]
} else {
remoteName = remainder
remote = remainder
}
if strings.ToLower(remoteName) != remoteName {
return nil, errors.New("invalid reference format: repository name must be lowercase")
if strings.ToLower(remote) != remote {
return nil, fmt.Errorf("invalid reference format: repository name (%s) must be lowercase", remote)
}
ref, err := Parse(domain + "/" + remainder)
@@ -56,41 +79,53 @@ func ParseNormalizedNamed(s string) (Named, error) {
return named, nil
}
// ParseDockerRef normalizes the image reference following the docker convention. This is added
// mainly for backward compatibility.
// The reference returned can only be either tagged or digested. For reference contains both tag
// and digest, the function returns digested reference, e.g. docker.io/library/busybox:latest@
// sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa will be returned as
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa.
// namedTaggedDigested is a reference that has both a tag and a digest.
type namedTaggedDigested interface {
NamedTagged
Digested
}
// ParseDockerRef normalizes the image reference following the docker convention,
// which allows for references to contain both a tag and a digest. It returns a
// reference that is either tagged or digested. For references containing both
// a tag and a digest, it returns a digested reference. For example, the following
// reference:
//
// docker.io/library/busybox:latest@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
//
// Is returned as a digested reference (with the ":latest" tag removed):
//
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
//
// References that are already "tagged" or "digested" are returned unmodified:
//
// // Already a digested reference
// docker.io/library/busybox@sha256:7cc4b5aefd1d0cadf8d97d4350462ba51c694ebca145b08d7d41b41acc8db5aa
//
// // Already a named reference
// docker.io/library/busybox:latest
func ParseDockerRef(ref string) (Named, error) {
named, err := ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, ok := named.(NamedTagged); ok {
if canonical, ok := named.(Canonical); ok {
// The reference is both tagged and digested, only
// return digested.
newNamed, err := WithName(canonical.Name())
if err != nil {
return nil, err
}
newCanonical, err := WithDigest(newNamed, canonical.Digest())
if err != nil {
return nil, err
}
return newCanonical, nil
if canonical, ok := named.(namedTaggedDigested); ok {
// The reference is both tagged and digested; only return digested.
newNamed, err := WithName(canonical.Name())
if err != nil {
return nil, err
}
return WithDigest(newNamed, canonical.Digest())
}
return TagNameOnly(named), nil
}
// splitDockerDomain splits a repository name to domain and remotename string.
// splitDockerDomain splits a repository name to domain and remote-name.
// If no valid domain is found, the default domain is used. Repository name
// needs to be already validated before.
func splitDockerDomain(name string) (domain, remainder string) {
i := strings.IndexRune(name, '/')
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") {
if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != localhost && strings.ToLower(name[:i]) == name[:i]) {
domain, remainder = defaultDomain, name
} else {
domain, remainder = name[:i], name[i+1:]
@@ -99,13 +134,13 @@ func splitDockerDomain(name string) (domain, remainder string) {
domain = defaultDomain
}
if domain == defaultDomain && !strings.ContainsRune(remainder, '/') {
remainder = officialRepoName + "/" + remainder
remainder = officialRepoPrefix + remainder
}
return
}
// familiarizeName returns a shortened version of the name familiar
// to to the Docker UI. Familiar names have the default domain
// to the Docker UI. Familiar names have the default domain
// "docker.io" and "library/" repository prefix removed.
// For example, "docker.io/library/redis" will have the familiar
// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp".
@@ -119,8 +154,15 @@ func familiarizeName(named namedRepository) repository {
if repo.domain == defaultDomain {
repo.domain = ""
// Handle official repositories which have the pattern "library/<official repo name>"
if split := strings.Split(repo.path, "/"); len(split) == 2 && split[0] == officialRepoName {
repo.path = split[1]
if strings.HasPrefix(repo.path, officialRepoPrefix) {
// TODO(thaJeztah): this check may be too strict, as it assumes the
// "library/" namespace does not have nested namespaces. While this
// is true (currently), technically it would be possible for Docker
// Hub to use those (e.g. "library/distros/ubuntu:latest").
// See https://github.com/distribution/distribution/pull/3769#issuecomment-1302031785.
if remainder := strings.TrimPrefix(repo.path, officialRepoPrefix); !strings.ContainsRune(remainder, '/') {
repo.path = remainder
}
}
}
return repo
@@ -180,20 +222,3 @@ func ParseAnyReference(ref string) (Reference, error) {
return ParseNormalizedNamed(ref)
}
// ParseAnyReferenceWithSet parses a reference string as a possible short
// identifier to be matched in a digest set, a full digest, or familiar name.
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
dgst, err := ds.Lookup(ref)
if err == nil {
return digestReference(dgst), nil
}
} else {
if dgst, err := digest.Parse(ref); err == nil {
return digestReference(dgst), nil
}
}
return ParseNormalizedNamed(ref)
}

View File

@@ -4,11 +4,14 @@
// Grammar
//
// reference := name [ ":" tag ] [ "@" digest ]
// name := [domain '/'] path-component ['/' path-component]*
// domain := domain-component ['.' domain-component]* [':' port-number]
// name := [domain '/'] remote-name
// domain := host [':' port-number]
// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A
// domain-name := domain-component ['.' domain-component]*
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/
// path-component := alpha-numeric [separator alpha-numeric]*
// path (or "remote-name") := path-component ['/' path-component]*
// alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/
//
@@ -21,7 +24,6 @@
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value
//
// identifier := /[a-f0-9]{64}/
// short-identifier := /[a-f0-9]{6,64}/
package reference
import (
@@ -145,7 +147,7 @@ type namedRepository interface {
Path() string
}
// Domain returns the domain part of the Named reference
// Domain returns the domain part of the [Named] reference.
func Domain(named Named) string {
if r, ok := named.(namedRepository); ok {
return r.Domain()
@@ -154,7 +156,7 @@ func Domain(named Named) string {
return domain
}
// Path returns the name without the domain part of the Named reference
// Path returns the name without the domain part of the [Named] reference.
func Path(named Named) (name string) {
if r, ok := named.(namedRepository); ok {
return r.Path()
@@ -175,7 +177,8 @@ func splitDomain(name string) (string, string) {
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
// DEPRECATED: Use Domain or Path
//
// Deprecated: Use [Domain] or [Path].
func SplitHostname(named Named) (string, string) {
if r, ok := named.(namedRepository); ok {
return r.Domain(), r.Path()
@@ -185,7 +188,6 @@ func SplitHostname(named Named) (string, string) {
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: Parse will not handle short digests.
func Parse(s string) (Reference, error) {
matches := ReferenceRegexp.FindStringSubmatch(s)
if matches == nil {
@@ -237,7 +239,6 @@ func Parse(s string) (Reference, error) {
// the Named interface. The reference must have a name and be in the canonical
// form, otherwise an error is returned.
// If an error was encountered it is returned, along with a nil Reference.
// NOTE: ParseNamed will not handle short digests.
func ParseNamed(s string) (Named, error) {
named, err := ParseNormalizedNamed(s)
if err != nil {
@@ -320,11 +321,13 @@ func WithDigest(name Named, digest digest.Digest) (Canonical, error) {
// TrimNamed removes any tag or digest from the named reference.
func TrimNamed(ref Named) Named {
domain, path := SplitHostname(ref)
return repository{
domain: domain,
path: path,
repo := repository{}
if r, ok := ref.(namedRepository); ok {
repo.domain, repo.path = r.Domain(), r.Path()
} else {
repo.domain, repo.path = splitDomain(ref.Name())
}
return repo
}
func getBestReferenceType(ref reference) Reference {

163
vendor/github.com/distribution/reference/regexp.go generated vendored Normal file
View File

@@ -0,0 +1,163 @@
package reference
import (
"regexp"
"strings"
)
// DigestRegexp matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
var DigestRegexp = regexp.MustCompile(digestPat)
// DomainRegexp matches hostname or IP-addresses, optionally including a port
// number. It defines the structure of potential domain components that may be
// part of image names. This is purposely a subset of what is allowed by DNS to
// ensure backwards compatibility with Docker image names. It may be a subset of
// DNS domain name, an IPv4 address in decimal format, or an IPv6 address between
// square brackets (excluding zone identifiers as defined by [RFC 6874] or special
// addresses such as IPv4-Mapped).
//
// [RFC 6874]: https://www.rfc-editor.org/rfc/rfc6874.
var DomainRegexp = regexp.MustCompile(domainAndPort)
// IdentifierRegexp is the format for string identifier used as a
// content addressable identifier using sha256. These identifiers
// are like digests without the algorithm, since sha256 is used.
var IdentifierRegexp = regexp.MustCompile(identifier)
// NameRegexp is the format for the name component of references, including
// an optional domain and port, but without tag or digest suffix.
var NameRegexp = regexp.MustCompile(namePat)
// ReferenceRegexp is the full supported format of a reference. The regexp
// is anchored and has capturing groups for name, tag, and digest
// components.
var ReferenceRegexp = regexp.MustCompile(referencePat)
// TagRegexp matches valid tag names. From [docker/docker:graph/tags.go].
//
// [docker/docker:graph/tags.go]: https://github.com/moby/moby/blob/v1.6.0/graph/tags.go#L26-L28
var TagRegexp = regexp.MustCompile(tag)
const (
// alphanumeric defines the alphanumeric atom, typically a
// component of names. This only allows lower case characters and digits.
alphanumeric = `[a-z0-9]+`
// separator defines the separators allowed to be embedded in name
// components. This allows one period, one or two underscore and multiple
// dashes. Repeated dashes and underscores are intentionally treated
// differently. In order to support valid hostnames as name components,
// supporting repeated dash was added. Additionally double underscore is
// now allowed as a separator to loosen the restriction for previously
// supported names.
separator = `(?:[._]|__|[-]+)`
// localhost is treated as a special value for domain-name. Any other
// domain-name without a "." or a ":port" are considered a path component.
localhost = `localhost`
// domainNameComponent restricts the registry domain component of a
// repository name to start with a component as defined by DomainRegexp.
domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
// optionalPort matches an optional port-number including the port separator
// (e.g. ":80").
optionalPort = `(?::[0-9]+)?`
// tag matches valid tag names. From docker/docker:graph/tags.go.
tag = `[\w][\w.-]{0,127}`
// digestPat matches well-formed digests, including algorithm (e.g. "sha256:<encoded>").
//
// TODO(thaJeztah): this should follow the same rules as https://pkg.go.dev/github.com/opencontainers/go-digest@v1.0.0#DigestRegexp
// so that go-digest defines the canonical format. Note that the go-digest is
// more relaxed:
// - it allows multiple algorithms (e.g. "sha256+b64:<encoded>") to allow
// future expansion of supported algorithms.
// - it allows the "<encoded>" value to use urlsafe base64 encoding as defined
// in [rfc4648, section 5].
//
// [rfc4648, section 5]: https://www.rfc-editor.org/rfc/rfc4648#section-5.
digestPat = `[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{32,}`
// identifier is the format for a content addressable identifier using sha256.
// These identifiers are like digests without the algorithm, since sha256 is used.
identifier = `([a-f0-9]{64})`
// ipv6address are enclosed between square brackets and may be represented
// in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format
// are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as
// IPv4-Mapped are deliberately excluded.
ipv6address = `\[(?:[a-fA-F0-9:]+)\]`
)
var (
// domainName defines the structure of potential domain components
// that may be part of image names. This is purposely a subset of what is
// allowed by DNS to ensure backwards compatibility with Docker image
// names. This includes IPv4 addresses on decimal format.
domainName = domainNameComponent + anyTimes(`\.`+domainNameComponent)
// host defines the structure of potential domains based on the URI
// Host subcomponent on rfc3986. It may be a subset of DNS domain name,
// or an IPv4 address in decimal format, or an IPv6 address between square
// brackets (excluding zone identifiers as defined by rfc6874 or special
// addresses such as IPv4-Mapped).
host = `(?:` + domainName + `|` + ipv6address + `)`
// allowed by the URI Host subcomponent on rfc3986 to ensure backwards
// compatibility with Docker image names.
domainAndPort = host + optionalPort
// anchoredTagRegexp matches valid tag names, anchored at the start and
// end of the matched string.
anchoredTagRegexp = regexp.MustCompile(anchored(tag))
// anchoredDigestRegexp matches valid digests, anchored at the start and
// end of the matched string.
anchoredDigestRegexp = regexp.MustCompile(anchored(digestPat))
// pathComponent restricts path-components to start with an alphanumeric
// character, with following parts able to be separated by a separator
// (one period, one or two underscore and multiple dashes).
pathComponent = alphanumeric + anyTimes(separator+alphanumeric)
// remoteName matches the remote-name of a repository. It consists of one
// or more forward slash (/) delimited path-components:
//
// pathComponent[[/pathComponent] ...] // e.g., "library/ubuntu"
remoteName = pathComponent + anyTimes(`/`+pathComponent)
namePat = optional(domainAndPort+`/`) + remoteName
// anchoredNameRegexp is used to parse a name value, capturing the
// domain and trailing components.
anchoredNameRegexp = regexp.MustCompile(anchored(optional(capture(domainAndPort), `/`), capture(remoteName)))
referencePat = anchored(capture(namePat), optional(`:`, capture(tag)), optional(`@`, capture(digestPat)))
// anchoredIdentifierRegexp is used to check or match an
// identifier value, anchored at start and end of string.
anchoredIdentifierRegexp = regexp.MustCompile(anchored(identifier))
)
// optional wraps the expression in a non-capturing group and makes the
// production optional.
func optional(res ...string) string {
return `(?:` + strings.Join(res, "") + `)?`
}
// anyTimes wraps the expression in a non-capturing group that can occur
// any number of times.
func anyTimes(res ...string) string {
return `(?:` + strings.Join(res, "") + `)*`
}
// capture wraps the expression in a capturing group.
func capture(res ...string) string {
return `(` + strings.Join(res, "") + `)`
}
// anchored anchors the regular expression by adding start and end delimiters.
func anchored(res ...string) string {
return `^` + strings.Join(res, "") + `$`
}

75
vendor/github.com/distribution/reference/sort.go generated vendored Normal file
View File

@@ -0,0 +1,75 @@
/*
Copyright The containerd Authors.
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 reference
import (
"sort"
)
// Sort sorts string references preferring higher information references.
//
// The precedence is as follows:
//
// 1. [Named] + [Tagged] + [Digested] (e.g., "docker.io/library/busybox:latest@sha256:<digest>")
// 2. [Named] + [Tagged] (e.g., "docker.io/library/busybox:latest")
// 3. [Named] + [Digested] (e.g., "docker.io/library/busybo@sha256:<digest>")
// 4. [Named] (e.g., "docker.io/library/busybox")
// 5. [Digested] (e.g., "docker.io@sha256:<digest>")
// 6. Parse error
func Sort(references []string) []string {
var prefs []Reference
var bad []string
for _, ref := range references {
pref, err := ParseAnyReference(ref)
if err != nil {
bad = append(bad, ref)
} else {
prefs = append(prefs, pref)
}
}
sort.Slice(prefs, func(a, b int) bool {
ar := refRank(prefs[a])
br := refRank(prefs[b])
if ar == br {
return prefs[a].String() < prefs[b].String()
}
return ar < br
})
sort.Strings(bad)
var refs []string
for _, pref := range prefs {
refs = append(refs, pref.String())
}
return append(refs, bad...)
}
func refRank(ref Reference) uint8 {
if _, ok := ref.(Named); ok {
if _, ok = ref.(Tagged); ok {
if _, ok = ref.(Digested); ok {
return 1
}
return 2
}
if _, ok = ref.(Digested); ok {
return 3
}
return 4
}
return 5
}

View File

@@ -1,7 +1,5 @@
linters:
enable:
- structcheck
- varcheck
- staticcheck
- unconvert
- gofmt
@@ -14,6 +12,14 @@ linters:
disable:
- errcheck
linters-settings:
revive:
rules:
# TODO(thaJeztah): temporarily disabled the "unused-parameter" check.
# It produces many warnings, and some of those may need to be looked at.
- name: unused-parameter
disabled: true
run:
deadline: 2m
skip-dirs:

View File

@@ -49,3 +49,6 @@ Hayley Swimelar <hswimelar@gmail.com>
Jose D. Gomez R <jose.gomez@suse.com>
Shengjing Zhu <zhsj@debian.org>
Silvin Lubecki <31478878+silvin-lubecki@users.noreply.github.com>
James Hewitt <james.hewitt@gmail.com>
Marcus Pettersen Irgens <m@mrcus.dev>
Ben Manuel <bmanuel@users.noreply.github.com>

View File

@@ -114,4 +114,4 @@ the registry binary generated in the "./bin" directory:
### Optional build tags
Optional [build tags](http://golang.org/pkg/go/build/) can be provided using
the environment variable `DOCKER_BUILDTAGS`.
the environment variable `BUILDTAGS`.

View File

@@ -1,7 +1,7 @@
# syntax=docker/dockerfile:1
ARG GO_VERSION=1.19.9
ARG ALPINE_VERSION=3.16
ARG GO_VERSION=1.20.8
ARG ALPINE_VERSION=3.18
ARG XX_VERSION=1.2.1
FROM --platform=$BUILDPLATFORM tonistiigi/xx:${XX_VERSION} AS xx
@@ -22,12 +22,12 @@ RUN --mount=target=. \
FROM base AS build
ARG TARGETPLATFORM
ARG LDFLAGS="-s -w"
ARG BUILDTAGS="include_oss include_gcs"
ARG BUILDTAGS="include_oss,include_gcs"
RUN --mount=type=bind,target=/go/src/github.com/docker/distribution,rw \
--mount=type=cache,target=/root/.cache/go-build \
--mount=target=/go/pkg/mod,type=cache \
--mount=type=bind,source=/tmp/.ldflags,target=/tmp/.ldflags,from=version \
set -x ; xx-go build -trimpath -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/registry ./cmd/registry \
set -x ; xx-go build -tags "${BUILDTAGS}" -trimpath -ldflags "$(cat /tmp/.ldflags) ${LDFLAGS}" -o /usr/bin/registry ./cmd/registry \
&& xx-verify --static /usr/bin/registry
FROM scratch AS binary

View File

@@ -50,7 +50,7 @@ version/version.go:
check: ## run all linters (TODO: enable "unused", "varcheck", "ineffassign", "unconvert", "staticheck", "goimports", "structcheck")
@echo "$(WHALE) $@"
@GO111MODULE=off golangci-lint run
@GO111MODULE=off golangci-lint --build-tags "${BUILDTAGS}" run
test: ## run tests, except integration test with test.short
@echo "$(WHALE) $@"

View File

@@ -8,7 +8,7 @@ import (
"net/http"
"time"
"github.com/docker/distribution/reference"
"github.com/distribution/reference"
"github.com/opencontainers/go-digest"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)

View File

@@ -1,247 +0,0 @@
package digestset
import (
"errors"
"sort"
"strings"
"sync"
digest "github.com/opencontainers/go-digest"
)
var (
// ErrDigestNotFound is used when a matching digest
// could not be found in a set.
ErrDigestNotFound = errors.New("digest not found")
// ErrDigestAmbiguous is used when multiple digests
// are found in a set. None of the matching digests
// should be considered valid matches.
ErrDigestAmbiguous = errors.New("ambiguous digest string")
)
// Set is used to hold a unique set of digests which
// may be easily referenced by easily referenced by a string
// representation of the digest as well as short representation.
// The uniqueness of the short representation is based on other
// digests in the set. If digests are omitted from this set,
// collisions in a larger set may not be detected, therefore it
// is important to always do short representation lookups on
// the complete set of digests. To mitigate collisions, an
// appropriately long short code should be used.
type Set struct {
mutex sync.RWMutex
entries digestEntries
}
// NewSet creates an empty set of digests
// which may have digests added.
func NewSet() *Set {
return &Set{
entries: digestEntries{},
}
}
// checkShortMatch checks whether two digests match as either whole
// values or short values. This function does not test equality,
// rather whether the second value could match against the first
// value.
func checkShortMatch(alg digest.Algorithm, hex, shortAlg, shortHex string) bool {
if len(hex) == len(shortHex) {
if hex != shortHex {
return false
}
if len(shortAlg) > 0 && string(alg) != shortAlg {
return false
}
} else if !strings.HasPrefix(hex, shortHex) {
return false
} else if len(shortAlg) > 0 && string(alg) != shortAlg {
return false
}
return true
}
// Lookup looks for a digest matching the given string representation.
// If no digests could be found ErrDigestNotFound will be returned
// with an empty digest value. If multiple matches are found
// ErrDigestAmbiguous will be returned with an empty digest value.
func (dst *Set) Lookup(d string) (digest.Digest, error) {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
if len(dst.entries) == 0 {
return "", ErrDigestNotFound
}
var (
searchFunc func(int) bool
alg digest.Algorithm
hex string
)
dgst, err := digest.Parse(d)
if err == digest.ErrDigestInvalidFormat {
hex = d
searchFunc = func(i int) bool {
return dst.entries[i].val >= d
}
} else {
hex = dgst.Hex()
alg = dgst.Algorithm()
searchFunc = func(i int) bool {
if dst.entries[i].val == hex {
return dst.entries[i].alg >= alg
}
return dst.entries[i].val >= hex
}
}
idx := sort.Search(len(dst.entries), searchFunc)
if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
return "", ErrDigestNotFound
}
if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
return dst.entries[idx].digest, nil
}
if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
return "", ErrDigestAmbiguous
}
return dst.entries[idx].digest, nil
}
// Add adds the given digest to the set. An error will be returned
// if the given digest is invalid. If the digest already exists in the
// set, this operation will be a no-op.
func (dst *Set) Add(d digest.Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
if idx == len(dst.entries) {
dst.entries = append(dst.entries, entry)
return nil
} else if dst.entries[idx].digest == d {
return nil
}
entries := append(dst.entries, nil)
copy(entries[idx+1:], entries[idx:len(entries)-1])
entries[idx] = entry
dst.entries = entries
return nil
}
// Remove removes the given digest from the set. An err will be
// returned if the given digest is invalid. If the digest does
// not exist in the set, this operation will be a no-op.
func (dst *Set) Remove(d digest.Digest) error {
if err := d.Validate(); err != nil {
return err
}
dst.mutex.Lock()
defer dst.mutex.Unlock()
entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
searchFunc := func(i int) bool {
if dst.entries[i].val == entry.val {
return dst.entries[i].alg >= entry.alg
}
return dst.entries[i].val >= entry.val
}
idx := sort.Search(len(dst.entries), searchFunc)
// Not found if idx is after or value at idx is not digest
if idx == len(dst.entries) || dst.entries[idx].digest != d {
return nil
}
entries := dst.entries
copy(entries[idx:], entries[idx+1:])
entries = entries[:len(entries)-1]
dst.entries = entries
return nil
}
// All returns all the digests in the set
func (dst *Set) All() []digest.Digest {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
retValues := make([]digest.Digest, len(dst.entries))
for i := range dst.entries {
retValues[i] = dst.entries[i].digest
}
return retValues
}
// ShortCodeTable returns a map of Digest to unique short codes. The
// length represents the minimum value, the maximum length may be the
// entire value of digest if uniqueness cannot be achieved without the
// full value. This function will attempt to make short codes as short
// as possible to be unique.
func ShortCodeTable(dst *Set, length int) map[digest.Digest]string {
dst.mutex.RLock()
defer dst.mutex.RUnlock()
m := make(map[digest.Digest]string, len(dst.entries))
l := length
resetIdx := 0
for i := 0; i < len(dst.entries); i++ {
var short string
extended := true
for extended {
extended = false
if len(dst.entries[i].val) <= l {
short = dst.entries[i].digest.String()
} else {
short = dst.entries[i].val[:l]
for j := i + 1; j < len(dst.entries); j++ {
if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
if j > resetIdx {
resetIdx = j
}
extended = true
} else {
break
}
}
if extended {
l++
}
}
}
m[dst.entries[i].digest] = short
if i >= resetIdx {
l = length
}
}
return m
}
type digestEntry struct {
alg digest.Algorithm
val string
digest digest.Digest
}
type digestEntries []*digestEntry
func (d digestEntries) Len() int {
return len(d)
}
func (d digestEntries) Less(i, j int) bool {
if d[i].val != d[j].val {
return d[i].val < d[j].val
}
return d[i].alg < d[j].alg
}
func (d digestEntries) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}

View File

@@ -0,0 +1,34 @@
package reference
import "github.com/distribution/reference"
// IsNameOnly returns true if reference only contains a repo name.
//
// Deprecated: use [reference.IsNameOnly].
func IsNameOnly(ref reference.Named) bool {
return reference.IsNameOnly(ref)
}
// FamiliarName returns the familiar name string
// for the given named, familiarizing if needed.
//
// Deprecated: use [reference.FamiliarName].
func FamiliarName(ref reference.Named) string {
return reference.FamiliarName(ref)
}
// FamiliarString returns the familiar string representation
// for the given reference, familiarizing if needed.
//
// Deprecated: use [reference.FamiliarString].
func FamiliarString(ref reference.Reference) string {
return reference.FamiliarString(ref)
}
// FamiliarMatch reports whether ref matches the specified pattern.
// See [path.Match] for supported patterns.
//
// Deprecated: use [reference.FamiliarMatch].
func FamiliarMatch(pattern string, ref reference.Reference) (bool, error) {
return reference.FamiliarMatch(pattern, ref)
}

View File

@@ -0,0 +1,92 @@
package reference
import (
"regexp"
"github.com/distribution/reference"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest/digestset"
)
// ParseNormalizedNamed parses a string into a named reference
// transforming a familiar name from Docker UI to a fully
// qualified reference. If the value may be an identifier
// use ParseAnyReference.
//
// Deprecated: use [reference.ParseNormalizedNamed].
func ParseNormalizedNamed(s string) (reference.Named, error) {
return reference.ParseNormalizedNamed(s)
}
// ParseDockerRef normalizes the image reference following the docker convention,
// which allows for references to contain both a tag and a digest.
//
// Deprecated: use [reference.ParseDockerRef].
func ParseDockerRef(ref string) (reference.Named, error) {
return reference.ParseDockerRef(ref)
}
// TagNameOnly adds the default tag "latest" to a reference if it only has
// a repo name.
//
// Deprecated: use [reference.TagNameOnly].
func TagNameOnly(ref reference.Named) reference.Named {
return reference.TagNameOnly(ref)
}
// ParseAnyReference parses a reference string as a possible identifier,
// full digest, or familiar name.
//
// Deprecated: use [reference.ParseAnyReference].
func ParseAnyReference(ref string) (reference.Reference, error) {
return reference.ParseAnyReference(ref)
}
// Functions and types below have been removed in distribution v3 and
// have not been ported to github.com/distribution/reference. See
// https://github.com/distribution/distribution/pull/3774
var (
// ShortIdentifierRegexp is the format used to represent a prefix
// of an identifier. A prefix may be used to match a sha256 identifier
// within a list of trusted identifiers.
//
// Deprecated: support for short-identifiers is deprecated, and will be removed in v3.
ShortIdentifierRegexp = regexp.MustCompile(shortIdentifier)
shortIdentifier = `([a-f0-9]{6,64})`
// anchoredShortIdentifierRegexp is used to check if a value
// is a possible identifier prefix, anchored at start and end
// of string.
anchoredShortIdentifierRegexp = regexp.MustCompile(`^` + shortIdentifier + `$`)
)
type digestReference digest.Digest
func (d digestReference) String() string {
return digest.Digest(d).String()
}
func (d digestReference) Digest() digest.Digest {
return digest.Digest(d)
}
// ParseAnyReferenceWithSet parses a reference string as a possible short
// identifier to be matched in a digest set, a full digest, or familiar name.
//
// Deprecated: support for short-identifiers is deprecated, and will be removed in v3.
func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) {
if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok {
dgst, err := ds.Lookup(ref)
if err == nil {
return digestReference(dgst), nil
}
} else {
if dgst, err := digest.Parse(ref); err == nil {
return digestReference(dgst), nil
}
}
return reference.ParseNormalizedNamed(ref)
}

View File

@@ -0,0 +1,172 @@
// Package reference is deprecated, and has moved to github.com/distribution/reference.
//
// Deprecated: use github.com/distribution/reference instead.
package reference
import (
"github.com/distribution/reference"
"github.com/opencontainers/go-digest"
)
const (
// NameTotalLengthMax is the maximum total number of characters in a repository name.
//
// Deprecated: use [reference.NameTotalLengthMax].
NameTotalLengthMax = reference.NameTotalLengthMax
)
var (
// ErrReferenceInvalidFormat represents an error while trying to parse a string as a reference.
//
// Deprecated: use [reference.ErrReferenceInvalidFormat].
ErrReferenceInvalidFormat = reference.ErrReferenceInvalidFormat
// ErrTagInvalidFormat represents an error while trying to parse a string as a tag.
//
// Deprecated: use [reference.ErrTagInvalidFormat].
ErrTagInvalidFormat = reference.ErrTagInvalidFormat
// ErrDigestInvalidFormat represents an error while trying to parse a string as a tag.
//
// Deprecated: use [reference.ErrDigestInvalidFormat].
ErrDigestInvalidFormat = reference.ErrDigestInvalidFormat
// ErrNameContainsUppercase is returned for invalid repository names that contain uppercase characters.
//
// Deprecated: use [reference.ErrNameContainsUppercase].
ErrNameContainsUppercase = reference.ErrNameContainsUppercase
// ErrNameEmpty is returned for empty, invalid repository names.
//
// Deprecated: use [reference.ErrNameEmpty].
ErrNameEmpty = reference.ErrNameEmpty
// ErrNameTooLong is returned when a repository name is longer than NameTotalLengthMax.
//
// Deprecated: use [reference.ErrNameTooLong].
ErrNameTooLong = reference.ErrNameTooLong
// ErrNameNotCanonical is returned when a name is not canonical.
//
// Deprecated: use [reference.ErrNameNotCanonical].
ErrNameNotCanonical = reference.ErrNameNotCanonical
)
// Reference is an opaque object reference identifier that may include
// modifiers such as a hostname, name, tag, and digest.
//
// Deprecated: use [reference.Reference].
type Reference = reference.Reference
// Field provides a wrapper type for resolving correct reference types when
// working with encoding.
//
// Deprecated: use [reference.Field].
type Field = reference.Field
// AsField wraps a reference in a Field for encoding.
//
// Deprecated: use [reference.AsField].
func AsField(ref reference.Reference) reference.Field {
return reference.AsField(ref)
}
// Named is an object with a full name
//
// Deprecated: use [reference.Named].
type Named = reference.Named
// Tagged is an object which has a tag
//
// Deprecated: use [reference.Tagged].
type Tagged = reference.Tagged
// NamedTagged is an object including a name and tag.
//
// Deprecated: use [reference.NamedTagged].
type NamedTagged reference.NamedTagged
// Digested is an object which has a digest
// in which it can be referenced by
//
// Deprecated: use [reference.Digested].
type Digested reference.Digested
// Canonical reference is an object with a fully unique
// name including a name with domain and digest
//
// Deprecated: use [reference.Canonical].
type Canonical reference.Canonical
// Domain returns the domain part of the [Named] reference.
//
// Deprecated: use [reference.Domain].
func Domain(named reference.Named) string {
return reference.Domain(named)
}
// Path returns the name without the domain part of the [Named] reference.
//
// Deprecated: use [reference.Path].
func Path(named reference.Named) (name string) {
return reference.Path(named)
}
// SplitHostname splits a named reference into a
// hostname and name string. If no valid hostname is
// found, the hostname is empty and the full value
// is returned as name
//
// Deprecated: Use [reference.Domain] or [reference.Path].
func SplitHostname(named reference.Named) (string, string) {
return reference.SplitHostname(named)
}
// Parse parses s and returns a syntactically valid Reference.
// If an error was encountered it is returned, along with a nil Reference.
//
// Deprecated: use [reference.Parse].
func Parse(s string) (reference.Reference, error) {
return reference.Parse(s)
}
// ParseNamed parses s and returns a syntactically valid reference implementing
// the Named interface. The reference must have a name and be in the canonical
// form, otherwise an error is returned.
// If an error was encountered it is returned, along with a nil Reference.
//
// Deprecated: use [reference.ParseNamed].
func ParseNamed(s string) (reference.Named, error) {
return reference.ParseNamed(s)
}
// WithName returns a named object representing the given string. If the input
// is invalid ErrReferenceInvalidFormat will be returned.
//
// Deprecated: use [reference.WithName].
func WithName(name string) (reference.Named, error) {
return reference.WithName(name)
}
// WithTag combines the name from "name" and the tag from "tag" to form a
// reference incorporating both the name and the tag.
//
// Deprecated: use [reference.WithTag].
func WithTag(name reference.Named, tag string) (reference.NamedTagged, error) {
return reference.WithTag(name, tag)
}
// WithDigest combines the name from "name" and the digest from "digest" to form
// a reference incorporating both the name and the digest.
//
// Deprecated: use [reference.WithDigest].
func WithDigest(name reference.Named, digest digest.Digest) (reference.Canonical, error) {
return reference.WithDigest(name, digest)
}
// TrimNamed removes any tag or digest from the named reference.
//
// Deprecated: use [reference.TrimNamed].
func TrimNamed(ref reference.Named) reference.Named {
return reference.TrimNamed(ref)
}

Some files were not shown because too many files have changed in this diff Show More