Unverified Commit 02a5bee6 authored by Brad Davidson's avatar Brad Davidson Committed by GitHub

Add system-default-registry support and remove shared code (#3285)

* Move registries.yaml handling out to rancher/wharfie * Add system-default-registry support * Add CLI support for kubelet image credential providers Signed-off-by: 's avatarBrad Davidson <brad.davidson@rancher.com>
parent 948295e8
...@@ -81,9 +81,9 @@ require ( ...@@ -81,9 +81,9 @@ require (
github.com/google/uuid v1.2.0 github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.8.0 github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket v1.4.2
github.com/k3s-io/helm-controller v0.8.3 github.com/k3s-io/helm-controller v0.9.1
github.com/k3s-io/kine v0.6.0 github.com/k3s-io/kine v0.6.0
github.com/klauspost/compress v1.11.7 github.com/klauspost/compress v1.12.2
github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000 github.com/kubernetes-sigs/cri-tools v0.0.0-00010101000000-000000000000
github.com/lib/pq v1.8.0 github.com/lib/pq v1.8.0
github.com/mattn/go-sqlite3 v1.14.4 github.com/mattn/go-sqlite3 v1.14.4
...@@ -92,10 +92,11 @@ require ( ...@@ -92,10 +92,11 @@ require (
github.com/natefinch/lumberjack v2.0.0+incompatible github.com/natefinch/lumberjack v2.0.0+incompatible
github.com/opencontainers/runc v1.0.0-rc93 github.com/opencontainers/runc v1.0.0-rc93
github.com/opencontainers/selinux v1.8.0 github.com/opencontainers/selinux v1.8.0
github.com/pierrec/lz4 v2.5.2+incompatible github.com/pierrec/lz4 v2.6.0+incompatible
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rancher/dynamiclistener v0.2.3 github.com/rancher/dynamiclistener v0.2.3
github.com/rancher/remotedialer v0.2.0 github.com/rancher/remotedialer v0.2.0
github.com/rancher/wharfie v0.3.4
github.com/rancher/wrangler v0.6.2 github.com/rancher/wrangler v0.6.2
github.com/rancher/wrangler-api v0.6.0 github.com/rancher/wrangler-api v0.6.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
...@@ -107,7 +108,7 @@ require ( ...@@ -107,7 +108,7 @@ require (
github.com/urfave/cli v1.22.2 github.com/urfave/cli v1.22.2
go.etcd.io/etcd v0.5.0-alpha.5.0.20201208200253-50621aee4aea go.etcd.io/etcd v0.5.0-alpha.5.0.20201208200253-50621aee4aea
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210224082022-3d97a244fca7 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073
google.golang.org/grpc v1.37.0 google.golang.org/grpc v1.37.0
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
......
...@@ -111,7 +111,7 @@ spec: ...@@ -111,7 +111,7 @@ spec:
beta.kubernetes.io/os: linux beta.kubernetes.io/os: linux
containers: containers:
- name: coredns - name: coredns
image: rancher/coredns-coredns:1.8.3 image: %{SYSTEM_DEFAULT_REGISTRY}%rancher/coredns-coredns:1.8.3
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
resources: resources:
limits: limits:
......
...@@ -63,7 +63,7 @@ spec: ...@@ -63,7 +63,7 @@ spec:
effect: "NoSchedule" effect: "NoSchedule"
containers: containers:
- name: local-path-provisioner - name: local-path-provisioner
image: rancher/local-path-provisioner:v0.0.19 image: %{SYSTEM_DEFAULT_REGISTRY}%rancher/local-path-provisioner:v0.0.19
imagePullPolicy: IfNotPresent imagePullPolicy: IfNotPresent
command: command:
- local-path-provisioner - local-path-provisioner
...@@ -150,4 +150,4 @@ data: ...@@ -150,4 +150,4 @@ data:
spec: spec:
containers: containers:
- name: helper-pod - name: helper-pod
image: rancher/library-busybox:1.32.1 image: %{SYSTEM_DEFAULT_REGISTRY}%rancher/library-busybox:1.32.1
...@@ -39,7 +39,7 @@ spec: ...@@ -39,7 +39,7 @@ spec:
emptyDir: {} emptyDir: {}
containers: containers:
- name: metrics-server - name: metrics-server
image: rancher/metrics-server:v0.3.6 image: %{SYSTEM_DEFAULT_REGISTRY}%rancher/metrics-server:v0.3.6
volumeMounts: volumeMounts:
- name: tmp-dir - name: tmp-dir
mountPath: /tmp mountPath: /tmp
......
...@@ -13,6 +13,8 @@ metadata: ...@@ -13,6 +13,8 @@ metadata:
namespace: kube-system namespace: kube-system
spec: spec:
chart: https://%{KUBERNETES_API}%/static/charts/traefik-9.18.2.tgz chart: https://%{KUBERNETES_API}%/static/charts/traefik-9.18.2.tgz
set:
global.systemDefaultRegistry: "%{SYSTEM_DEFAULT_REGISTRY_RAW}%"
valuesContent: |- valuesContent: |-
rbac: rbac:
enabled: true enabled: true
......
...@@ -29,6 +29,7 @@ import ( ...@@ -29,6 +29,7 @@ import (
"github.com/rancher/k3s/pkg/daemons/control/deps" "github.com/rancher/k3s/pkg/daemons/control/deps"
"github.com/rancher/k3s/pkg/util" "github.com/rancher/k3s/pkg/util"
"github.com/rancher/k3s/pkg/version" "github.com/rancher/k3s/pkg/version"
"github.com/rancher/wrangler/pkg/slice"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/json"
"k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/net"
...@@ -437,7 +438,6 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N ...@@ -437,7 +438,6 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N
if envInfo.Rootless { if envInfo.Rootless {
nodeConfig.AgentConfig.RootDir = filepath.Join(envInfo.DataDir, "agent", "kubelet") nodeConfig.AgentConfig.RootDir = filepath.Join(envInfo.DataDir, "agent", "kubelet")
} }
nodeConfig.AgentConfig.PauseImage = envInfo.PauseImage
nodeConfig.AgentConfig.Snapshotter = envInfo.Snapshotter nodeConfig.AgentConfig.Snapshotter = envInfo.Snapshotter
nodeConfig.AgentConfig.IPSECPSK = controlConfig.IPSECPSK nodeConfig.AgentConfig.IPSECPSK = controlConfig.IPSECPSK
nodeConfig.AgentConfig.StrongSwanDir = filepath.Join(envInfo.DataDir, "agent", "strongswan") nodeConfig.AgentConfig.StrongSwanDir = filepath.Join(envInfo.DataDir, "agent", "strongswan")
...@@ -550,12 +550,26 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N ...@@ -550,12 +550,26 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N
nodeConfig.AgentConfig.ClusterDNSs = controlConfig.ClusterDNSs nodeConfig.AgentConfig.ClusterDNSs = controlConfig.ClusterDNSs
} }
nodeConfig.AgentConfig.PauseImage = envInfo.PauseImage
nodeConfig.AgentConfig.AirgapExtraRegistry = envInfo.AirgapExtraRegistry
// Apply SystemDefaultRegistry to PauseImage and AirgapExtraRegistry
if controlConfig.SystemDefaultRegistry != "" {
if !strings.HasPrefix(nodeConfig.AgentConfig.PauseImage, controlConfig.SystemDefaultRegistry) {
nodeConfig.AgentConfig.PauseImage = controlConfig.SystemDefaultRegistry + "/" + nodeConfig.AgentConfig.PauseImage
}
if !slice.ContainsString(nodeConfig.AgentConfig.AirgapExtraRegistry, controlConfig.SystemDefaultRegistry) {
nodeConfig.AgentConfig.AirgapExtraRegistry = append(nodeConfig.AgentConfig.AirgapExtraRegistry, controlConfig.SystemDefaultRegistry)
}
}
nodeConfig.AgentConfig.ExtraKubeletArgs = envInfo.ExtraKubeletArgs nodeConfig.AgentConfig.ExtraKubeletArgs = envInfo.ExtraKubeletArgs
nodeConfig.AgentConfig.ExtraKubeProxyArgs = envInfo.ExtraKubeProxyArgs nodeConfig.AgentConfig.ExtraKubeProxyArgs = envInfo.ExtraKubeProxyArgs
nodeConfig.AgentConfig.NodeTaints = envInfo.Taints nodeConfig.AgentConfig.NodeTaints = envInfo.Taints
nodeConfig.AgentConfig.NodeLabels = envInfo.Labels nodeConfig.AgentConfig.NodeLabels = envInfo.Labels
nodeConfig.AgentConfig.ImageCredProvBinDir = envInfo.ImageCredProvBinDir
nodeConfig.AgentConfig.ImageCredProvConfig = envInfo.ImageCredProvConfig
nodeConfig.AgentConfig.PrivateRegistry = envInfo.PrivateRegistry nodeConfig.AgentConfig.PrivateRegistry = envInfo.PrivateRegistry
nodeConfig.AgentConfig.AirgapExtraRegistry = envInfo.AirgapExtraRegistry
nodeConfig.AgentConfig.DisableCCM = controlConfig.DisableCCM nodeConfig.AgentConfig.DisableCCM = controlConfig.DisableCCM
nodeConfig.AgentConfig.DisableNPC = controlConfig.DisableNPC nodeConfig.AgentConfig.DisableNPC = controlConfig.DisableNPC
nodeConfig.AgentConfig.DisableKubeProxy = controlConfig.DisableKubeProxy nodeConfig.AgentConfig.DisableKubeProxy = controlConfig.DisableKubeProxy
......
...@@ -30,11 +30,11 @@ import ( ...@@ -30,11 +30,11 @@ import (
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/rancher/k3s/pkg/untar" "github.com/rancher/k3s/pkg/untar"
"github.com/rancher/k3s/pkg/version" "github.com/rancher/k3s/pkg/version"
"github.com/rancher/wharfie/pkg/registries"
"github.com/rancher/wrangler/pkg/merr" "github.com/rancher/wrangler/pkg/merr"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"google.golang.org/grpc" "google.golang.org/grpc"
yaml "gopkg.in/yaml.v2"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/kubelet/util" "k8s.io/kubernetes/pkg/kubelet/util"
) )
...@@ -334,7 +334,7 @@ func prePullImages(ctx context.Context, conn *grpc.ClientConn, images io.Reader) ...@@ -334,7 +334,7 @@ func prePullImages(ctx context.Context, conn *grpc.ClientConn, images io.Reader)
// setupContainerdConfig generates the containerd.toml, using a template combined with various // setupContainerdConfig generates the containerd.toml, using a template combined with various
// runtime configurations and registry mirror settings provided by the administrator. // runtime configurations and registry mirror settings provided by the administrator.
func setupContainerdConfig(ctx context.Context, cfg *config.Node) error { func setupContainerdConfig(ctx context.Context, cfg *config.Node) error {
privRegistries, err := getPrivateRegistries(ctx, cfg) privRegistries, err := registries.GetPrivateRegistries(cfg.AgentConfig.PrivateRegistry)
if err != nil { if err != nil {
return err return err
} }
...@@ -353,7 +353,7 @@ func setupContainerdConfig(ctx context.Context, cfg *config.Node) error { ...@@ -353,7 +353,7 @@ func setupContainerdConfig(ctx context.Context, cfg *config.Node) error {
NodeConfig: cfg, NodeConfig: cfg,
DisableCgroup: disableCgroup, DisableCgroup: disableCgroup,
IsRunningInUserNS: isRunningInUserNS, IsRunningInUserNS: isRunningInUserNS,
PrivateRegistryConfig: privRegistries, PrivateRegistryConfig: privRegistries.Registry(),
} }
selEnabled, selConfigured, err := selinuxStatus() selEnabled, selConfigured, err := selinuxStatus()
...@@ -383,20 +383,3 @@ func setupContainerdConfig(ctx context.Context, cfg *config.Node) error { ...@@ -383,20 +383,3 @@ func setupContainerdConfig(ctx context.Context, cfg *config.Node) error {
return util2.WriteFile(cfg.Containerd.Config, parsedTemplate) return util2.WriteFile(cfg.Containerd.Config, parsedTemplate)
} }
// getPrivateRegistries loads the registry mirror configuration from registries.yaml
func getPrivateRegistries(ctx context.Context, cfg *config.Node) (*templates.Registry, error) {
privRegistries := &templates.Registry{}
privRegistryFile, err := ioutil.ReadFile(cfg.AgentConfig.PrivateRegistry)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
logrus.Infof("Using registry config file at %s", cfg.AgentConfig.PrivateRegistry)
if err := yaml.Unmarshal(privRegistryFile, &privRegistries); err != nil {
return nil, err
}
return privRegistries, nil
}
...@@ -4,6 +4,8 @@ import ( ...@@ -4,6 +4,8 @@ import (
"bytes" "bytes"
"text/template" "text/template"
"github.com/rancher/wharfie/pkg/registries"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
) )
...@@ -11,7 +13,7 @@ type ContainerdConfig struct { ...@@ -11,7 +13,7 @@ type ContainerdConfig struct {
NodeConfig *config.Node NodeConfig *config.Node
DisableCgroup bool DisableCgroup bool
IsRunningInUserNS bool IsRunningInUserNS bool
PrivateRegistryConfig *Registry PrivateRegistryConfig *registries.Registry
} }
const ContainerdConfigTemplate = ` const ContainerdConfigTemplate = `
......
...@@ -36,14 +36,17 @@ type Agent struct { ...@@ -36,14 +36,17 @@ type Agent struct {
WithNodeID bool WithNodeID bool
EnableSELinux bool EnableSELinux bool
ProtectKernelDefaults bool ProtectKernelDefaults bool
ClusterReset bool
PrivateRegistry string PrivateRegistry string
SystemDefaultRegistry string
AirgapExtraRegistry cli.StringSlice AirgapExtraRegistry cli.StringSlice
ExtraKubeletArgs cli.StringSlice ExtraKubeletArgs cli.StringSlice
ExtraKubeProxyArgs cli.StringSlice ExtraKubeProxyArgs cli.StringSlice
Labels cli.StringSlice Labels cli.StringSlice
Taints cli.StringSlice Taints cli.StringSlice
ImageCredProvBinDir string
ImageCredProvConfig string
AgentShared AgentShared
ClusterReset bool
} }
type AgentShared struct { type AgentShared struct {
...@@ -100,7 +103,7 @@ var ( ...@@ -100,7 +103,7 @@ var (
Name: "pause-image", Name: "pause-image",
Usage: "(agent/runtime) Customized pause image for containerd or docker sandbox", Usage: "(agent/runtime) Customized pause image for containerd or docker sandbox",
Destination: &AgentConfig.PauseImage, Destination: &AgentConfig.PauseImage,
Value: "docker.io/rancher/pause:3.1", Value: "rancher/pause:3.1",
} }
SnapshotterFlag = cli.StringFlag{ SnapshotterFlag = cli.StringFlag{
Name: "snapshotter", Name: "snapshotter",
...@@ -149,6 +152,18 @@ var ( ...@@ -149,6 +152,18 @@ var (
Usage: "(agent/node) Registering and starting kubelet with set of labels", Usage: "(agent/node) Registering and starting kubelet with set of labels",
Value: &AgentConfig.Labels, Value: &AgentConfig.Labels,
} }
ImageCredProvBinDirFlag = cli.StringFlag{
Name: "image-credential-provider-bin-dir",
Usage: "(agent/node) The path to the directory where credential provider plugin binaries are located",
Destination: &AgentConfig.ImageCredProvBinDir,
Value: "/var/lib/rancher/credentialprovider/bin",
}
ImageCredProvConfigFlag = cli.StringFlag{
Name: "image-credential-provider-config",
Usage: "(agent/node) The path to the credential provider plugin config file",
Destination: &AgentConfig.ImageCredProvConfig,
Value: "/var/lib/rancher/credentialprovider/config.yaml",
}
DisableSELinuxFlag = cli.BoolTFlag{ DisableSELinuxFlag = cli.BoolTFlag{
Name: "disable-selinux", Name: "disable-selinux",
Usage: "(deprecated) Use --selinux to explicitly enable SELinux", Usage: "(deprecated) Use --selinux to explicitly enable SELinux",
...@@ -228,6 +243,8 @@ func NewAgentCommand(action func(ctx *cli.Context) error) cli.Command { ...@@ -228,6 +243,8 @@ func NewAgentCommand(action func(ctx *cli.Context) error) cli.Command {
WithNodeIDFlag, WithNodeIDFlag,
NodeLabels, NodeLabels,
NodeTaints, NodeTaints,
ImageCredProvBinDirFlag,
ImageCredProvConfigFlag,
DockerFlag, DockerFlag,
CRIEndpointFlag, CRIEndpointFlag,
PauseImageFlag, PauseImageFlag,
......
...@@ -61,6 +61,7 @@ type Server struct { ...@@ -61,6 +61,7 @@ type Server struct {
ClusterReset bool ClusterReset bool
ClusterResetRestorePath string ClusterResetRestorePath string
EncryptSecrets bool EncryptSecrets bool
SystemDefaultRegistry string
StartupHooks []func(context.Context, <-chan struct{}, string) error StartupHooks []func(context.Context, <-chan struct{}, string) error
EtcdSnapshotName string EtcdSnapshotName string
EtcdDisableSnapshots bool EtcdDisableSnapshots bool
...@@ -360,6 +361,8 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -360,6 +361,8 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
WithNodeIDFlag, WithNodeIDFlag,
NodeLabels, NodeLabels,
NodeTaints, NodeTaints,
ImageCredProvBinDirFlag,
ImageCredProvConfigFlag,
DockerFlag, DockerFlag,
CRIEndpointFlag, CRIEndpointFlag,
PauseImageFlag, PauseImageFlag,
...@@ -419,6 +422,12 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command { ...@@ -419,6 +422,12 @@ func NewServerCommand(action func(*cli.Context) error) cli.Command {
Usage: "(experimental) Enable Secret encryption at rest", Usage: "(experimental) Enable Secret encryption at rest",
Destination: &ServerConfig.EncryptSecrets, Destination: &ServerConfig.EncryptSecrets,
}, },
cli.StringFlag{
Name: "system-default-registry",
Usage: "(image) Private registry to be used for all system images",
EnvVar: version.ProgramUpper + "_SYSTEM_DEFAULT_REGISTRY",
Destination: &ServerConfig.SystemDefaultRegistry,
},
&SELinuxFlag, &SELinuxFlag,
LBServerPortFlag, LBServerPortFlag,
......
...@@ -157,6 +157,7 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont ...@@ -157,6 +157,7 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont
serverConfig.ControlConfig.ClusterReset = cfg.ClusterReset serverConfig.ControlConfig.ClusterReset = cfg.ClusterReset
serverConfig.ControlConfig.ClusterResetRestorePath = cfg.ClusterResetRestorePath serverConfig.ControlConfig.ClusterResetRestorePath = cfg.ClusterResetRestorePath
serverConfig.ControlConfig.SystemDefaultRegistry = cfg.SystemDefaultRegistry
if serverConfig.ControlConfig.SupervisorPort == 0 { if serverConfig.ControlConfig.SupervisorPort == 0 {
serverConfig.ControlConfig.SupervisorPort = serverConfig.ControlConfig.HTTPSPort serverConfig.ControlConfig.SupervisorPort = serverConfig.ControlConfig.HTTPSPort
......
...@@ -159,6 +159,13 @@ func startKubelet(cfg *config.Agent) error { ...@@ -159,6 +159,13 @@ func startKubelet(cfg *config.Agent) error {
argsMap["cloud-provider"] = "external" argsMap["cloud-provider"] = "external"
} }
if ImageCredProvAvailable(cfg) {
logrus.Infof("Kubelet image credential provider bin dir and configuration file found.")
argsMap["feature-gates"] = addFeatureGate(argsMap["feature-gates"], "KubeletCredentialProviders=true")
argsMap["image-credential-provider-bin-dir"] = cfg.ImageCredProvBinDir
argsMap["image-credential-provider-config"] = cfg.ImageCredProvConfig
}
if cfg.Rootless { if cfg.Rootless {
// "/sys/fs/cgroup" is namespaced // "/sys/fs/cgroup" is namespaced
cgroupfsWritable := unix.Access("/sys/fs/cgroup", unix.W_OK) == nil cgroupfsWritable := unix.Access("/sys/fs/cgroup", unix.W_OK) == nil
...@@ -193,6 +200,20 @@ func addFeatureGate(current, new string) string { ...@@ -193,6 +200,20 @@ func addFeatureGate(current, new string) string {
return current + "," + new return current + "," + new
} }
// ImageCredProvAvailable checks to see if the kubelet image credential provider bin dir and config
// files exist and are of the correct types. This is exported so that it may be used by downstream projects.
func ImageCredProvAvailable(cfg *config.Agent) bool {
if info, err := os.Stat(cfg.ImageCredProvBinDir); err != nil || !info.IsDir() {
logrus.Debugf("Kubelet image credential provider bin directory check failed: %v", err)
return false
}
if info, err := os.Stat(cfg.ImageCredProvConfig); err != nil || info.IsDir() {
logrus.Debugf("Kubelet image credential provider config file check failed: %v", err)
return false
}
return true
}
func CheckCgroups() (kubeletRoot, runtimeRoot string, hasCFS, hasPIDs bool) { func CheckCgroups() (kubeletRoot, runtimeRoot string, hasCFS, hasPIDs bool) {
cgroupsModeV2 := cgroups.Mode() == cgroups.Unified cgroupsModeV2 := cgroups.Mode() == cgroups.Unified
......
...@@ -87,9 +87,12 @@ type Agent struct { ...@@ -87,9 +87,12 @@ type Agent struct {
CNIPlugin bool CNIPlugin bool
NodeTaints []string NodeTaints []string
NodeLabels []string NodeLabels []string
ImageCredProvBinDir string
ImageCredProvConfig string
IPSECPSK string IPSECPSK string
StrongSwanDir string StrongSwanDir string
PrivateRegistry string PrivateRegistry string
SystemDefaultRegistry string
AirgapExtraRegistry []string AirgapExtraRegistry []string
DisableCCM bool DisableCCM bool
DisableNPC bool DisableNPC bool
...@@ -134,6 +137,7 @@ type Control struct { ...@@ -134,6 +137,7 @@ type Control struct {
FlannelBackend string FlannelBackend string
IPSECPSK string IPSECPSK string
DefaultLocalStoragePath string DefaultLocalStoragePath string
SystemDefaultRegistry string
DisableCCM bool DisableCCM bool
DisableNPC bool DisableNPC bool
DisableKubeProxy bool DisableKubeProxy bool
......
...@@ -182,6 +182,12 @@ func coreControllers(ctx context.Context, sc *Context, config *Config) error { ...@@ -182,6 +182,12 @@ func coreControllers(ctx context.Context, sc *Context, config *Config) error {
return err return err
} }
// apply SystemDefaultRegistry setting to Helm and ServiceLB before starting controllers
if config.ControlConfig.SystemDefaultRegistry != "" {
helm.DefaultJobImage = config.ControlConfig.SystemDefaultRegistry + "/" + helm.DefaultJobImage
servicelb.DefaultLBImage = config.ControlConfig.SystemDefaultRegistry + "/" + servicelb.DefaultLBImage
}
helm.Register(ctx, sc.Apply, helm.Register(ctx, sc.Apply,
sc.Helm.Helm().V1().HelmChart(), sc.Helm.Helm().V1().HelmChart(),
sc.Helm.Helm().V1().HelmChartConfig(), sc.Helm.Helm().V1().HelmChartConfig(),
...@@ -220,9 +226,11 @@ func stageFiles(ctx context.Context, sc *Context, controlConfig *config.Control) ...@@ -220,9 +226,11 @@ func stageFiles(ctx context.Context, sc *Context, controlConfig *config.Control)
} }
dataDir = filepath.Join(controlConfig.DataDir, "manifests") dataDir = filepath.Join(controlConfig.DataDir, "manifests")
templateVars := map[string]string{ templateVars := map[string]string{
"%{CLUSTER_DNS}%": controlConfig.ClusterDNS.String(), "%{CLUSTER_DNS}%": controlConfig.ClusterDNS.String(),
"%{CLUSTER_DOMAIN}%": controlConfig.ClusterDomain, "%{CLUSTER_DOMAIN}%": controlConfig.ClusterDomain,
"%{DEFAULT_LOCAL_STORAGE_PATH}%": controlConfig.DefaultLocalStoragePath, "%{DEFAULT_LOCAL_STORAGE_PATH}%": controlConfig.DefaultLocalStoragePath,
"%{SYSTEM_DEFAULT_REGISTRY}%": registryTemplate(controlConfig.SystemDefaultRegistry),
"%{SYSTEM_DEFAULT_REGISTRY_RAW}%": controlConfig.SystemDefaultRegistry,
} }
skip := controlConfig.Skips skip := controlConfig.Skips
...@@ -237,6 +245,16 @@ func stageFiles(ctx context.Context, sc *Context, controlConfig *config.Control) ...@@ -237,6 +245,16 @@ func stageFiles(ctx context.Context, sc *Context, controlConfig *config.Control)
return deploy.WatchFiles(ctx, sc.Apply, sc.K3s.K3s().V1().Addon(), controlConfig.Disables, dataDir) return deploy.WatchFiles(ctx, sc.Apply, sc.K3s.K3s().V1().Addon(), controlConfig.Disables, dataDir)
} }
// registryTemplate behaves like the system_default_registry template in Rancher helm charts,
// and returns the registry value with a trailing forward slash if the registry string is not empty.
// If it is empty, it is passed through as a no-op.
func registryTemplate(registry string) string {
if registry == "" {
return registry
}
return registry + "/"
}
// isHelmChartTraefikV1 checks for an existing HelmChart resource with spec.chart containing traefik-1, // isHelmChartTraefikV1 checks for an existing HelmChart resource with spec.chart containing traefik-1,
// as deployed by the legacy chart (https://%{KUBERNETES_API}%/static/charts/traefik-1.81.0.tgz) // as deployed by the legacy chart (https://%{KUBERNETES_API}%/static/charts/traefik-1.81.0.tgz)
func isHelmChartTraefikV1(sc *Context) bool { func isHelmChartTraefikV1(sc *Context) bool {
......
...@@ -31,10 +31,10 @@ var ( ...@@ -31,10 +31,10 @@ var (
svcNameLabel = "svccontroller." + version.Program + ".cattle.io/svcname" svcNameLabel = "svccontroller." + version.Program + ".cattle.io/svcname"
daemonsetNodeLabel = "svccontroller." + version.Program + ".cattle.io/enablelb" daemonsetNodeLabel = "svccontroller." + version.Program + ".cattle.io/enablelb"
nodeSelectorLabel = "svccontroller." + version.Program + ".cattle.io/nodeselector" nodeSelectorLabel = "svccontroller." + version.Program + ".cattle.io/nodeselector"
DefaultLBImage = "rancher/klipper-lb:v0.2.0"
) )
const ( const (
image = "rancher/klipper-lb:v0.1.2"
Ready = condition.Cond("Ready") Ready = condition.Cond("Ready")
) )
...@@ -341,7 +341,7 @@ func (h *handler) newDaemonSet(svc *core.Service) (*apps.DaemonSet, error) { ...@@ -341,7 +341,7 @@ func (h *handler) newDaemonSet(svc *core.Service) (*apps.DaemonSet, error) {
portName := fmt.Sprintf("lb-port-%d", port.Port) portName := fmt.Sprintf("lb-port-%d", port.Port)
container := core.Container{ container := core.Container{
Name: portName, Name: portName,
Image: image, Image: DefaultLBImage,
ImagePullPolicy: core.PullIfNotPresent, ImagePullPolicy: core.PullIfNotPresent,
Ports: []core.ContainerPort{ Ports: []core.ContainerPort{
{ {
......
This source diff could not be displayed because it is too large. You can view the blob instead.
docker.io/rancher/coredns-coredns:1.8.3 docker.io/rancher/coredns-coredns:1.8.3
docker.io/rancher/klipper-helm:v0.4.3 docker.io/rancher/klipper-helm:v0.5.0-build20210505
docker.io/rancher/klipper-lb:v0.1.2 docker.io/rancher/klipper-lb:v0.2.0
docker.io/rancher/library-busybox:1.32.1 docker.io/rancher/library-busybox:1.32.1
docker.io/rancher/library-traefik:2.4.8 docker.io/rancher/library-traefik:2.4.8
docker.io/rancher/local-path-provisioner:v0.0.19 docker.io/rancher/local-path-provisioner:v0.0.19
......
{{/* vim: set filetype=mustache: */}}
{{- define "system_default_registry" -}}
{{- if .Values.global.systemDefaultRegistry -}}
{{- printf "%s/" .Values.global.systemDefaultRegistry -}}
{{- else -}}
{{- "" -}}
{{- end -}}
{{- end -}}
...@@ -82,6 +82,10 @@ download_and_package_traefik () { ...@@ -82,6 +82,10 @@ download_and_package_traefik () {
cp -R ${TRAEFIK_TMP_CRD}/overlay-upstream/* ${TRAEFIK_TMP_CHART} cp -R ${TRAEFIK_TMP_CRD}/overlay-upstream/* ${TRAEFIK_TMP_CHART}
rm -rf ${TRAEFIK_TMP_CRD}/overlay-upstream rm -rf ${TRAEFIK_TMP_CRD}/overlay-upstream
# Modify charts to support system-default-registry
echo -e 'global:\n systemDefaultRegistry: ""' >> ${TRAEFIK_TMP_CHART}/values.yaml
find ${TRAEFIK_TMP_CHART} -type f | xargs sed -i 's/{{ .Values.image.name }}/{{ template "system_default_registry" .}}&/g'
# Move CRDs from main chart to CRD chart # Move CRDs from main chart to CRD chart
mkdir -p ${TRAEFIK_TMP_CRD}/templates mkdir -p ${TRAEFIK_TMP_CRD}/templates
mv ${TRAEFIK_TMP_CHART}/crds/* ${TRAEFIK_TMP_CRD}/templates mv ${TRAEFIK_TMP_CHART}/crds/* ${TRAEFIK_TMP_CRD}/templates
......
...@@ -9,7 +9,7 @@ airgap_image_file='scripts/airgap/image-list.txt' ...@@ -9,7 +9,7 @@ airgap_image_file='scripts/airgap/image-list.txt'
images=$(cat "${airgap_image_file}") images=$(cat "${airgap_image_file}")
xargs -n1 docker pull <<< "${images}" xargs -n1 docker pull <<< "${images}"
docker save ${images} -o dist/artifacts/k3s-airgap-images-${ARCH}.tar docker save ${images} -o dist/artifacts/k3s-airgap-images-${ARCH}.tar
zstd -T0 -16 -f --long=25 dist/artifacts/k3s-airgap-images-${ARCH}.tar -o dist/artifacts/k3s-airgap-images-${ARCH}.tar.zst zstd --no-progress -T0 -16 -f --long=25 dist/artifacts/k3s-airgap-images-${ARCH}.tar -o dist/artifacts/k3s-airgap-images-${ARCH}.tar.zst
gzip -v -c dist/artifacts/k3s-airgap-images-${ARCH}.tar > dist/artifacts/k3s-airgap-images-${ARCH}.tar.gz gzip -v -c dist/artifacts/k3s-airgap-images-${ARCH}.tar > dist/artifacts/k3s-airgap-images-${ARCH}.tar.gz
if [ ${ARCH} = amd64 ]; then if [ ${ARCH} = amd64 ]; then
cp "${airgap_image_file}" dist/artifacts/k3s-images.txt cp "${airgap_image_file}" dist/artifacts/k3s-images.txt
......
...@@ -36,7 +36,7 @@ mkdir -p dist/artifacts ...@@ -36,7 +36,7 @@ mkdir -p dist/artifacts
) )
tar cvf ./build/out/data.tar --exclude ./bin/hyperkube ./bin ./etc tar cvf ./build/out/data.tar --exclude ./bin/hyperkube ./bin ./etc
zstd -v -T0 -16 -f --long=25 --rm ./build/out/data.tar -o ./build/out/data.tar.zst zstd --no-progress -T0 -16 -f --long=25 --rm ./build/out/data.tar -o ./build/out/data.tar.zst
HASH=$(sha256sum ./build/out/data.tar.zst | awk '{print $1}') HASH=$(sha256sum ./build/out/data.tar.zst | awk '{print $1}')
cp ./build/out/data.tar.zst ./build/data/${HASH}.tar.zst cp ./build/out/data.tar.zst ./build/data/${HASH}.tar.zst
......
Docker
Copyright 2012-2017 Docker, Inc.
This product includes software developed at Docker, Inc. (https://www.docker.com).
This product contains software (https://github.com/creack/pty) developed
by Keith Rarick, licensed under the MIT License.
The following is courtesy of our legal counsel:
Use and transfer of Docker may be subject to certain restrictions by the
United States and other governments.
It is your responsibility to ensure that your use and/or transfer does not
violate applicable laws.
For more information, please see https://www.bis.doc.gov
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
package config
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/config/configfile"
"github.com/docker/cli/cli/config/credentials"
"github.com/docker/cli/cli/config/types"
"github.com/docker/docker/pkg/homedir"
"github.com/pkg/errors"
)
const (
// ConfigFileName is the name of config file
ConfigFileName = "config.json"
configFileDir = ".docker"
oldConfigfile = ".dockercfg"
contextsDir = "contexts"
)
var (
configDir = os.Getenv("DOCKER_CONFIG")
)
func init() {
if configDir == "" {
configDir = filepath.Join(homedir.Get(), configFileDir)
}
}
// Dir returns the directory the configuration file is stored in
func Dir() string {
return configDir
}
// ContextStoreDir returns the directory the docker contexts are stored in
func ContextStoreDir() string {
return filepath.Join(Dir(), contextsDir)
}
// SetDir sets the directory the configuration file is stored in
func SetDir(dir string) {
configDir = filepath.Clean(dir)
}
// Path returns the path to a file relative to the config dir
func Path(p ...string) (string, error) {
path := filepath.Join(append([]string{Dir()}, p...)...)
if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
return "", errors.Errorf("path %q is outside of root config directory %q", path, Dir())
}
return path, nil
}
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
// a non-nested reader
func LegacyLoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
configFile := configfile.ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
}
err := configFile.LegacyLoadFromReader(configData)
return &configFile, err
}
// LoadFromReader is a convenience function that creates a ConfigFile object from
// a reader
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
configFile := configfile.ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
}
err := configFile.LoadFromReader(configData)
return &configFile, err
}
// Load reads the configuration files in the given directory, and sets up
// the auth config information and returns values.
// FIXME: use the internal golang config parser
func Load(configDir string) (*configfile.ConfigFile, error) {
if configDir == "" {
configDir = Dir()
}
filename := filepath.Join(configDir, ConfigFileName)
configFile := configfile.New(filename)
// Try happy path first - latest config file
if _, err := os.Stat(filename); err == nil {
file, err := os.Open(filename)
if err != nil {
return configFile, errors.Wrap(err, filename)
}
defer file.Close()
err = configFile.LoadFromReader(file)
if err != nil {
err = errors.Wrap(err, filename)
}
return configFile, err
} else if !os.IsNotExist(err) {
// if file is there but we can't stat it for any reason other
// than it doesn't exist then stop
return configFile, errors.Wrap(err, filename)
}
// Can't find latest config file so check for the old one
homedir, err := os.UserHomeDir()
if err != nil {
return configFile, errors.Wrap(err, oldConfigfile)
}
confFile := filepath.Join(homedir, oldConfigfile)
if _, err := os.Stat(confFile); err != nil {
return configFile, nil //missing file is not an error
}
file, err := os.Open(confFile)
if err != nil {
return configFile, errors.Wrap(err, filename)
}
defer file.Close()
err = configFile.LegacyLoadFromReader(file)
if err != nil {
return configFile, errors.Wrap(err, filename)
}
return configFile, nil
}
// LoadDefaultConfigFile attempts to load the default config file and returns
// an initialized ConfigFile struct if none is found.
func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
configFile, err := Load(Dir())
if err != nil {
fmt.Fprintf(stderr, "WARNING: Error loading config file: %v\n", err)
}
if !configFile.ContainsAuth() {
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
}
return configFile
}
package credentials
import (
"github.com/docker/cli/cli/config/types"
)
// Store is the interface that any credentials store must implement.
type Store interface {
// Erase removes credentials from the store for a given server.
Erase(serverAddress string) error
// Get retrieves credentials from the store for a given server.
Get(serverAddress string) (types.AuthConfig, error)
// GetAll retrieves all the credentials from the store.
GetAll() (map[string]types.AuthConfig, error)
// Store saves credentials in the store.
Store(authConfig types.AuthConfig) error
}
package credentials
import (
"os/exec"
)
// DetectDefaultStore return the default credentials store for the platform if
// the store executable is available.
func DetectDefaultStore(store string) string {
platformDefault := defaultCredentialsStore()
// user defined or no default for platform
if store != "" || platformDefault == "" {
return store
}
if _, err := exec.LookPath(remoteCredentialsPrefix + platformDefault); err == nil {
return platformDefault
}
return ""
}
package credentials
func defaultCredentialsStore() string {
return "osxkeychain"
}
package credentials
import (
"os/exec"
)
func defaultCredentialsStore() string {
if _, err := exec.LookPath("pass"); err == nil {
return "pass"
}
return "secretservice"
}
// +build !windows,!darwin,!linux
package credentials
func defaultCredentialsStore() string {
return ""
}
package credentials
func defaultCredentialsStore() string {
return "wincred"
}
package credentials
import (
"strings"
"github.com/docker/cli/cli/config/types"
)
type store interface {
Save() error
GetAuthConfigs() map[string]types.AuthConfig
GetFilename() string
}
// fileStore implements a credentials store using
// the docker configuration file to keep the credentials in plain text.
type fileStore struct {
file store
}
// NewFileStore creates a new file credentials store.
func NewFileStore(file store) Store {
return &fileStore{file: file}
}
// Erase removes the given credentials from the file store.
func (c *fileStore) Erase(serverAddress string) error {
delete(c.file.GetAuthConfigs(), serverAddress)
return c.file.Save()
}
// Get retrieves credentials for a specific server from the file store.
func (c *fileStore) Get(serverAddress string) (types.AuthConfig, error) {
authConfig, ok := c.file.GetAuthConfigs()[serverAddress]
if !ok {
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
for r, ac := range c.file.GetAuthConfigs() {
if serverAddress == ConvertToHostname(r) {
return ac, nil
}
}
authConfig = types.AuthConfig{}
}
return authConfig, nil
}
func (c *fileStore) GetAll() (map[string]types.AuthConfig, error) {
return c.file.GetAuthConfigs(), nil
}
// Store saves the given credentials in the file store.
func (c *fileStore) Store(authConfig types.AuthConfig) error {
c.file.GetAuthConfigs()[authConfig.ServerAddress] = authConfig
return c.file.Save()
}
func (c *fileStore) GetFilename() string {
return c.file.GetFilename()
}
func (c *fileStore) IsFileStore() bool {
return true
}
// ConvertToHostname converts a registry url which has http|https prepended
// to just an hostname.
// Copied from github.com/docker/docker/registry.ConvertToHostname to reduce dependencies.
func ConvertToHostname(url string) string {
stripped := url
if strings.HasPrefix(url, "http://") {
stripped = strings.TrimPrefix(url, "http://")
} else if strings.HasPrefix(url, "https://") {
stripped = strings.TrimPrefix(url, "https://")
}
nameParts := strings.SplitN(stripped, "/", 2)
return nameParts[0]
}
package credentials
import (
"github.com/docker/cli/cli/config/types"
"github.com/docker/docker-credential-helpers/client"
"github.com/docker/docker-credential-helpers/credentials"
)
const (
remoteCredentialsPrefix = "docker-credential-"
tokenUsername = "<token>"
)
// nativeStore implements a credentials store
// using native keychain to keep credentials secure.
// It piggybacks into a file store to keep users' emails.
type nativeStore struct {
programFunc client.ProgramFunc
fileStore Store
}
// NewNativeStore creates a new native store that
// uses a remote helper program to manage credentials.
func NewNativeStore(file store, helperSuffix string) Store {
name := remoteCredentialsPrefix + helperSuffix
return &nativeStore{
programFunc: client.NewShellProgramFunc(name),
fileStore: NewFileStore(file),
}
}
// Erase removes the given credentials from the native store.
func (c *nativeStore) Erase(serverAddress string) error {
if err := client.Erase(c.programFunc, serverAddress); err != nil {
return err
}
// Fallback to plain text store to remove email
return c.fileStore.Erase(serverAddress)
}
// Get retrieves credentials for a specific server from the native store.
func (c *nativeStore) Get(serverAddress string) (types.AuthConfig, error) {
// load user email if it exist or an empty auth config.
auth, _ := c.fileStore.Get(serverAddress)
creds, err := c.getCredentialsFromStore(serverAddress)
if err != nil {
return auth, err
}
auth.Username = creds.Username
auth.IdentityToken = creds.IdentityToken
auth.Password = creds.Password
return auth, nil
}
// GetAll retrieves all the credentials from the native store.
func (c *nativeStore) GetAll() (map[string]types.AuthConfig, error) {
auths, err := c.listCredentialsInStore()
if err != nil {
return nil, err
}
// Emails are only stored in the file store.
// This call can be safely eliminated when emails are removed.
fileConfigs, _ := c.fileStore.GetAll()
authConfigs := make(map[string]types.AuthConfig)
for registry := range auths {
creds, err := c.getCredentialsFromStore(registry)
if err != nil {
return nil, err
}
ac := fileConfigs[registry] // might contain Email
ac.Username = creds.Username
ac.Password = creds.Password
ac.IdentityToken = creds.IdentityToken
authConfigs[registry] = ac
}
return authConfigs, nil
}
// Store saves the given credentials in the file store.
func (c *nativeStore) Store(authConfig types.AuthConfig) error {
if err := c.storeCredentialsInStore(authConfig); err != nil {
return err
}
authConfig.Username = ""
authConfig.Password = ""
authConfig.IdentityToken = ""
// Fallback to old credential in plain text to save only the email
return c.fileStore.Store(authConfig)
}
// storeCredentialsInStore executes the command to store the credentials in the native store.
func (c *nativeStore) storeCredentialsInStore(config types.AuthConfig) error {
creds := &credentials.Credentials{
ServerURL: config.ServerAddress,
Username: config.Username,
Secret: config.Password,
}
if config.IdentityToken != "" {
creds.Username = tokenUsername
creds.Secret = config.IdentityToken
}
return client.Store(c.programFunc, creds)
}
// getCredentialsFromStore executes the command to get the credentials from the native store.
func (c *nativeStore) getCredentialsFromStore(serverAddress string) (types.AuthConfig, error) {
var ret types.AuthConfig
creds, err := client.Get(c.programFunc, serverAddress)
if err != nil {
if credentials.IsErrCredentialsNotFound(err) {
// do not return an error if the credentials are not
// in the keychain. Let docker ask for new credentials.
return ret, nil
}
return ret, err
}
if creds.Username == tokenUsername {
ret.IdentityToken = creds.Secret
} else {
ret.Password = creds.Secret
ret.Username = creds.Username
}
ret.ServerAddress = serverAddress
return ret, nil
}
// listCredentialsInStore returns a listing of stored credentials as a map of
// URL -> username.
func (c *nativeStore) listCredentialsInStore() (map[string]string, error) {
return client.List(c.programFunc)
}
package types
// AuthConfig contains authorization information for connecting to a Registry
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// Email is an optional value associated with the username.
// This field is deprecated and will be removed in a later
// version of docker.
Email string `json:"email,omitempty"`
ServerAddress string `json:"serveraddress,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string `json:"registrytoken,omitempty"`
}
Copyright (c) 2016 David Calavera
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package client
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"github.com/docker/docker-credential-helpers/credentials"
)
// isValidCredsMessage checks if 'msg' contains invalid credentials error message.
// It returns whether the logs are free of invalid credentials errors and the error if it isn't.
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername.
func isValidCredsMessage(msg string) error {
if credentials.IsCredentialsMissingServerURLMessage(msg) {
return credentials.NewErrCredentialsMissingServerURL()
}
if credentials.IsCredentialsMissingUsernameMessage(msg) {
return credentials.NewErrCredentialsMissingUsername()
}
return nil
}
// Store uses an external program to save credentials.
func Store(program ProgramFunc, creds *credentials.Credentials) error {
cmd := program("store")
buffer := new(bytes.Buffer)
if err := json.NewEncoder(buffer).Encode(creds); err != nil {
return err
}
cmd.Input(buffer)
out, err := cmd.Output()
if err != nil {
t := strings.TrimSpace(string(out))
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
err = isValidErr
}
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
}
return nil
}
// Get executes an external program to get the credentials from a native store.
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
cmd := program("get")
cmd.Input(strings.NewReader(serverURL))
out, err := cmd.Output()
if err != nil {
t := strings.TrimSpace(string(out))
if credentials.IsErrCredentialsNotFoundMessage(t) {
return nil, credentials.NewErrCredentialsNotFound()
}
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
err = isValidErr
}
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
}
resp := &credentials.Credentials{
ServerURL: serverURL,
}
if err := json.NewDecoder(bytes.NewReader(out)).Decode(resp); err != nil {
return nil, err
}
return resp, nil
}
// Erase executes a program to remove the server credentials from the native store.
func Erase(program ProgramFunc, serverURL string) error {
cmd := program("erase")
cmd.Input(strings.NewReader(serverURL))
out, err := cmd.Output()
if err != nil {
t := strings.TrimSpace(string(out))
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
err = isValidErr
}
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
}
return nil
}
// List executes a program to list server credentials in the native store.
func List(program ProgramFunc) (map[string]string, error) {
cmd := program("list")
cmd.Input(strings.NewReader("unused"))
out, err := cmd.Output()
if err != nil {
t := strings.TrimSpace(string(out))
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
err = isValidErr
}
return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
}
var resp map[string]string
if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
return nil, err
}
return resp, nil
}
package client
import (
"fmt"
"io"
"os"
"os/exec"
)
// Program is an interface to execute external programs.
type Program interface {
Output() ([]byte, error)
Input(in io.Reader)
}
// ProgramFunc is a type of function that initializes programs based on arguments.
type ProgramFunc func(args ...string) Program
// NewShellProgramFunc creates programs that are executed in a Shell.
func NewShellProgramFunc(name string) ProgramFunc {
return NewShellProgramFuncWithEnv(name, nil)
}
// NewShellProgramFuncWithEnv creates programs that are executed in a Shell with environment variables
func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc {
return func(args ...string) Program {
return &Shell{cmd: createProgramCmdRedirectErr(name, args, env)}
}
}
func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd {
programCmd := exec.Command(commandName, args...)
programCmd.Env = os.Environ()
if env != nil {
for k, v := range *env {
programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v))
}
}
programCmd.Stderr = os.Stderr
return programCmd
}
// Shell invokes shell commands to talk with a remote credentials helper.
type Shell struct {
cmd *exec.Cmd
}
// Output returns responses from the remote credentials helper.
func (s *Shell) Output() ([]byte, error) {
return s.cmd.Output()
}
// Input sets the input to send to a remote credentials helper.
func (s *Shell) Input(in io.Reader) {
s.cmd.Stdin = in
}
package credentials
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"strings"
)
// Credentials holds the information shared between docker and the credentials store.
type Credentials struct {
ServerURL string
Username string
Secret string
}
// isValid checks the integrity of Credentials object such that no credentials lack
// a server URL or a username.
// It returns whether the credentials are valid and the error if it isn't.
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername
func (c *Credentials) isValid() (bool, error) {
if len(c.ServerURL) == 0 {
return false, NewErrCredentialsMissingServerURL()
}
if len(c.Username) == 0 {
return false, NewErrCredentialsMissingUsername()
}
return true, nil
}
// CredsLabel holds the way Docker credentials should be labeled as such in credentials stores that allow labelling.
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
var CredsLabel = "Docker Credentials"
// SetCredsLabel is a simple setter for CredsLabel
func SetCredsLabel(label string) {
CredsLabel = label
}
// Serve initializes the credentials helper and parses the action argument.
// This function is designed to be called from a command line interface.
// It uses os.Args[1] as the key for the action.
// It uses os.Stdin as input and os.Stdout as output.
// This function terminates the program with os.Exit(1) if there is an error.
func Serve(helper Helper) {
var err error
if len(os.Args) != 2 {
err = fmt.Errorf("Usage: %s <store|get|erase|list|version>", os.Args[0])
}
if err == nil {
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout)
}
if err != nil {
fmt.Fprintf(os.Stdout, "%v\n", err)
os.Exit(1)
}
}
// HandleCommand uses a helper and a key to run a credential action.
func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error {
switch key {
case "store":
return Store(helper, in)
case "get":
return Get(helper, in, out)
case "erase":
return Erase(helper, in)
case "list":
return List(helper, out)
case "version":
return PrintVersion(out)
}
return fmt.Errorf("Unknown credential action `%s`", key)
}
// Store uses a helper and an input reader to save credentials.
// The reader must contain the JSON serialization of a Credentials struct.
func Store(helper Helper, reader io.Reader) error {
scanner := bufio.NewScanner(reader)
buffer := new(bytes.Buffer)
for scanner.Scan() {
buffer.Write(scanner.Bytes())
}
if err := scanner.Err(); err != nil && err != io.EOF {
return err
}
var creds Credentials
if err := json.NewDecoder(buffer).Decode(&creds); err != nil {
return err
}
if ok, err := creds.isValid(); !ok {
return err
}
return helper.Add(&creds)
}
// Get retrieves the credentials for a given server url.
// The reader must contain the server URL to search.
// The writer is used to write the JSON serialization of the credentials.
func Get(helper Helper, reader io.Reader, writer io.Writer) error {
scanner := bufio.NewScanner(reader)
buffer := new(bytes.Buffer)
for scanner.Scan() {
buffer.Write(scanner.Bytes())
}
if err := scanner.Err(); err != nil && err != io.EOF {
return err
}
serverURL := strings.TrimSpace(buffer.String())
if len(serverURL) == 0 {
return NewErrCredentialsMissingServerURL()
}
username, secret, err := helper.Get(serverURL)
if err != nil {
return err
}
resp := Credentials{
ServerURL: serverURL,
Username: username,
Secret: secret,
}
buffer.Reset()
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
return err
}
fmt.Fprint(writer, buffer.String())
return nil
}
// Erase removes credentials from the store.
// The reader must contain the server URL to remove.
func Erase(helper Helper, reader io.Reader) error {
scanner := bufio.NewScanner(reader)
buffer := new(bytes.Buffer)
for scanner.Scan() {
buffer.Write(scanner.Bytes())
}
if err := scanner.Err(); err != nil && err != io.EOF {
return err
}
serverURL := strings.TrimSpace(buffer.String())
if len(serverURL) == 0 {
return NewErrCredentialsMissingServerURL()
}
return helper.Delete(serverURL)
}
//List returns all the serverURLs of keys in
//the OS store as a list of strings
func List(helper Helper, writer io.Writer) error {
accts, err := helper.List()
if err != nil {
return err
}
return json.NewEncoder(writer).Encode(accts)
}
//PrintVersion outputs the current version.
func PrintVersion(writer io.Writer) error {
fmt.Fprintln(writer, Version)
return nil
}
package credentials
const (
// ErrCredentialsNotFound standardizes the not found error, so every helper returns
// the same message and docker can handle it properly.
errCredentialsNotFoundMessage = "credentials not found in native keychain"
// ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize
// invalid credentials or credentials management operations
errCredentialsMissingServerURLMessage = "no credentials server URL"
errCredentialsMissingUsernameMessage = "no credentials username"
)
// errCredentialsNotFound represents an error
// raised when credentials are not in the store.
type errCredentialsNotFound struct{}
// Error returns the standard error message
// for when the credentials are not in the store.
func (errCredentialsNotFound) Error() string {
return errCredentialsNotFoundMessage
}
// NewErrCredentialsNotFound creates a new error
// for when the credentials are not in the store.
func NewErrCredentialsNotFound() error {
return errCredentialsNotFound{}
}
// IsErrCredentialsNotFound returns true if the error
// was caused by not having a set of credentials in a store.
func IsErrCredentialsNotFound(err error) bool {
_, ok := err.(errCredentialsNotFound)
return ok
}
// IsErrCredentialsNotFoundMessage returns true if the error
// was caused by not having a set of credentials in a store.
//
// This function helps to check messages returned by an
// external program via its standard output.
func IsErrCredentialsNotFoundMessage(err string) bool {
return err == errCredentialsNotFoundMessage
}
// errCredentialsMissingServerURL represents an error raised
// when the credentials object has no server URL or when no
// server URL is provided to a credentials operation requiring
// one.
type errCredentialsMissingServerURL struct{}
func (errCredentialsMissingServerURL) Error() string {
return errCredentialsMissingServerURLMessage
}
// errCredentialsMissingUsername represents an error raised
// when the credentials object has no username or when no
// username is provided to a credentials operation requiring
// one.
type errCredentialsMissingUsername struct{}
func (errCredentialsMissingUsername) Error() string {
return errCredentialsMissingUsernameMessage
}
// NewErrCredentialsMissingServerURL creates a new error for
// errCredentialsMissingServerURL.
func NewErrCredentialsMissingServerURL() error {
return errCredentialsMissingServerURL{}
}
// NewErrCredentialsMissingUsername creates a new error for
// errCredentialsMissingUsername.
func NewErrCredentialsMissingUsername() error {
return errCredentialsMissingUsername{}
}
// IsCredentialsMissingServerURL returns true if the error
// was an errCredentialsMissingServerURL.
func IsCredentialsMissingServerURL(err error) bool {
_, ok := err.(errCredentialsMissingServerURL)
return ok
}
// IsCredentialsMissingServerURLMessage checks for an
// errCredentialsMissingServerURL in the error message.
func IsCredentialsMissingServerURLMessage(err string) bool {
return err == errCredentialsMissingServerURLMessage
}
// IsCredentialsMissingUsername returns true if the error
// was an errCredentialsMissingUsername.
func IsCredentialsMissingUsername(err error) bool {
_, ok := err.(errCredentialsMissingUsername)
return ok
}
// IsCredentialsMissingUsernameMessage checks for an
// errCredentialsMissingUsername in the error message.
func IsCredentialsMissingUsernameMessage(err string) bool {
return err == errCredentialsMissingUsernameMessage
}
package credentials
// Helper is the interface a credentials store helper must implement.
type Helper interface {
// Add appends credentials to the store.
Add(*Credentials) error
// Delete removes credentials from the store.
Delete(serverURL string) error
// Get retrieves credentials from the store.
// It returns username and secret as strings.
Get(serverURL string) (string, string, error)
// List returns the stored serverURLs and their associated usernames.
List() (map[string]string, error)
}
package credentials
// Version holds a string describing the current version
const Version = "0.6.3"
package homedir // import "github.com/docker/docker/pkg/homedir"
import (
"errors"
"os"
"path/filepath"
"strings"
)
// GetRuntimeDir returns XDG_RUNTIME_DIR.
// XDG_RUNTIME_DIR is typically configured via pam_systemd.
// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set.
//
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
func GetRuntimeDir() (string, error) {
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" {
return xdgRuntimeDir, nil
}
return "", errors.New("could not get XDG_RUNTIME_DIR")
}
// StickRuntimeDirContents sets the sticky bit on files that are under
// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system.
//
// StickyRuntimeDir returns slice of sticked files.
// StickyRuntimeDir returns nil error if XDG_RUNTIME_DIR is not set.
//
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
func StickRuntimeDirContents(files []string) ([]string, error) {
runtimeDir, err := GetRuntimeDir()
if err != nil {
// ignore error if runtimeDir is empty
return nil, nil
}
runtimeDir, err = filepath.Abs(runtimeDir)
if err != nil {
return nil, err
}
var sticked []string
for _, f := range files {
f, err = filepath.Abs(f)
if err != nil {
return sticked, err
}
if strings.HasPrefix(f, runtimeDir+"/") {
if err = stick(f); err != nil {
return sticked, err
}
sticked = append(sticked, f)
}
}
return sticked, nil
}
func stick(f string) error {
st, err := os.Stat(f)
if err != nil {
return err
}
m := st.Mode()
m |= os.ModeSticky
return os.Chmod(f, m)
}
// GetDataHome returns XDG_DATA_HOME.
// GetDataHome returns $HOME/.local/share and nil error if XDG_DATA_HOME is not set.
//
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
func GetDataHome() (string, error) {
if xdgDataHome := os.Getenv("XDG_DATA_HOME"); xdgDataHome != "" {
return xdgDataHome, nil
}
home := os.Getenv("HOME")
if home == "" {
return "", errors.New("could not get either XDG_DATA_HOME or HOME")
}
return filepath.Join(home, ".local", "share"), nil
}
// GetConfigHome returns XDG_CONFIG_HOME.
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set.
//
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html
func GetConfigHome() (string, error) {
if xdgConfigHome := os.Getenv("XDG_CONFIG_HOME"); xdgConfigHome != "" {
return xdgConfigHome, nil
}
home := os.Getenv("HOME")
if home == "" {
return "", errors.New("could not get either XDG_CONFIG_HOME or HOME")
}
return filepath.Join(home, ".config"), nil
}
// +build !linux
package homedir // import "github.com/docker/docker/pkg/homedir"
import (
"errors"
)
// GetRuntimeDir is unsupported on non-linux system.
func GetRuntimeDir() (string, error) {
return "", errors.New("homedir.GetRuntimeDir() is not supported on this system")
}
// StickRuntimeDirContents is unsupported on non-linux system.
func StickRuntimeDirContents(files []string) ([]string, error) {
return nil, errors.New("homedir.StickRuntimeDirContents() is not supported on this system")
}
// GetDataHome is unsupported on non-linux system.
func GetDataHome() (string, error) {
return "", errors.New("homedir.GetDataHome() is not supported on this system")
}
// GetConfigHome is unsupported on non-linux system.
func GetConfigHome() (string, error) {
return "", errors.New("homedir.GetConfigHome() is not supported on this system")
}
// +build !windows
package homedir // import "github.com/docker/docker/pkg/homedir"
import (
"os"
"os/user"
)
// Key returns the env var name for the user's home dir based on
// the platform being run on
func Key() string {
return "HOME"
}
// Get returns the home directory of the current user with the help of
// environment variables depending on the target operating system.
// Returned path should be used with "path/filepath" to form new paths.
//
// If linking statically with cgo enabled against glibc, ensure the
// osusergo build tag is used.
//
// If needing to do nss lookups, do not disable cgo or set osusergo.
func Get() string {
home := os.Getenv(Key())
if home == "" {
if u, err := user.Current(); err == nil {
return u.HomeDir
}
}
return home
}
// GetShortcutString returns the string that is shortcut to user's home directory
// in the native shell of the platform running on.
func GetShortcutString() string {
return "~"
}
package homedir // import "github.com/docker/docker/pkg/homedir"
import (
"os"
)
// Key returns the env var name for the user's home dir based on
// the platform being run on
func Key() string {
return "USERPROFILE"
}
// Get returns the home directory of the current user with the help of
// environment variables depending on the target operating system.
// Returned path should be used with "path/filepath" to form new paths.
func Get() string {
return os.Getenv(Key())
}
// GetShortcutString returns the string that is shortcut to user's home directory
// in the native shell of the platform running on.
func GetShortcutString() string {
return "%USERPROFILE%" // be careful while using in format functions
}
...@@ -8,8 +8,10 @@ ...@@ -8,8 +8,10 @@
# Please keep the list sorted. # Please keep the list sorted.
Amazon.com, Inc
Damian Gryski <dgryski@gmail.com> Damian Gryski <dgryski@gmail.com>
Google Inc. Google Inc.
Jan Mercl <0xjnml@gmail.com> Jan Mercl <0xjnml@gmail.com>
Klaus Post <klauspost@gmail.com>
Rodolfo Carvalho <rhcarvalho@gmail.com> Rodolfo Carvalho <rhcarvalho@gmail.com>
Sebastien Binet <seb.binet@gmail.com> Sebastien Binet <seb.binet@gmail.com>
...@@ -28,7 +28,9 @@ ...@@ -28,7 +28,9 @@
Damian Gryski <dgryski@gmail.com> Damian Gryski <dgryski@gmail.com>
Jan Mercl <0xjnml@gmail.com> Jan Mercl <0xjnml@gmail.com>
Jonathan Swinney <jswinney@amazon.com>
Kai Backman <kaib@golang.org> Kai Backman <kaib@golang.org>
Klaus Post <klauspost@gmail.com>
Marc-Antoine Ruel <maruel@chromium.org> Marc-Antoine Ruel <maruel@chromium.org>
Nigel Tao <nigeltao@golang.org> Nigel Tao <nigeltao@golang.org>
Rob Pike <r@golang.org> Rob Pike <r@golang.org>
......
...@@ -52,6 +52,8 @@ const ( ...@@ -52,6 +52,8 @@ const (
// Otherwise, a newly allocated slice will be returned. // Otherwise, a newly allocated slice will be returned.
// //
// The dst and src must not overlap. It is valid to pass a nil dst. // The dst and src must not overlap. It is valid to pass a nil dst.
//
// Decode handles the Snappy block format, not the Snappy stream format.
func Decode(dst, src []byte) ([]byte, error) { func Decode(dst, src []byte) ([]byte, error) {
dLen, s, err := decodedLen(src) dLen, s, err := decodedLen(src)
if err != nil { if err != nil {
...@@ -83,6 +85,8 @@ func NewReader(r io.Reader) *Reader { ...@@ -83,6 +85,8 @@ func NewReader(r io.Reader) *Reader {
} }
// Reader is an io.Reader that can read Snappy-compressed bytes. // Reader is an io.Reader that can read Snappy-compressed bytes.
//
// Reader handles the Snappy stream format, not the Snappy block format.
type Reader struct { type Reader struct {
r io.Reader r io.Reader
err error err error
......
...@@ -184,7 +184,9 @@ tagLit60Plus: ...@@ -184,7 +184,9 @@ tagLit60Plus:
// checks. In the asm version, we code it once instead of once per switch case. // checks. In the asm version, we code it once instead of once per switch case.
ADDQ CX, SI ADDQ CX, SI
SUBQ $58, SI SUBQ $58, SI
CMPQ SI, R13 MOVQ SI, BX
SUBQ R11, BX
CMPQ BX, R12
JA errCorrupt JA errCorrupt
// case x == 60: // case x == 60:
...@@ -230,7 +232,9 @@ tagCopy4: ...@@ -230,7 +232,9 @@ tagCopy4:
ADDQ $5, SI ADDQ $5, SI
// if uint(s) > uint(len(src)) { etc } // if uint(s) > uint(len(src)) { etc }
CMPQ SI, R13 MOVQ SI, BX
SUBQ R11, BX
CMPQ BX, R12
JA errCorrupt JA errCorrupt
// length = 1 + int(src[s-5])>>2 // length = 1 + int(src[s-5])>>2
...@@ -247,7 +251,9 @@ tagCopy2: ...@@ -247,7 +251,9 @@ tagCopy2:
ADDQ $3, SI ADDQ $3, SI
// if uint(s) > uint(len(src)) { etc } // if uint(s) > uint(len(src)) { etc }
CMPQ SI, R13 MOVQ SI, BX
SUBQ R11, BX
CMPQ BX, R12
JA errCorrupt JA errCorrupt
// length = 1 + int(src[s-3])>>2 // length = 1 + int(src[s-3])>>2
...@@ -271,7 +277,9 @@ tagCopy: ...@@ -271,7 +277,9 @@ tagCopy:
ADDQ $2, SI ADDQ $2, SI
// if uint(s) > uint(len(src)) { etc } // if uint(s) > uint(len(src)) { etc }
CMPQ SI, R13 MOVQ SI, BX
SUBQ R11, BX
CMPQ BX, R12
JA errCorrupt JA errCorrupt
// offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1])) // offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// +build !appengine // +build !appengine
// +build gc // +build gc
// +build !noasm // +build !noasm
// +build amd64 arm64
package snappy package snappy
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !amd64 appengine !gc noasm // +build !amd64,!arm64 appengine !gc noasm
package snappy package snappy
...@@ -87,7 +87,7 @@ func decode(dst, src []byte) int { ...@@ -87,7 +87,7 @@ func decode(dst, src []byte) int {
} }
// Copy from an earlier sub-slice of dst to a later sub-slice. // Copy from an earlier sub-slice of dst to a later sub-slice.
// If no overlap, use the built-in copy: // If no overlap, use the built-in copy:
if offset > length { if offset >= length {
copy(dst[d:d+length], dst[d-offset:]) copy(dst[d:d+length], dst[d-offset:])
d += length d += length
continue continue
......
...@@ -15,6 +15,8 @@ import ( ...@@ -15,6 +15,8 @@ import (
// Otherwise, a newly allocated slice will be returned. // Otherwise, a newly allocated slice will be returned.
// //
// The dst and src must not overlap. It is valid to pass a nil dst. // The dst and src must not overlap. It is valid to pass a nil dst.
//
// Encode handles the Snappy block format, not the Snappy stream format.
func Encode(dst, src []byte) []byte { func Encode(dst, src []byte) []byte {
if n := MaxEncodedLen(len(src)); n < 0 { if n := MaxEncodedLen(len(src)); n < 0 {
panic(ErrTooLarge) panic(ErrTooLarge)
...@@ -139,6 +141,8 @@ func NewBufferedWriter(w io.Writer) *Writer { ...@@ -139,6 +141,8 @@ func NewBufferedWriter(w io.Writer) *Writer {
} }
// Writer is an io.Writer that can write Snappy-compressed bytes. // Writer is an io.Writer that can write Snappy-compressed bytes.
//
// Writer handles the Snappy stream format, not the Snappy block format.
type Writer struct { type Writer struct {
w io.Writer w io.Writer
err error err error
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// +build !appengine // +build !appengine
// +build gc // +build gc
// +build !noasm // +build !noasm
// +build amd64 arm64
package snappy package snappy
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// +build !amd64 appengine !gc noasm // +build !amd64,!arm64 appengine !gc noasm
package snappy package snappy
......
module github.com/golang/snappy
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
// //
// The canonical, C++ implementation is at https://github.com/google/snappy and // The canonical, C++ implementation is at https://github.com/google/snappy and
// it only implements the block format. // it only implements the block format.
package snappy package snappy // import "github.com/golang/snappy"
import ( import (
"hash/crc32" "hash/crc32"
......
...@@ -11,7 +11,6 @@ import ( ...@@ -11,7 +11,6 @@ import (
"time" "time"
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"golang.org/x/xerrors"
) )
func equateAlways(_, _ interface{}) bool { return true } func equateAlways(_, _ interface{}) bool { return true }
...@@ -147,10 +146,3 @@ func areConcreteErrors(x, y interface{}) bool { ...@@ -147,10 +146,3 @@ func areConcreteErrors(x, y interface{}) bool {
_, ok2 := y.(error) _, ok2 := y.(error)
return ok1 && ok2 return ok1 && ok2
} }
func compareErrors(x, y interface{}) bool {
xe := x.(error)
ye := y.(error)
// TODO(≥go1.13): Use standard definition of errors.Is.
return xerrors.Is(xe, ye) || xerrors.Is(ye, xe)
}
// Copyright 2021, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.13
package cmpopts
import "errors"
func compareErrors(x, y interface{}) bool {
xe := x.(error)
ye := y.(error)
return errors.Is(xe, ye) || errors.Is(ye, xe)
}
// Copyright 2021, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.13
// TODO(≥go1.13): For support on <go1.13, we use the xerrors package.
// Drop this file when we no longer support older Go versions.
package cmpopts
import "golang.org/x/xerrors"
func compareErrors(x, y interface{}) bool {
xe := x.(error)
ye := y.(error)
return xerrors.Is(xe, ye) || xerrors.Is(ye, xe)
}
...@@ -79,7 +79,7 @@ func (opts formatOptions) verbosity() uint { ...@@ -79,7 +79,7 @@ func (opts formatOptions) verbosity() uint {
} }
} }
const maxVerbosityPreset = 3 const maxVerbosityPreset = 6
// verbosityPreset modifies the verbosity settings given an index // verbosityPreset modifies the verbosity settings given an index
// between 0 and maxVerbosityPreset, inclusive. // between 0 and maxVerbosityPreset, inclusive.
...@@ -100,7 +100,7 @@ func verbosityPreset(opts formatOptions, i int) formatOptions { ...@@ -100,7 +100,7 @@ func verbosityPreset(opts formatOptions, i int) formatOptions {
func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) { func (opts formatOptions) FormatDiff(v *valueNode, ptrs *pointerReferences) (out textNode) {
if opts.DiffMode == diffIdentical { if opts.DiffMode == diffIdentical {
opts = opts.WithVerbosity(1) opts = opts.WithVerbosity(1)
} else { } else if opts.verbosity() < 3 {
opts = opts.WithVerbosity(3) opts = opts.WithVerbosity(3)
} }
......
...@@ -26,8 +26,6 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { ...@@ -26,8 +26,6 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
return false // No differences detected return false // No differences detected
case !v.ValueX.IsValid() || !v.ValueY.IsValid(): case !v.ValueX.IsValid() || !v.ValueY.IsValid():
return false // Both values must be valid return false // Both values must be valid
case v.Type.Kind() == reflect.Slice && (v.ValueX.Len() == 0 || v.ValueY.Len() == 0):
return false // Both slice values have to be non-empty
case v.NumIgnored > 0: case v.NumIgnored > 0:
return false // Some ignore option was used return false // Some ignore option was used
case v.NumTransformed > 0: case v.NumTransformed > 0:
...@@ -45,7 +43,16 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { ...@@ -45,7 +43,16 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
return false return false
} }
switch t := v.Type; t.Kind() { // Check whether this is an interface with the same concrete types.
t := v.Type
vx, vy := v.ValueX, v.ValueY
if t.Kind() == reflect.Interface && !vx.IsNil() && !vy.IsNil() && vx.Elem().Type() == vy.Elem().Type() {
vx, vy = vx.Elem(), vy.Elem()
t = vx.Type()
}
// Check whether we provide specialized diffing for this type.
switch t.Kind() {
case reflect.String: case reflect.String:
case reflect.Array, reflect.Slice: case reflect.Array, reflect.Slice:
// Only slices of primitive types have specialized handling. // Only slices of primitive types have specialized handling.
...@@ -57,6 +64,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { ...@@ -57,6 +64,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
return false return false
} }
// Both slice values have to be non-empty.
if t.Kind() == reflect.Slice && (vx.Len() == 0 || vy.Len() == 0) {
return false
}
// If a sufficient number of elements already differ, // If a sufficient number of elements already differ,
// use specialized formatting even if length requirement is not met. // use specialized formatting even if length requirement is not met.
if v.NumDiff > v.NumSame { if v.NumDiff > v.NumSame {
...@@ -68,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { ...@@ -68,7 +80,7 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
// Use specialized string diffing for longer slices or strings. // Use specialized string diffing for longer slices or strings.
const minLength = 64 const minLength = 64
return v.ValueX.Len() >= minLength && v.ValueY.Len() >= minLength return vx.Len() >= minLength && vy.Len() >= minLength
} }
// FormatDiffSlice prints a diff for the slices (or strings) represented by v. // FormatDiffSlice prints a diff for the slices (or strings) represented by v.
...@@ -77,6 +89,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool { ...@@ -77,6 +89,11 @@ func (opts formatOptions) CanFormatDiffSlice(v *valueNode) bool {
func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode { func (opts formatOptions) FormatDiffSlice(v *valueNode) textNode {
assert(opts.DiffMode == diffUnknown) assert(opts.DiffMode == diffUnknown)
t, vx, vy := v.Type, v.ValueX, v.ValueY t, vx, vy := v.Type, v.ValueX, v.ValueY
if t.Kind() == reflect.Interface {
vx, vy = vx.Elem(), vy.Elem()
t = vx.Type()
opts = opts.WithTypeMode(emitType)
}
// Auto-detect the type of the data. // Auto-detect the type of the data.
var isLinedText, isText, isBinary bool var isLinedText, isText, isBinary bool
......
# `authn`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/authn?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/authn)
This README outlines how we acquire and use credentials when interacting with a registry.
As much as possible, we attempt to emulate docker's authentication behavior and configuration so that this library "just works" if you've already configured credentials that work with docker; however, when things don't work, a basic understanding of what's going on can help with debugging.
The official documentation for how docker authentication works is (reasonably) scattered across several different sites and GitHub repositories, so we've tried to summarize the relevant bits here.
## tl;dr for consumers of this package
By default, [`pkg/v1/remote`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/remote) uses [`Anonymous`](https://godoc.org/github.com/google/go-containerregistry/pkg/authn#Anonymous) credentials (i.e. _none_), which for most registries will only allow read access to public images.
To use the credentials found in your docker config file, you can use the [`DefaultKeychain`](https://godoc.org/github.com/google/go-containerregistry/pkg/authn#DefaultKeychain), e.g.:
```go
package main
import (
"fmt"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/remote"
)
func main() {
ref, err := name.ParseReference("registry.example.com/private/repo")
if err != nil {
panic(err)
}
// Fetch the manifest using default credentials.
img, err := remote.Get(ref, remote.WithAuthFromKeychain(authn.DefaultKeychain))
if err != nil {
panic(err)
}
// Prints the digest of registry.example.com/private/repo
fmt.Println(img.Digest)
}
```
(If you're only using [gcr.io](https://gcr.io), see the [`pkg/v1/google.Keychain`](https://godoc.org/github.com/google/go-containerregistry/pkg/v1/google#Keychain), which emulates [`docker-credential-gcr`](https://github.com/GoogleCloudPlatform/docker-credential-gcr).)
## The Config File
This file contains various configuration options for docker and is (by default) located at:
* `$HOME/.docker/config.json` (on linux and darwin), or
* `%USERPROFILE%\.docker\config.json` (on windows).
You can override this location with the `DOCKER_CONFIG` environment variable.
### Plaintext
The config file is where your credentials are stored when you invoke `docker login`, e.g. the contents may look something like this:
```json
{
"auths": {
"registry.example.com": {
"auth": "QXp1cmVEaWFtb25kOmh1bnRlcjI="
}
}
}
```
The `auths` map has an entry per registry, and the `auth` field contains your username and password encoded as [HTTP 'Basic' Auth](https://tools.ietf.org/html/rfc7617).
**NOTE**: This means that your credentials are stored _in plaintext_:
```bash
$ echo "QXp1cmVEaWFtb25kOmh1bnRlcjI=" | base64 -d
AzureDiamond:hunter2
```
For what it's worth, this config file is equivalent to:
```json
{
"auths": {
"registry.example.com": {
"username": "AzureDiamond",
"password": "hunter2"
}
}
}
```
... which is useful to know if e.g. your CI system provides you a registry username and password via environment variables and you want to populate this file manually without invoking `docker login`.
### Helpers
If you log in like this, docker will warn you that you should use a [credential helper](https://docs.docker.com/engine/reference/commandline/login/#credentials-store), and you should!
To configure a global credential helper:
```json
{
"credsStore": "osxkeychain"
}
```
To configure a per-registry credential helper:
```json
{
"credHelpers": {
"gcr.io": "gcr"
}
}
```
We use [`github.com/docker/cli/cli/config.Load`](https://godoc.org/github.com/docker/cli/cli/config#Load) to parse the config file and invoke any necessary credential helpers. This handles the logic of taking a [`ConfigFile`](https://github.com/docker/cli/blob/ba63a92655c0bea4857b8d6cc4991498858b3c60/cli/config/configfile/file.go#L25-L54) + registry domain and producing an [`AuthConfig`](https://github.com/docker/cli/blob/ba63a92655c0bea4857b8d6cc4991498858b3c60/cli/config/types/authconfig.go#L3-L22), which determines how we authenticate to the registry.
## Credential Helpers
The [credential helper protocol](https://github.com/docker/docker-credential-helpers) allows you to configure a binary that supplies credentials for the registry, rather than hard-coding them in the config file.
The protocol has several verbs, but the one we most care about is `get`.
For example, using the following config file:
```json
{
"credHelpers": {
"gcr.io": "gcr",
"eu.gcr.io": "gcr"
}
}
```
To acquire credentials for `gcr.io`, we look in the `credHelpers` map to find
the credential helper for `gcr.io` is `gcr`. By appending that value to
`docker-credential-`, we can get the name of the binary we need to use.
For this example, that's `docker-credential-gcr`, which must be on our `$PATH`.
We'll then invoke that binary to get credentials:
```bash
$ echo "gcr.io" | docker-credential-gcr get
{"Username":"_token","Secret":"<long access token>"}
```
You can configure the same credential helper for multiple registries, which is
why we need to pass the domain in via STDIN, e.g. if we were trying to access
`eu.gcr.io`, we'd do this instead:
```bash
$ echo "eu.gcr.io" | docker-credential-gcr get
{"Username":"_token","Secret":"<long access token>"}
```
### Debugging credential helpers
If a credential helper is configured but doesn't seem to be working, it can be
challenging to debug. Implementing a fake credential helper lets you poke around
to make it easier to see where the failure is happening.
This "implements" a credential helper with hard-coded values:
```
#!/usr/bin/env bash
echo '{"Username":"<token>","Secret":"hunter2"}'
```
This implements a credential helper that prints the output of
`docker-credential-gcr` to both stderr and whatever called it, which allows you
to snoop on another credential helper:
```
#!/usr/bin/env bash
docker-credential-gcr $@ | tee >(cat 1>&2)
```
Put those files somewhere on your path, naming them e.g.
`docker-credential-hardcoded` and `docker-credential-tee`, then modify the
config file to use them:
```json
{
"credHelpers": {
"gcr.io": "tee",
"eu.gcr.io": "hardcoded"
}
}
```
The `docker-credential-tee` trick works with both `crane` and `docker`:
```bash
$ crane manifest gcr.io/google-containers/pause > /dev/null
{"ServerURL":"","Username":"_dcgcr_1_5_0_token","Secret":"<redacted>"}
$ docker pull gcr.io/google-containers/pause
Using default tag: latest
{"ServerURL":"","Username":"_dcgcr_1_5_0_token","Secret":"<redacted>"}
latest: Pulling from google-containers/pause
a3ed95caeb02: Pull complete
4964c72cd024: Pull complete
Digest: sha256:a78c2d6208eff9b672de43f880093100050983047b7b0afe0217d3656e1b0d5f
Status: Downloaded newer image for gcr.io/google-containers/pause:latest
gcr.io/google-containers/pause:latest
```
## The Registry
There are two methods for authenticating against a registry:
[token](https://docs.docker.com/registry/spec/auth/token/) and
[oauth2](https://docs.docker.com/registry/spec/auth/oauth/).
Both methods are used to acquire an opaque `Bearer` token (or
[RegistryToken](https://github.com/docker/cli/blob/ba63a92655c0bea4857b8d6cc4991498858b3c60/cli/config/types/authconfig.go#L21))
to use in the `Authorization` header. The registry will return a `401
Unauthorized` during the [version
check](https://github.com/opencontainers/distribution-spec/blob/2c3975d1f03b67c9a0203199038adea0413f0573/spec.md#api-version-check)
(or during normal operations) with
[Www-Authenticate](https://tools.ietf.org/html/rfc7235#section-4.1) challenge
indicating how to proceed.
### Token
If we get back an `AuthConfig` containing a [`Username/Password`](https://github.com/docker/cli/blob/ba63a92655c0bea4857b8d6cc4991498858b3c60/cli/config/types/authconfig.go#L5-L6)
or
[`Auth`](https://github.com/docker/cli/blob/ba63a92655c0bea4857b8d6cc4991498858b3c60/cli/config/types/authconfig.go#L7),
we'll use the token method for authentication:
![basic](../../images/credhelper-basic.svg)
### OAuth 2
If we get back an `AuthConfig` containing an [`IdentityToken`](https://github.com/docker/cli/blob/ba63a92655c0bea4857b8d6cc4991498858b3c60/cli/config/types/authconfig.go#L18)
we'll use the oauth2 method for authentication:
![oauth](../../images/credhelper-oauth.svg)
This happens when a credential helper returns a response with the
[`Username`](https://github.com/docker/docker-credential-helpers/blob/f78081d1f7fef6ad74ad6b79368de6348386e591/credentials/credentials.go#L16)
set to `<token>` (no, that's not a placeholder, the literal string `"<token>"`).
It is unclear why: [moby/moby#36926](https://github.com/moby/moby/issues/36926).
We only support the oauth2 `grant_type` for `refresh_token` ([#629](https://github.com/google/go-containerregistry/issues/629)),
since it's impossible to determine from the registry response whether we should
use oauth, and the token method for authentication is widely implemented by
registries.
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
// anonymous implements Authenticator for anonymous authentication.
type anonymous struct{}
// Authorization implements Authenticator.
func (a *anonymous) Authorization() (*AuthConfig, error) {
return &AuthConfig{}, nil
}
// Anonymous is a singleton Authenticator for providing anonymous auth.
var Anonymous Authenticator = &anonymous{}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
// auth is an Authenticator that simply returns the wrapped AuthConfig.
type auth struct {
config AuthConfig
}
// FromConfig returns an Authenticator that just returns the given AuthConfig.
func FromConfig(cfg AuthConfig) Authenticator {
return &auth{cfg}
}
// Authorization implements Authenticator.
func (a *auth) Authorization() (*AuthConfig, error) {
return &a.config, nil
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
// AuthConfig contains authorization information for connecting to a Registry
// Inlined what we use from github.com/docker/cli/cli/config/types
type AuthConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Auth string `json:"auth,omitempty"`
// IdentityToken is used to authenticate the user and get
// an access token for the registry.
IdentityToken string `json:"identitytoken,omitempty"`
// RegistryToken is a bearer token to be sent to a registry
RegistryToken string `json:"registrytoken,omitempty"`
}
// Authenticator is used to authenticate Docker transports.
type Authenticator interface {
// Authorization returns the value to use in an http transport's Authorization header.
Authorization() (*AuthConfig, error)
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
// Basic implements Authenticator for basic authentication.
type Basic struct {
Username string
Password string
}
// Authorization implements Authenticator.
func (b *Basic) Authorization() (*AuthConfig, error) {
return &AuthConfig{
Username: b.Username,
Password: b.Password,
}, nil
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
// Bearer implements Authenticator for bearer authentication.
type Bearer struct {
Token string `json:"token"`
}
// Authorization implements Authenticator.
func (b *Bearer) Authorization() (*AuthConfig, error) {
return &AuthConfig{
RegistryToken: b.Token,
}, nil
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn defines different methods of authentication for
// talking to a container registry.
package authn
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
import (
"os"
"github.com/docker/cli/cli/config"
"github.com/docker/cli/cli/config/types"
"github.com/google/go-containerregistry/pkg/name"
)
// Resource represents a registry or repository that can be authenticated against.
type Resource interface {
// String returns the full string representation of the target, e.g.
// gcr.io/my-project or just gcr.io.
String() string
// RegistryStr returns just the registry portion of the target, e.g. for
// gcr.io/my-project, this should just return gcr.io. This is needed to
// pull out an appropriate hostname.
RegistryStr() string
}
// Keychain is an interface for resolving an image reference to a credential.
type Keychain interface {
// Resolve looks up the most appropriate credential for the specified target.
Resolve(Resource) (Authenticator, error)
}
// defaultKeychain implements Keychain with the semantics of the standard Docker
// credential keychain.
type defaultKeychain struct{}
var (
// DefaultKeychain implements Keychain by interpreting the docker config file.
DefaultKeychain Keychain = &defaultKeychain{}
)
const (
// DefaultAuthKey is the key used for dockerhub in config files, which
// is hardcoded for historical reasons.
DefaultAuthKey = "https://" + name.DefaultRegistry + "/v1/"
)
// Resolve implements Keychain.
func (dk *defaultKeychain) Resolve(target Resource) (Authenticator, error) {
cf, err := config.Load(os.Getenv("DOCKER_CONFIG"))
if err != nil {
return nil, err
}
// See:
// https://github.com/google/ko/issues/90
// https://github.com/moby/moby/blob/fc01c2b481097a6057bec3cd1ab2d7b4488c50c4/registry/config.go#L397-L404
key := target.RegistryStr()
if key == name.DefaultRegistry {
key = DefaultAuthKey
}
cfg, err := cf.GetAuthConfig(key)
if err != nil {
return nil, err
}
empty := types.AuthConfig{}
if cfg == empty {
return Anonymous, nil
}
return FromConfig(AuthConfig{
Username: cfg.Username,
Password: cfg.Password,
Auth: cfg.Auth,
IdentityToken: cfg.IdentityToken,
RegistryToken: cfg.RegistryToken,
}), nil
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 authn
type multiKeychain struct {
keychains []Keychain
}
// Assert that our multi-keychain implements Keychain.
var _ (Keychain) = (*multiKeychain)(nil)
// NewMultiKeychain composes a list of keychains into one new keychain.
func NewMultiKeychain(kcs ...Keychain) Keychain {
return &multiKeychain{keychains: kcs}
}
// Resolve implements Keychain.
func (mk *multiKeychain) Resolve(target Resource) (Authenticator, error) {
for _, kc := range mk.keychains {
auth, err := kc.Resolve(target)
if err != nil {
return nil, err
}
if auth != Anonymous {
return auth, nil
}
}
return Anonymous, nil
}
# `name`
[![GoDoc](https://godoc.org/github.com/google/go-containerregistry/pkg/name?status.svg)](https://godoc.org/github.com/google/go-containerregistry/pkg/name)
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import (
"strings"
"unicode/utf8"
)
// stripRunesFn returns a function which returns -1 (i.e. a value which
// signals deletion in strings.Map) for runes in 'runes', and the rune otherwise.
func stripRunesFn(runes string) func(rune) rune {
return func(r rune) rune {
if strings.ContainsRune(runes, r) {
return -1
}
return r
}
}
// checkElement checks a given named element matches character and length restrictions.
// Returns true if the given element adheres to the given restrictions, false otherwise.
func checkElement(name, element, allowedRunes string, minRunes, maxRunes int) error {
numRunes := utf8.RuneCountInString(element)
if (numRunes < minRunes) || (maxRunes < numRunes) {
return NewErrBadName("%s must be between %d and %d runes in length: %s", name, minRunes, maxRunes, element)
} else if len(strings.Map(stripRunesFn(allowedRunes), element)) != 0 {
return NewErrBadName("%s can only contain the runes `%s`: %s", name, allowedRunes, element)
}
return nil
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import (
"strings"
)
const (
// These have the form: sha256:<hex string>
// TODO(dekkagaijin): replace with opencontainers/go-digest or docker/distribution's validation.
digestChars = "sh:0123456789abcdef"
digestDelim = "@"
)
// Digest stores a digest name in a structured form.
type Digest struct {
Repository
digest string
original string
}
// Ensure Digest implements Reference
var _ Reference = (*Digest)(nil)
// Context implements Reference.
func (d Digest) Context() Repository {
return d.Repository
}
// Identifier implements Reference.
func (d Digest) Identifier() string {
return d.DigestStr()
}
// DigestStr returns the digest component of the Digest.
func (d Digest) DigestStr() string {
return d.digest
}
// Name returns the name from which the Digest was derived.
func (d Digest) Name() string {
return d.Repository.Name() + digestDelim + d.DigestStr()
}
// String returns the original input string.
func (d Digest) String() string {
return d.original
}
func checkDigest(name string) error {
return checkElement("digest", name, digestChars, 7+64, 7+64)
}
// NewDigest returns a new Digest representing the given name.
func NewDigest(name string, opts ...Option) (Digest, error) {
// Split on "@"
parts := strings.Split(name, digestDelim)
if len(parts) != 2 {
return Digest{}, NewErrBadName("a digest must contain exactly one '@' separator (e.g. registry/repository@digest) saw: %s", name)
}
base := parts[0]
digest := parts[1]
// Always check that the digest is valid.
if err := checkDigest(digest); err != nil {
return Digest{}, err
}
tag, err := NewTag(base, opts...)
if err == nil {
base = tag.Repository.Name()
}
repo, err := NewRepository(base, opts...)
if err != nil {
return Digest{}, err
}
return Digest{
Repository: repo,
digest: digest,
original: name,
}, nil
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name defines structured types for representing image references.
//
// What's in a name? For image references, not nearly enough!
//
// Image references look a lot like URLs, but they differ in that they don't
// contain the scheme (http or https), they can end with a :tag or a @digest
// (the latter being validated), and they perform defaulting for missing
// components.
//
// Since image references don't contain the scheme, we do our best to infer
// if we use http or https from the given hostname. We allow http fallback for
// any host that looks like localhost (localhost, 127.0.0.1, ::1), ends in
// ".local", or is in the "private" address space per RFC 1918. For everything
// else, we assume https only. To override this heuristic, use the Insecure
// option.
//
// Image references with a digest signal to us that we should verify the content
// of the image matches the digest. E.g. when pulling a Digest reference, we'll
// calculate the sha256 of the manifest returned by the registry and error out
// if it doesn't match what we asked for.
//
// For defaulting, we interpret "ubuntu" as
// "index.docker.io/library/ubuntu:latest" because we add the missing repo
// "library", the missing registry "index.docker.io", and the missing tag
// "latest". To disable this defaulting, use the StrictValidation option. This
// is useful e.g. to only allow image references that explicitly set a tag or
// digest, so that you don't accidentally pull "latest".
package name
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import "fmt"
// ErrBadName is an error for when a bad docker name is supplied.
type ErrBadName struct {
info string
}
func (e *ErrBadName) Error() string {
return e.info
}
// NewErrBadName returns a ErrBadName which returns the given formatted string from Error().
func NewErrBadName(fmtStr string, args ...interface{}) *ErrBadName {
return &ErrBadName{fmt.Sprintf(fmtStr, args...)}
}
// IsErrBadName returns true if the given error is an ErrBadName.
func IsErrBadName(err error) bool {
_, ok := err.(*ErrBadName)
return ok
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
const (
// DefaultRegistry is the registry name that will be used if no registry
// provided and the default is not overridden.
DefaultRegistry = "index.docker.io"
defaultRegistryAlias = "docker.io"
// DefaultTag is the tag name that will be used if no tag provided and the
// default is not overridden.
DefaultTag = "latest"
)
type options struct {
strict bool // weak by default
insecure bool // secure by default
defaultRegistry string
defaultTag string
}
func makeOptions(opts ...Option) options {
opt := options{
defaultRegistry: DefaultRegistry,
defaultTag: DefaultTag,
}
for _, o := range opts {
o(&opt)
}
return opt
}
// Option is a functional option for name parsing.
type Option func(*options)
// StrictValidation is an Option that requires image references to be fully
// specified; i.e. no defaulting for registry (dockerhub), repo (library),
// or tag (latest).
func StrictValidation(opts *options) {
opts.strict = true
}
// WeakValidation is an Option that sets defaults when parsing names, see
// StrictValidation.
func WeakValidation(opts *options) {
opts.strict = false
}
// Insecure is an Option that allows image references to be fetched without TLS.
func Insecure(opts *options) {
opts.insecure = true
}
// OptionFn is a function that returns an option.
type OptionFn func() Option
// WithDefaultRegistry sets the default registry that will be used if one is not
// provided.
func WithDefaultRegistry(r string) Option {
return func(opts *options) {
opts.defaultRegistry = r
}
}
// WithDefaultTag sets the default tag that will be used if one is not provided.
func WithDefaultTag(t string) Option {
return func(opts *options) {
opts.defaultTag = t
}
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import (
"fmt"
)
// Reference defines the interface that consumers use when they can
// take either a tag or a digest.
type Reference interface {
fmt.Stringer
// Context accesses the Repository context of the reference.
Context() Repository
// Identifier accesses the type-specific portion of the reference.
Identifier() string
// Name is the fully-qualified reference name.
Name() string
// Scope is the scope needed to access this reference.
Scope(string) string
}
// ParseReference parses the string as a reference, either by tag or digest.
func ParseReference(s string, opts ...Option) (Reference, error) {
if t, err := NewTag(s, opts...); err == nil {
return t, nil
}
if d, err := NewDigest(s, opts...); err == nil {
return d, nil
}
return nil, NewErrBadName("could not parse reference: " + s)
}
type stringConst string
// MustParseReference behaves like ParseReference, but panics instead of
// returning an error. It's intended for use in tests, or when a value is
// expected to be valid at code authoring time.
//
// To discourage its use in scenarios where the value is not known at code
// authoring time, it must be passed a string constant:
//
// const str = "valid/string"
// MustParseReference(str)
// MustParseReference("another/valid/string")
// MustParseReference(str + "/and/more")
//
// These will not compile:
//
// var str = "valid/string"
// MustParseReference(str)
// MustParseReference(strings.Join([]string{"valid", "string"}, "/"))
func MustParseReference(s stringConst, opts ...Option) Reference {
ref, err := ParseReference(string(s), opts...)
if err != nil {
panic(err)
}
return ref
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import (
"net"
"net/url"
"regexp"
"strings"
)
// Detect more complex forms of local references.
var reLocal = regexp.MustCompile(`.*\.local(?:host)?(?::\d{1,5})?$`)
// Detect the loopback IP (127.0.0.1)
var reLoopback = regexp.MustCompile(regexp.QuoteMeta("127.0.0.1"))
// Detect the loopback IPV6 (::1)
var reipv6Loopback = regexp.MustCompile(regexp.QuoteMeta("::1"))
// Registry stores a docker registry name in a structured form.
type Registry struct {
insecure bool
registry string
}
// RegistryStr returns the registry component of the Registry.
func (r Registry) RegistryStr() string {
return r.registry
}
// Name returns the name from which the Registry was derived.
func (r Registry) Name() string {
return r.RegistryStr()
}
func (r Registry) String() string {
return r.Name()
}
// Scope returns the scope required to access the registry.
func (r Registry) Scope(string) string {
// The only resource under 'registry' is 'catalog'. http://goo.gl/N9cN9Z
return "registry:catalog:*"
}
func (r Registry) isRFC1918() bool {
ipStr := strings.Split(r.Name(), ":")[0]
ip := net.ParseIP(ipStr)
if ip == nil {
return false
}
for _, cidr := range []string{"10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"} {
_, block, _ := net.ParseCIDR(cidr)
if block.Contains(ip) {
return true
}
}
return false
}
// Scheme returns https scheme for all the endpoints except localhost or when explicitly defined.
func (r Registry) Scheme() string {
if r.insecure {
return "http"
}
if r.isRFC1918() {
return "http"
}
if strings.HasPrefix(r.Name(), "localhost:") {
return "http"
}
if reLocal.MatchString(r.Name()) {
return "http"
}
if reLoopback.MatchString(r.Name()) {
return "http"
}
if reipv6Loopback.MatchString(r.Name()) {
return "http"
}
return "https"
}
func checkRegistry(name string) error {
// Per RFC 3986, registries (authorities) are required to be prefixed with "//"
// url.Host == hostname[:port] == authority
if url, err := url.Parse("//" + name); err != nil || url.Host != name {
return NewErrBadName("registries must be valid RFC 3986 URI authorities: %s", name)
}
return nil
}
// NewRegistry returns a Registry based on the given name.
// Strict validation requires explicit, valid RFC 3986 URI authorities to be given.
func NewRegistry(name string, opts ...Option) (Registry, error) {
opt := makeOptions(opts...)
if opt.strict && len(name) == 0 {
return Registry{}, NewErrBadName("strict validation requires the registry to be explicitly defined")
}
if err := checkRegistry(name); err != nil {
return Registry{}, err
}
if name == "" {
name = opt.defaultRegistry
}
// Rewrite "docker.io" to "index.docker.io".
// See: https://github.com/google/go-containerregistry/issues/68
if name == defaultRegistryAlias {
name = DefaultRegistry
}
return Registry{registry: name, insecure: opt.insecure}, nil
}
// NewInsecureRegistry returns an Insecure Registry based on the given name.
//
// Deprecated: Use the Insecure Option with NewRegistry instead.
func NewInsecureRegistry(name string, opts ...Option) (Registry, error) {
opts = append(opts, Insecure)
return NewRegistry(name, opts...)
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import (
"fmt"
"strings"
)
const (
defaultNamespace = "library"
repositoryChars = "abcdefghijklmnopqrstuvwxyz0123456789_-./"
regRepoDelimiter = "/"
)
// Repository stores a docker repository name in a structured form.
type Repository struct {
Registry
repository string
}
// See https://docs.docker.com/docker-hub/official_repos
func hasImplicitNamespace(repo string, reg Registry) bool {
return !strings.ContainsRune(repo, '/') && reg.RegistryStr() == DefaultRegistry
}
// RepositoryStr returns the repository component of the Repository.
func (r Repository) RepositoryStr() string {
if hasImplicitNamespace(r.repository, r.Registry) {
return fmt.Sprintf("%s/%s", defaultNamespace, r.repository)
}
return r.repository
}
// Name returns the name from which the Repository was derived.
func (r Repository) Name() string {
regName := r.Registry.Name()
if regName != "" {
return regName + regRepoDelimiter + r.RepositoryStr()
}
// TODO: As far as I can tell, this is unreachable.
return r.RepositoryStr()
}
func (r Repository) String() string {
return r.Name()
}
// Scope returns the scope required to perform the given action on the registry.
// TODO(jonjohnsonjr): consider moving scopes to a separate package.
func (r Repository) Scope(action string) string {
return fmt.Sprintf("repository:%s:%s", r.RepositoryStr(), action)
}
func checkRepository(repository string) error {
return checkElement("repository", repository, repositoryChars, 2, 255)
}
// NewRepository returns a new Repository representing the given name, according to the given strictness.
func NewRepository(name string, opts ...Option) (Repository, error) {
opt := makeOptions(opts...)
if len(name) == 0 {
return Repository{}, NewErrBadName("a repository name must be specified")
}
var registry string
repo := name
parts := strings.SplitN(name, regRepoDelimiter, 2)
if len(parts) == 2 && (strings.ContainsRune(parts[0], '.') || strings.ContainsRune(parts[0], ':')) {
// The first part of the repository is treated as the registry domain
// iff it contains a '.' or ':' character, otherwise it is all repository
// and the domain defaults to Docker Hub.
registry = parts[0]
repo = parts[1]
}
if err := checkRepository(repo); err != nil {
return Repository{}, err
}
reg, err := NewRegistry(registry, opts...)
if err != nil {
return Repository{}, err
}
if hasImplicitNamespace(repo, reg) && opt.strict {
return Repository{}, NewErrBadName("strict validation requires the full repository path (missing 'library')")
}
return Repository{reg, repo}, nil
}
// Tag returns a Tag in this Repository.
func (r Repository) Tag(identifier string) Tag {
t := Tag{
tag: identifier,
Repository: r,
}
t.original = t.Name()
return t
}
// Digest returns a Digest in this Repository.
func (r Repository) Digest(identifier string) Digest {
d := Digest{
digest: identifier,
Repository: r,
}
d.original = d.Name()
return d
}
// Copyright 2018 Google LLC All Rights Reserved.
//
// 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 name
import (
"strings"
)
const (
// TODO(dekkagaijin): use the docker/distribution regexes for validation.
tagChars = "abcdefghijklmnopqrstuvwxyz0123456789_-.ABCDEFGHIJKLMNOPQRSTUVWXYZ"
tagDelim = ":"
)
// Tag stores a docker tag name in a structured form.
type Tag struct {
Repository
tag string
original string
}
// Ensure Tag implements Reference
var _ Reference = (*Tag)(nil)
// Context implements Reference.
func (t Tag) Context() Repository {
return t.Repository
}
// Identifier implements Reference.
func (t Tag) Identifier() string {
return t.TagStr()
}
// TagStr returns the tag component of the Tag.
func (t Tag) TagStr() string {
return t.tag
}
// Name returns the name from which the Tag was derived.
func (t Tag) Name() string {
return t.Repository.Name() + tagDelim + t.TagStr()
}
// String returns the original input string.
func (t Tag) String() string {
return t.original
}
// Scope returns the scope required to perform the given action on the tag.
func (t Tag) Scope(action string) string {
return t.Repository.Scope(action)
}
func checkTag(name string) error {
return checkElement("tag", name, tagChars, 1, 128)
}
// NewTag returns a new Tag representing the given name, according to the given strictness.
func NewTag(name string, opts ...Option) (Tag, error) {
opt := makeOptions(opts...)
base := name
tag := ""
// Split on ":"
parts := strings.Split(name, tagDelim)
// Verify that we aren't confusing a tag for a hostname w/ port for the purposes of weak validation.
if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], regRepoDelimiter) {
base = strings.Join(parts[:len(parts)-1], tagDelim)
tag = parts[len(parts)-1]
}
// We don't require a tag, but if we get one check it's valid,
// even when not being strict.
// If we are being strict, we want to validate the tag regardless in case
// it's empty.
if tag != "" || opt.strict {
if err := checkTag(tag); err != nil {
return Tag{}, err
}
}
if tag == "" {
tag = opt.defaultTag
}
repo, err := NewRepository(base, opts...)
if err != nil {
return Tag{}, err
}
return Tag{
Repository: repo,
tag: tag,
original: name,
}, nil
}
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"crypto/sha256" "crypto/sha256"
"fmt" "fmt"
"os" "os"
"regexp"
"sort" "sort"
"strings" "strings"
...@@ -27,7 +28,9 @@ import ( ...@@ -27,7 +28,9 @@ import (
) )
var ( var (
trueVal = true trueVal = true
commaRE = regexp.MustCompile(`\\*,`)
DefaultJobImage = "rancher/klipper-helm:v0.5.0-build20210505"
) )
type Controller struct { type Controller struct {
...@@ -39,12 +42,11 @@ type Controller struct { ...@@ -39,12 +42,11 @@ type Controller struct {
} }
const ( const (
DefaultJobImage = "rancher/klipper-helm:v0.4.3" Label = "helmcharts.helm.cattle.io/chart"
Label = "helmcharts.helm.cattle.io/chart" Annotation = "helmcharts.helm.cattle.io/configHash"
Annotation = "helmcharts.helm.cattle.io/configHash" CRDName = "helmcharts.helm.cattle.io"
CRDName = "helmcharts.helm.cattle.io" ConfigCRDName = "helmchartconfigs.helm.cattle.io"
ConfigCRDName = "helmchartconfigs.helm.cattle.io" Name = "helm-controller"
Name = "helm-controller"
) )
func Register(ctx context.Context, apply apply.Apply, func Register(ctx context.Context, apply apply.Apply,
...@@ -400,7 +402,7 @@ func args(chart *helmv1.HelmChart) []string { ...@@ -400,7 +402,7 @@ func args(chart *helmv1.HelmChart) []string {
if val.StrVal == "false" || val.StrVal == "true" { if val.StrVal == "false" || val.StrVal == "true" {
args = append(args, "--set", fmt.Sprintf("%s=%s", k, val.StrVal)) args = append(args, "--set", fmt.Sprintf("%s=%s", k, val.StrVal))
} else if val.StrVal != "" { } else if val.StrVal != "" {
args = append(args, "--set-string", fmt.Sprintf("%s=%s", k, val.StrVal)) args = append(args, "--set-string", fmt.Sprintf("%s=%s", k, commaRE.ReplaceAllStringFunc(val.StrVal, escapeComma)))
} else { } else {
args = append(args, "--set", fmt.Sprintf("%s=%d", k, val.IntVal)) args = append(args, "--set", fmt.Sprintf("%s=%d", k, val.IntVal))
} }
...@@ -418,6 +420,18 @@ func keys(val map[string]intstr.IntOrString) []string { ...@@ -418,6 +420,18 @@ func keys(val map[string]intstr.IntOrString) []string {
return keys return keys
} }
// escapeComma should be passed a string consisting of zero or more backslashes, followed by a comma.
// If there are an even number of characters (such as `\,` or `\\\,`) then the comma is escaped.
// If there are an uneven number of characters (such as `,` or `\\,` then the comma is not escaped,
// and we need to escape it by adding an additional backslash.
// This logic is difficult if not impossible to accomplish with a simple regex submatch replace.
func escapeComma(match string) string {
if len(match)%2 == 1 {
match = `\` + match
}
return match
}
func setProxyEnv(job *batch.Job) { func setProxyEnv(job *batch.Job) {
proxySysEnv := []string{ proxySysEnv := []string{
"all_proxy", "all_proxy",
......
...@@ -92,7 +92,6 @@ func (c *cState) init(bw *bitWriter, ct *cTable, tableLog uint8, first symbolTra ...@@ -92,7 +92,6 @@ func (c *cState) init(bw *bitWriter, ct *cTable, tableLog uint8, first symbolTra
im := int32((nbBitsOut << 16) - first.deltaNbBits) im := int32((nbBitsOut << 16) - first.deltaNbBits)
lu := (im >> nbBitsOut) + first.deltaFindState lu := (im >> nbBitsOut) + first.deltaFindState
c.state = c.stateTable[lu] c.state = c.stateTable[lu]
return
} }
// encode the output symbol provided and write it to the bitstream. // encode the output symbol provided and write it to the bitstream.
...@@ -301,7 +300,7 @@ func (s *Scratch) writeCount() error { ...@@ -301,7 +300,7 @@ func (s *Scratch) writeCount() error {
out[outP+1] = byte(bitStream >> 8) out[outP+1] = byte(bitStream >> 8)
outP += (bitCount + 7) / 8 outP += (bitCount + 7) / 8
if uint16(charnum) > s.symbolLen { if charnum > s.symbolLen {
return errors.New("internal error: charnum > s.symbolLen") return errors.New("internal error: charnum > s.symbolLen")
} }
s.Out = out[:outP] s.Out = out[:outP]
...@@ -331,7 +330,7 @@ type cTable struct { ...@@ -331,7 +330,7 @@ type cTable struct {
func (s *Scratch) allocCtable() { func (s *Scratch) allocCtable() {
tableSize := 1 << s.actualTableLog tableSize := 1 << s.actualTableLog
// get tableSymbol that is big enough. // get tableSymbol that is big enough.
if cap(s.ct.tableSymbol) < int(tableSize) { if cap(s.ct.tableSymbol) < tableSize {
s.ct.tableSymbol = make([]byte, tableSize) s.ct.tableSymbol = make([]byte, tableSize)
} }
s.ct.tableSymbol = s.ct.tableSymbol[:tableSize] s.ct.tableSymbol = s.ct.tableSymbol[:tableSize]
...@@ -565,8 +564,8 @@ func (s *Scratch) normalizeCount2() error { ...@@ -565,8 +564,8 @@ func (s *Scratch) normalizeCount2() error {
distributed uint32 distributed uint32
total = uint32(s.br.remain()) total = uint32(s.br.remain())
tableLog = s.actualTableLog tableLog = s.actualTableLog
lowThreshold = uint32(total >> tableLog) lowThreshold = total >> tableLog
lowOne = uint32((total * 3) >> (tableLog + 1)) lowOne = (total * 3) >> (tableLog + 1)
) )
for i, cnt := range s.count[:s.symbolLen] { for i, cnt := range s.count[:s.symbolLen] {
if cnt == 0 { if cnt == 0 {
...@@ -591,7 +590,7 @@ func (s *Scratch) normalizeCount2() error { ...@@ -591,7 +590,7 @@ func (s *Scratch) normalizeCount2() error {
if (total / toDistribute) > lowOne { if (total / toDistribute) > lowOne {
// risk of rounding to zero // risk of rounding to zero
lowOne = uint32((total * 3) / (toDistribute * 2)) lowOne = (total * 3) / (toDistribute * 2)
for i, cnt := range s.count[:s.symbolLen] { for i, cnt := range s.count[:s.symbolLen] {
if (s.norm[i] == notYetAssigned) && (cnt <= lowOne) { if (s.norm[i] == notYetAssigned) && (cnt <= lowOne) {
s.norm[i] = 1 s.norm[i] = 1
......
...@@ -172,7 +172,7 @@ type decSymbol struct { ...@@ -172,7 +172,7 @@ type decSymbol struct {
// allocDtable will allocate decoding tables if they are not big enough. // allocDtable will allocate decoding tables if they are not big enough.
func (s *Scratch) allocDtable() { func (s *Scratch) allocDtable() {
tableSize := 1 << s.actualTableLog tableSize := 1 << s.actualTableLog
if cap(s.decTable) < int(tableSize) { if cap(s.decTable) < tableSize {
s.decTable = make([]decSymbol, tableSize) s.decTable = make([]decSymbol, tableSize)
} }
s.decTable = s.decTable[:tableSize] s.decTable = s.decTable[:tableSize]
...@@ -340,7 +340,7 @@ type decoder struct { ...@@ -340,7 +340,7 @@ type decoder struct {
func (d *decoder) init(in *bitReader, dt []decSymbol, tableLog uint8) { func (d *decoder) init(in *bitReader, dt []decSymbol, tableLog uint8) {
d.dt = dt d.dt = dt
d.br = in d.br = in
d.state = uint16(in.getBits(tableLog)) d.state = in.getBits(tableLog)
} }
// next returns the next symbol and sets the next state. // next returns the next symbol and sets the next state.
......
...@@ -403,7 +403,7 @@ func (s *Scratch) buildCTable() error { ...@@ -403,7 +403,7 @@ func (s *Scratch) buildCTable() error {
var startNode = int16(s.symbolLen) var startNode = int16(s.symbolLen)
nonNullRank := s.symbolLen - 1 nonNullRank := s.symbolLen - 1
nodeNb := int16(startNode) nodeNb := startNode
huffNode := s.nodes[1 : huffNodesLen+1] huffNode := s.nodes[1 : huffNodesLen+1]
// This overlays the slice above, but allows "-1" index lookups. // This overlays the slice above, but allows "-1" index lookups.
...@@ -536,7 +536,6 @@ func (s *Scratch) huffSort() { ...@@ -536,7 +536,6 @@ func (s *Scratch) huffSort() {
} }
nodes[pos&huffNodesMask] = nodeElt{count: c, symbol: byte(n)} nodes[pos&huffNodesMask] = nodeElt{count: c, symbol: byte(n)}
} }
return
} }
func (s *Scratch) setMaxHeight(lastNonNull int) uint8 { func (s *Scratch) setMaxHeight(lastNonNull int) uint8 {
...@@ -580,7 +579,7 @@ func (s *Scratch) setMaxHeight(lastNonNull int) uint8 { ...@@ -580,7 +579,7 @@ func (s *Scratch) setMaxHeight(lastNonNull int) uint8 {
// Get pos of last (smallest) symbol per rank // Get pos of last (smallest) symbol per rank
{ {
currentNbBits := uint8(maxNbBits) currentNbBits := maxNbBits
for pos := int(n); pos >= 0; pos-- { for pos := int(n); pos >= 0; pos-- {
if huffNode[pos].nbBits >= currentNbBits { if huffNode[pos].nbBits >= currentNbBits {
continue continue
......
del old.txt
go test -bench=. >>old.txt && go test -bench=. >>old.txt && go test -bench=. >>old.txt && benchstat -delta-test=ttest old.txt new.txt
...@@ -152,8 +152,8 @@ This package: ...@@ -152,8 +152,8 @@ This package:
file out level insize outsize millis mb/s file out level insize outsize millis mb/s
silesia.tar zskp 1 211947520 73101992 643 313.87 silesia.tar zskp 1 211947520 73101992 643 313.87
silesia.tar zskp 2 211947520 67504318 969 208.38 silesia.tar zskp 2 211947520 67504318 969 208.38
silesia.tar zskp 3 211947520 65177448 1899 106.44 silesia.tar zskp 3 211947520 64595893 2007 100.68
silesia.tar zskp 4 211947520 61381950 8115 24.91 silesia.tar zskp 4 211947520 60995370 7691 26.28
cgo zstd: cgo zstd:
silesia.tar zstd 1 211947520 73605392 543 371.56 silesia.tar zstd 1 211947520 73605392 543 371.56
...@@ -171,8 +171,8 @@ https://files.klauspost.com/compress/gob-stream.7z ...@@ -171,8 +171,8 @@ https://files.klauspost.com/compress/gob-stream.7z
file out level insize outsize millis mb/s file out level insize outsize millis mb/s
gob-stream zskp 1 1911399616 235022249 3088 590.30 gob-stream zskp 1 1911399616 235022249 3088 590.30
gob-stream zskp 2 1911399616 205669791 3786 481.34 gob-stream zskp 2 1911399616 205669791 3786 481.34
gob-stream zskp 3 1911399616 185792019 9324 195.48 gob-stream zskp 3 1911399616 175034659 9636 189.17
gob-stream zskp 4 1911399616 171537212 32113 56.76 gob-stream zskp 4 1911399616 167273881 29337 62.13
gob-stream zstd 1 1911399616 249810424 2637 691.26 gob-stream zstd 1 1911399616 249810424 2637 691.26
gob-stream zstd 3 1911399616 208192146 3490 522.31 gob-stream zstd 3 1911399616 208192146 3490 522.31
gob-stream zstd 6 1911399616 193632038 6687 272.56 gob-stream zstd 6 1911399616 193632038 6687 272.56
...@@ -187,8 +187,8 @@ http://mattmahoney.net/dc/textdata.html ...@@ -187,8 +187,8 @@ http://mattmahoney.net/dc/textdata.html
file out level insize outsize millis mb/s file out level insize outsize millis mb/s
enwik9 zskp 1 1000000000 343848582 3609 264.18 enwik9 zskp 1 1000000000 343848582 3609 264.18
enwik9 zskp 2 1000000000 317276632 5746 165.97 enwik9 zskp 2 1000000000 317276632 5746 165.97
enwik9 zskp 3 1000000000 294540704 11725 81.34 enwik9 zskp 3 1000000000 292243069 12162 78.41
enwik9 zskp 4 1000000000 276609671 44029 21.66 enwik9 zskp 4 1000000000 275241169 36430 26.18
enwik9 zstd 1 1000000000 358072021 3110 306.65 enwik9 zstd 1 1000000000 358072021 3110 306.65
enwik9 zstd 3 1000000000 313734672 4784 199.35 enwik9 zstd 3 1000000000 313734672 4784 199.35
enwik9 zstd 6 1000000000 295138875 10290 92.68 enwik9 zstd 6 1000000000 295138875 10290 92.68
...@@ -202,8 +202,8 @@ https://files.klauspost.com/compress/github-june-2days-2019.json.zst ...@@ -202,8 +202,8 @@ https://files.klauspost.com/compress/github-june-2days-2019.json.zst
file out level insize outsize millis mb/s file out level insize outsize millis mb/s
github-june-2days-2019.json zskp 1 6273951764 699045015 10620 563.40 github-june-2days-2019.json zskp 1 6273951764 699045015 10620 563.40
github-june-2days-2019.json zskp 2 6273951764 617881763 11687 511.96 github-june-2days-2019.json zskp 2 6273951764 617881763 11687 511.96
github-june-2days-2019.json zskp 3 6273951764 537511906 29252 204.54 github-june-2days-2019.json zskp 3 6273951764 524340691 34043 175.75
github-june-2days-2019.json zskp 4 6273951764 512796117 97791 61.18 github-june-2days-2019.json zskp 4 6273951764 503314661 93811 63.78
github-june-2days-2019.json zstd 1 6273951764 766284037 8450 708.00 github-june-2days-2019.json zstd 1 6273951764 766284037 8450 708.00
github-june-2days-2019.json zstd 3 6273951764 661889476 10927 547.57 github-june-2days-2019.json zstd 3 6273951764 661889476 10927 547.57
github-june-2days-2019.json zstd 6 6273951764 642756859 22996 260.18 github-june-2days-2019.json zstd 6 6273951764 642756859 22996 260.18
...@@ -217,8 +217,8 @@ https://files.klauspost.com/compress/rawstudio-mint14.7z ...@@ -217,8 +217,8 @@ https://files.klauspost.com/compress/rawstudio-mint14.7z
file out level insize outsize millis mb/s file out level insize outsize millis mb/s
rawstudio-mint14.tar zskp 1 8558382592 3667489370 20210 403.84 rawstudio-mint14.tar zskp 1 8558382592 3667489370 20210 403.84
rawstudio-mint14.tar zskp 2 8558382592 3364592300 31873 256.07 rawstudio-mint14.tar zskp 2 8558382592 3364592300 31873 256.07
rawstudio-mint14.tar zskp 3 8558382592 3224594213 71751 113.75 rawstudio-mint14.tar zskp 3 8558382592 3158085214 77675 105.08
rawstudio-mint14.tar zskp 4 8558382592 3027332295 486243 16.79 rawstudio-mint14.tar zskp 4 8558382592 3020370044 404956 20.16
rawstudio-mint14.tar zstd 1 8558382592 3609250104 17136 476.27 rawstudio-mint14.tar zstd 1 8558382592 3609250104 17136 476.27
rawstudio-mint14.tar zstd 3 8558382592 3341679997 29262 278.92 rawstudio-mint14.tar zstd 3 8558382592 3341679997 29262 278.92
rawstudio-mint14.tar zstd 6 8558382592 3235846406 77904 104.77 rawstudio-mint14.tar zstd 6 8558382592 3235846406 77904 104.77
...@@ -232,8 +232,8 @@ https://files.klauspost.com/compress/nyc-taxi-data-10M.csv.zst ...@@ -232,8 +232,8 @@ https://files.klauspost.com/compress/nyc-taxi-data-10M.csv.zst
file out level insize outsize millis mb/s file out level insize outsize millis mb/s
nyc-taxi-data-10M.csv zskp 1 3325605752 641339945 8925 355.35 nyc-taxi-data-10M.csv zskp 1 3325605752 641339945 8925 355.35
nyc-taxi-data-10M.csv zskp 2 3325605752 591748091 11268 281.44 nyc-taxi-data-10M.csv zskp 2 3325605752 591748091 11268 281.44
nyc-taxi-data-10M.csv zskp 3 3325605752 538490114 19880 159.53 nyc-taxi-data-10M.csv zskp 3 3325605752 530289687 25239 125.66
nyc-taxi-data-10M.csv zskp 4 3325605752 495986829 89368 35.49 nyc-taxi-data-10M.csv zskp 4 3325605752 490907191 65939 48.10
nyc-taxi-data-10M.csv zstd 1 3325605752 687399637 8233 385.18 nyc-taxi-data-10M.csv zstd 1 3325605752 687399637 8233 385.18
nyc-taxi-data-10M.csv zstd 3 3325605752 598514411 10065 315.07 nyc-taxi-data-10M.csv zstd 3 3325605752 598514411 10065 315.07
nyc-taxi-data-10M.csv zstd 6 3325605752 570522953 20038 158.27 nyc-taxi-data-10M.csv zstd 6 3325605752 570522953 20038 158.27
......
...@@ -22,28 +22,44 @@ type blockEnc struct { ...@@ -22,28 +22,44 @@ type blockEnc struct {
dictLitEnc *huff0.Scratch dictLitEnc *huff0.Scratch
wr bitWriter wr bitWriter
extraLits int extraLits int
last bool
output []byte output []byte
recentOffsets [3]uint32 recentOffsets [3]uint32
prevRecentOffsets [3]uint32 prevRecentOffsets [3]uint32
last bool
lowMem bool
} }
// init should be used once the block has been created. // init should be used once the block has been created.
// If called more than once, the effect is the same as calling reset. // If called more than once, the effect is the same as calling reset.
func (b *blockEnc) init() { func (b *blockEnc) init() {
if cap(b.literals) < maxCompressedLiteralSize { if b.lowMem {
b.literals = make([]byte, 0, maxCompressedLiteralSize) // 1K literals
} if cap(b.literals) < 1<<10 {
const defSeqs = 200 b.literals = make([]byte, 0, 1<<10)
b.literals = b.literals[:0] }
if cap(b.sequences) < defSeqs { const defSeqs = 20
b.sequences = make([]seq, 0, defSeqs) if cap(b.sequences) < defSeqs {
} b.sequences = make([]seq, 0, defSeqs)
if cap(b.output) < maxCompressedBlockSize { }
b.output = make([]byte, 0, maxCompressedBlockSize) // 1K
if cap(b.output) < 1<<10 {
b.output = make([]byte, 0, 1<<10)
}
} else {
if cap(b.literals) < maxCompressedBlockSize {
b.literals = make([]byte, 0, maxCompressedBlockSize)
}
const defSeqs = 200
if cap(b.sequences) < defSeqs {
b.sequences = make([]seq, 0, defSeqs)
}
if cap(b.output) < maxCompressedBlockSize {
b.output = make([]byte, 0, maxCompressedBlockSize)
}
} }
if b.coders.mlEnc == nil { if b.coders.mlEnc == nil {
b.coders.mlEnc = &fseEncoder{} b.coders.mlEnc = &fseEncoder{}
b.coders.mlPrev = &fseEncoder{} b.coders.mlPrev = &fseEncoder{}
...@@ -370,9 +386,9 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error { ...@@ -370,9 +386,9 @@ func (b *blockEnc) encodeLits(lits []byte, raw bool) error {
b.output = bh.appendTo(b.output) b.output = bh.appendTo(b.output)
b.output = append(b.output, lits[0]) b.output = append(b.output, lits[0])
return nil return nil
case nil:
default: default:
return err return err
case nil:
} }
// Compressed... // Compressed...
// Now, allow reuse // Now, allow reuse
...@@ -512,11 +528,6 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -512,11 +528,6 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
if debug { if debug {
println("Adding literals RLE") println("Adding literals RLE")
} }
default:
if debug {
println("Adding literals ERROR:", err)
}
return err
case nil: case nil:
// Compressed litLen... // Compressed litLen...
if reUsed { if reUsed {
...@@ -547,6 +558,11 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error { ...@@ -547,6 +558,11 @@ func (b *blockEnc) encode(org []byte, raw, rawAllLits bool) error {
if debug { if debug {
println("Adding literals compressed") println("Adding literals compressed")
} }
default:
if debug {
println("Adding literals ERROR:", err)
}
return err
} }
// Sequence compression // Sequence compression
......
...@@ -93,7 +93,7 @@ func (h *Header) Decode(in []byte) error { ...@@ -93,7 +93,7 @@ func (h *Header) Decode(in []byte) error {
h.HasCheckSum = fhd&(1<<2) != 0 h.HasCheckSum = fhd&(1<<2) != 0
if fhd&(1<<3) != 0 { if fhd&(1<<3) != 0 {
return errors.New("Reserved bit set on frame header") return errors.New("reserved bit set on frame header")
} }
// Read Window_Descriptor // Read Window_Descriptor
...@@ -174,7 +174,7 @@ func (h *Header) Decode(in []byte) error { ...@@ -174,7 +174,7 @@ func (h *Header) Decode(in []byte) error {
if len(in) < 3 { if len(in) < 3 {
return nil return nil
} }
tmp, in := in[:3], in[3:] tmp := in[:3]
bh := uint32(tmp[0]) | (uint32(tmp[1]) << 8) | (uint32(tmp[2]) << 16) bh := uint32(tmp[0]) | (uint32(tmp[1]) << 8) | (uint32(tmp[2]) << 16)
h.FirstBlock.Last = bh&1 != 0 h.FirstBlock.Last = bh&1 != 0
blockType := blockType((bh >> 1) & 3) blockType := blockType((bh >> 1) & 3)
......
...@@ -179,8 +179,7 @@ func (d *Decoder) Reset(r io.Reader) error { ...@@ -179,8 +179,7 @@ func (d *Decoder) Reset(r io.Reader) error {
// If bytes buffer and < 1MB, do sync decoding anyway. // If bytes buffer and < 1MB, do sync decoding anyway.
if bb, ok := r.(byter); ok && bb.Len() < 1<<20 { if bb, ok := r.(byter); ok && bb.Len() < 1<<20 {
var bb2 byter bb2 := bb
bb2 = bb
if debug { if debug {
println("*bytes.Buffer detected, doing sync decode, len:", bb.Len()) println("*bytes.Buffer detected, doing sync decode, len:", bb.Len())
} }
...@@ -237,20 +236,17 @@ func (d *Decoder) drainOutput() { ...@@ -237,20 +236,17 @@ func (d *Decoder) drainOutput() {
println("current already flushed") println("current already flushed")
return return
} }
for { for v := range d.current.output {
select { if v.d != nil {
case v := <-d.current.output: if debug {
if v.d != nil { printf("re-adding decoder %p", v.d)
if debug {
printf("re-adding decoder %p", v.d)
}
d.decoders <- v.d
}
if v.err == errEndOfStream {
println("current flushed")
d.current.flushed = true
return
} }
d.decoders <- v.d
}
if v.err == errEndOfStream {
println("current flushed")
d.current.flushed = true
return
} }
} }
} }
......
...@@ -6,7 +6,6 @@ package zstd ...@@ -6,7 +6,6 @@ package zstd
import ( import (
"errors" "errors"
"fmt"
"runtime" "runtime"
) )
...@@ -43,7 +42,7 @@ func WithDecoderLowmem(b bool) DOption { ...@@ -43,7 +42,7 @@ func WithDecoderLowmem(b bool) DOption {
func WithDecoderConcurrency(n int) DOption { func WithDecoderConcurrency(n int) DOption {
return func(o *decoderOptions) error { return func(o *decoderOptions) error {
if n <= 0 { if n <= 0 {
return fmt.Errorf("Concurrency must be at least 1") return errors.New("concurrency must be at least 1")
} }
o.concurrent = n o.concurrent = n
return nil return nil
...@@ -61,7 +60,7 @@ func WithDecoderMaxMemory(n uint64) DOption { ...@@ -61,7 +60,7 @@ func WithDecoderMaxMemory(n uint64) DOption {
return errors.New("WithDecoderMaxMemory must be at least 1") return errors.New("WithDecoderMaxMemory must be at least 1")
} }
if n > 1<<63 { if n > 1<<63 {
return fmt.Errorf("WithDecoderMaxmemory must be less than 1 << 63") return errors.New("WithDecoderMaxmemory must be less than 1 << 63")
} }
o.maxDecodedSize = n o.maxDecodedSize = n
return nil return nil
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment