Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Brian Goff
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.
+8
View File
@@ -0,0 +1,8 @@
### github.com/cpuguy83/dockercfg
Go library to load docker CLI configs, auths, etc. with minimal deps.
So far the only deps are on the stdlib.
### Usage
See the [godoc](https://godoc.org/github.com/cpuguy83/dockercfg) for API details.
I'm currently using this in [zapp](https://github.com/cpuguy83/zapp/blob/d25c43d4cd7ccf29fba184aafbc720a753e1a15d/main.go#L58-L83) to handle registry auth instead of always asking the user to enter it.
+215
View File
@@ -0,0 +1,215 @@
package dockercfg
import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io/fs"
"os/exec"
"runtime"
"strings"
)
// This is used by the docker CLI in cases where an oauth identity token is used.
// In that case the username is stored literally as `<token>`
// When fetching the credentials we check for this value to determine if.
const tokenUsername = "<token>"
// GetRegistryCredentials gets registry credentials for the passed in registry host.
//
// This will use [LoadDefaultConfig] to read registry auth details from the config.
// If the config doesn't exist, it will attempt to load registry credentials using the default credential helper for the platform.
func GetRegistryCredentials(hostname string) (string, string, error) {
cfg, err := LoadDefaultConfig()
if err != nil {
if !errors.Is(err, fs.ErrNotExist) {
return "", "", fmt.Errorf("load default config: %w", err)
}
return GetCredentialsFromHelper("", hostname)
}
return cfg.GetRegistryCredentials(hostname)
}
// ResolveRegistryHost can be used to transform a docker registry host name into what is used for the docker config/cred helpers
//
// This is useful for using with containerd authorizers.
// Naturally this only transforms docker hub URLs.
func ResolveRegistryHost(host string) string {
switch host {
case "index.docker.io", "docker.io", "https://index.docker.io/v1/", "registry-1.docker.io":
return "https://index.docker.io/v1/"
}
return host
}
// GetRegistryCredentials gets credentials, if any, for the provided hostname.
//
// Hostnames should already be resolved using [ResolveRegistryHost].
//
// If the returned username string is empty, the password is an identity token.
func (c *Config) GetRegistryCredentials(hostname string) (string, string, error) {
h, ok := c.CredentialHelpers[hostname]
if ok {
return GetCredentialsFromHelper(h, hostname)
}
if c.CredentialsStore != "" {
username, password, err := GetCredentialsFromHelper(c.CredentialsStore, hostname)
if err != nil {
return "", "", fmt.Errorf("get credentials from store: %w", err)
}
if username != "" || password != "" {
return username, password, nil
}
}
auth, ok := c.AuthConfigs[hostname]
if !ok {
return GetCredentialsFromHelper("", hostname)
}
if auth.IdentityToken != "" {
return "", auth.IdentityToken, nil
}
if auth.Username != "" && auth.Password != "" {
return auth.Username, auth.Password, nil
}
return DecodeBase64Auth(auth)
}
// DecodeBase64Auth decodes the legacy file-based auth storage from the docker CLI.
// It takes the "Auth" filed from AuthConfig and decodes that into a username and password.
//
// If "Auth" is empty, an empty user/pass will be returned, but not an error.
func DecodeBase64Auth(auth AuthConfig) (string, string, error) {
if auth.Auth == "" {
return "", "", nil
}
decLen := base64.StdEncoding.DecodedLen(len(auth.Auth))
decoded := make([]byte, decLen)
n, err := base64.StdEncoding.Decode(decoded, []byte(auth.Auth))
if err != nil {
return "", "", fmt.Errorf("decode auth: %w", err)
}
decoded = decoded[:n]
const sep = ":"
user, pass, found := strings.Cut(string(decoded), sep)
if !found {
return "", "", fmt.Errorf("invalid auth: missing %q separator", sep)
}
return user, pass, nil
}
// Errors from credential helpers.
var (
ErrCredentialsNotFound = errors.New("credentials not found in native keychain")
ErrCredentialsMissingServerURL = errors.New("no credentials server URL")
)
//nolint:gochecknoglobals // These are used to mock exec in tests.
var (
// execLookPath is a variable that can be used to mock exec.LookPath in tests.
execLookPath = exec.LookPath
// execCommand is a variable that can be used to mock exec.Command in tests.
execCommand = exec.Command
)
// GetCredentialsFromHelper attempts to lookup credentials from the passed in docker credential helper.
//
// The credential helper should just be the suffix name (no "docker-credential-").
// If the passed in helper program is empty this will look up the default helper for the platform.
//
// If the credentials are not found, no error is returned, only empty credentials.
//
// Hostnames should already be resolved using [ResolveRegistryHost]
//
// If the username string is empty, the password string is an identity token.
func GetCredentialsFromHelper(helper, hostname string) (string, string, error) {
if helper == "" {
helper, helperErr := getCredentialHelper()
if helperErr != nil {
return "", "", fmt.Errorf("get credential helper: %w", helperErr)
}
if helper == "" {
return "", "", nil
}
}
helper = "docker-credential-" + helper
p, err := execLookPath(helper)
if err != nil {
if !errors.Is(err, exec.ErrNotFound) {
return "", "", fmt.Errorf("look up %q: %w", helper, err)
}
return "", "", nil
}
var outBuf, errBuf bytes.Buffer
cmd := execCommand(p, "get")
cmd.Stdin = strings.NewReader(hostname)
cmd.Stdout = &outBuf
cmd.Stderr = &errBuf
if err = cmd.Run(); err != nil {
out := strings.TrimSpace(outBuf.String())
switch out {
case ErrCredentialsNotFound.Error():
return "", "", nil
case ErrCredentialsMissingServerURL.Error():
return "", "", ErrCredentialsMissingServerURL
default:
return "", "", fmt.Errorf("execute %q stdout: %q stderr: %q: %w",
helper, out, strings.TrimSpace(errBuf.String()), err,
)
}
}
var creds struct {
Username string `json:"Username"`
Secret string `json:"Secret"`
}
if err = json.Unmarshal(outBuf.Bytes(), &creds); err != nil {
return "", "", fmt.Errorf("unmarshal credentials from: %q: %w", helper, err)
}
// When tokenUsername is used, the output is an identity token and the username is garbage.
if creds.Username == tokenUsername {
creds.Username = ""
}
return creds.Username, creds.Secret, nil
}
// getCredentialHelper gets the default credential helper name for the current platform.
func getCredentialHelper() (string, error) {
switch runtime.GOOS {
case "linux":
if _, err := exec.LookPath("pass"); err != nil {
if errors.Is(err, exec.ErrNotFound) {
return "secretservice", nil
}
return "", fmt.Errorf(`look up "pass": %w`, err)
}
return "pass", nil
case "darwin":
return "osxkeychain", nil
case "windows":
return "wincred", nil
default:
return "", nil
}
}
+65
View File
@@ -0,0 +1,65 @@
package dockercfg
// Config represents the on disk format of the docker CLI's config file.
type Config struct {
AuthConfigs map[string]AuthConfig `json:"auths"`
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
PsFormat string `json:"psFormat,omitempty"`
ImagesFormat string `json:"imagesFormat,omitempty"`
NetworksFormat string `json:"networksFormat,omitempty"`
PluginsFormat string `json:"pluginsFormat,omitempty"`
VolumesFormat string `json:"volumesFormat,omitempty"`
StatsFormat string `json:"statsFormat,omitempty"`
DetachKeys string `json:"detachKeys,omitempty"`
CredentialsStore string `json:"credsStore,omitempty"`
CredentialHelpers map[string]string `json:"credHelpers,omitempty"`
Filename string `json:"-"` // Note: for internal use only.
ServiceInspectFormat string `json:"serviceInspectFormat,omitempty"`
ServicesFormat string `json:"servicesFormat,omitempty"`
TasksFormat string `json:"tasksFormat,omitempty"`
SecretFormat string `json:"secretFormat,omitempty"`
ConfigFormat string `json:"configFormat,omitempty"`
NodesFormat string `json:"nodesFormat,omitempty"`
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
StackOrchestrator string `json:"stackOrchestrator,omitempty"`
Kubernetes *KubernetesConfig `json:"kubernetes,omitempty"`
CurrentContext string `json:"currentContext,omitempty"`
CLIPluginsExtraDirs []string `json:"cliPluginsExtraDirs,omitempty"`
Aliases map[string]string `json:"aliases,omitempty"`
}
// ProxyConfig contains proxy configuration settings.
type ProxyConfig struct {
HTTPProxy string `json:"httpProxy,omitempty"`
HTTPSProxy string `json:"httpsProxy,omitempty"`
NoProxy string `json:"noProxy,omitempty"`
FTPProxy string `json:"ftpProxy,omitempty"`
}
// 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"`
}
// KubernetesConfig contains Kubernetes orchestrator settings.
type KubernetesConfig struct {
AllNamespaces string `json:"allNamespaces,omitempty"`
}
+55
View File
@@ -0,0 +1,55 @@
package dockercfg
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
)
// UserHomeConfigPath returns the path to the docker config in the current user's home dir.
func UserHomeConfigPath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("user home dir: %w", err)
}
return filepath.Join(home, ".docker", "config.json"), nil
}
// ConfigPath returns the path to the docker cli config.
//
// It will either use the DOCKER_CONFIG env var if set, or the value from [UserHomeConfigPath]
// DOCKER_CONFIG would be the dir path where `config.json` is stored, this returns the path to config.json.
func ConfigPath() (string, error) {
if p := os.Getenv("DOCKER_CONFIG"); p != "" {
return filepath.Join(p, "config.json"), nil
}
return UserHomeConfigPath()
}
// LoadDefaultConfig loads the docker cli config from the path returned from [ConfigPath].
func LoadDefaultConfig() (Config, error) {
var cfg Config
p, err := ConfigPath()
if err != nil {
return cfg, fmt.Errorf("config path: %w", err)
}
return cfg, FromFile(p, &cfg)
}
// FromFile loads config from the specified path into cfg.
func FromFile(configPath string, cfg *Config) error {
f, err := os.Open(configPath)
if err != nil {
return fmt.Errorf("open config: %w", err)
}
defer f.Close()
if err = json.NewDecoder(f).Decode(&cfg); err != nil {
return fmt.Errorf("decode config: %w", err)
}
return nil
}