Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
Antonio Murdaca 2016-01-21 12:26:59 +01:00
parent f6bb924053
commit 7c8a3fdbe0
8 changed files with 343 additions and 10 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
skopeo
skopeo.1

8
Godeps/Godeps.json generated
View File

@ -1,6 +1,9 @@
{
"ImportPath": "github.com/runcom/skopeo",
"GoVersion": "go1.5.3",
"Packages": [
"."
],
"Deps": [
{
"ImportPath": "github.com/Sirupsen/logrus",
@ -22,6 +25,11 @@
"Comment": "v1.4.1-9391-g5537a92",
"Rev": "5537a92e450ea56e2002f83ff50bb70fdb2cc25e"
},
{
"ImportPath": "github.com/docker/docker/cliconfig",
"Comment": "v1.4.1-9391-g5537a92",
"Rev": "5537a92e450ea56e2002f83ff50bb70fdb2cc25e"
},
{
"ImportPath": "github.com/docker/docker/daemon/graphdriver",
"Comment": "v1.4.1-9391-g5537a92",

View File

@ -0,0 +1,277 @@
package cliconfig
import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/pkg/homedir"
"github.com/docker/engine-api/types"
)
const (
// ConfigFileName is the name of config file
ConfigFileName = "config.json"
oldConfigfile = ".dockercfg"
// This constant is only used for really old config files when the
// URL wasn't saved as part of the config file and it was just
// assumed to be this value.
defaultIndexserver = "https://index.docker.io/v1/"
)
var (
configDir = os.Getenv("DOCKER_CONFIG")
)
func init() {
if configDir == "" {
configDir = filepath.Join(homedir.Get(), ".docker")
}
}
// ConfigDir returns the directory the configuration file is stored in
func ConfigDir() string {
return configDir
}
// SetConfigDir sets the directory the configuration file is stored in
func SetConfigDir(dir string) {
configDir = dir
}
// ConfigFile ~/.docker/config.json file info
type ConfigFile struct {
AuthConfigs map[string]types.AuthConfig `json:"auths"`
HTTPHeaders map[string]string `json:"HttpHeaders,omitempty"`
PsFormat string `json:"psFormat,omitempty"`
ImagesFormat string `json:"imagesFormat,omitempty"`
DetachKeys string `json:"detachKeys,omitempty"`
filename string // Note: not serialized - for internal use only
}
// NewConfigFile initializes an empty configuration file for the given filename 'fn'
func NewConfigFile(fn string) *ConfigFile {
return &ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
HTTPHeaders: make(map[string]string),
filename: fn,
}
}
// LegacyLoadFromReader reads the non-nested configuration data given and sets up the
// auth config information with given directory and populates the receiver object
func (configFile *ConfigFile) LegacyLoadFromReader(configData io.Reader) error {
b, err := ioutil.ReadAll(configData)
if err != nil {
return err
}
if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil {
arr := strings.Split(string(b), "\n")
if len(arr) < 2 {
return fmt.Errorf("The Auth config file is empty")
}
authConfig := types.AuthConfig{}
origAuth := strings.Split(arr[0], " = ")
if len(origAuth) != 2 {
return fmt.Errorf("Invalid Auth config file")
}
authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1])
if err != nil {
return err
}
origEmail := strings.Split(arr[1], " = ")
if len(origEmail) != 2 {
return fmt.Errorf("Invalid Auth config file")
}
authConfig.Email = origEmail[1]
authConfig.ServerAddress = defaultIndexserver
configFile.AuthConfigs[defaultIndexserver] = authConfig
} else {
for k, authConfig := range configFile.AuthConfigs {
authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
if err != nil {
return err
}
authConfig.Auth = ""
authConfig.ServerAddress = k
configFile.AuthConfigs[k] = authConfig
}
}
return nil
}
// LoadFromReader reads the configuration data given and sets up the auth config
// information with given directory and populates the receiver object
func (configFile *ConfigFile) LoadFromReader(configData io.Reader) error {
if err := json.NewDecoder(configData).Decode(&configFile); err != nil {
return err
}
var err error
for addr, ac := range configFile.AuthConfigs {
ac.Username, ac.Password, err = decodeAuth(ac.Auth)
if err != nil {
return err
}
ac.Auth = ""
ac.ServerAddress = addr
configFile.AuthConfigs[addr] = ac
}
return nil
}
// LegacyLoadFromReader is a convenience function that creates a ConfigFile object from
// a non-nested reader
func LegacyLoadFromReader(configData io.Reader) (*ConfigFile, error) {
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, error) {
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 return values.
// FIXME: use the internal golang config parser
func Load(configDir string) (*ConfigFile, error) {
if configDir == "" {
configDir = ConfigDir()
}
configFile := ConfigFile{
AuthConfigs: make(map[string]types.AuthConfig),
filename: filepath.Join(configDir, ConfigFileName),
}
// Try happy path first - latest config file
if _, err := os.Stat(configFile.filename); err == nil {
file, err := os.Open(configFile.filename)
if err != nil {
return &configFile, fmt.Errorf("%s - %v", configFile.filename, err)
}
defer file.Close()
err = configFile.LoadFromReader(file)
if err != nil {
err = fmt.Errorf("%s - %v", configFile.filename, err)
}
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, fmt.Errorf("%s - %v", configFile.filename, err)
}
// Can't find latest config file so check for the old one
confFile := filepath.Join(homedir.Get(), 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, fmt.Errorf("%s - %v", confFile, err)
}
defer file.Close()
err = configFile.LegacyLoadFromReader(file)
if err != nil {
return &configFile, fmt.Errorf("%s - %v", confFile, err)
}
if configFile.HTTPHeaders == nil {
configFile.HTTPHeaders = map[string]string{}
}
return &configFile, nil
}
// SaveToWriter encodes and writes out all the authorization information to
// the given writer
func (configFile *ConfigFile) SaveToWriter(writer io.Writer) error {
// Encode sensitive data into a new/temp struct
tmpAuthConfigs := make(map[string]types.AuthConfig, len(configFile.AuthConfigs))
for k, authConfig := range configFile.AuthConfigs {
authCopy := authConfig
// encode and save the authstring, while blanking out the original fields
authCopy.Auth = encodeAuth(&authCopy)
authCopy.Username = ""
authCopy.Password = ""
authCopy.ServerAddress = ""
tmpAuthConfigs[k] = authCopy
}
saveAuthConfigs := configFile.AuthConfigs
configFile.AuthConfigs = tmpAuthConfigs
defer func() { configFile.AuthConfigs = saveAuthConfigs }()
data, err := json.MarshalIndent(configFile, "", "\t")
if err != nil {
return err
}
_, err = writer.Write(data)
return err
}
// Save encodes and writes out all the authorization information
func (configFile *ConfigFile) Save() error {
if configFile.Filename() == "" {
return fmt.Errorf("Can't save config with empty filename")
}
if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil {
return err
}
f, err := os.OpenFile(configFile.filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
return configFile.SaveToWriter(f)
}
// Filename returns the name of the configuration file
func (configFile *ConfigFile) Filename() string {
return configFile.filename
}
// encodeAuth creates a base64 encoded string to containing authorization information
func encodeAuth(authConfig *types.AuthConfig) string {
authStr := authConfig.Username + ":" + authConfig.Password
msg := []byte(authStr)
encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg)))
base64.StdEncoding.Encode(encoded, msg)
return string(encoded)
}
// decodeAuth decodes a base64 encoded string and returns username and password
func decodeAuth(authStr string) (string, string, error) {
decLen := base64.StdEncoding.DecodedLen(len(authStr))
decoded := make([]byte, decLen)
authByte := []byte(authStr)
n, err := base64.StdEncoding.Decode(decoded, authByte)
if err != nil {
return "", "", err
}
if n > decLen {
return "", "", fmt.Errorf("Something went wrong decoding auth config")
}
arr := strings.SplitN(string(decoded), ":", 2)
if len(arr) != 2 {
return "", "", fmt.Errorf("Invalid auth configuration file")
}
password := strings.Trim(arr[1], "\x00")
return arr[0], password, nil
}

View File

@ -1,13 +1,17 @@
export GOPATH:=$(CURDIR)/Godeps/_workspace:$(GOPATH)
BINDIR=${DESTDIR}/usr/local/bin/
BINDIR=${DESTDIR}/usr/bin/
MANDIR=${DESTDIR}/usr/share/man
all:
go build -o skopeo .
go-md2man -in man/skopeo.1.md -out skopeo.1
install:
install -d -m 0755 ${BINDIR}
install -m 755 skopeo ${BINDIR}
install -m 644 skopeo.1 ${MANDIR}/man1/
clean:
rm -f skopeo
rm -f skopeo.1

View File

@ -92,17 +92,9 @@ $ make
```
Installing
-
If you built from the _Building_ step, just do:
```sh
$ sudo make install
```
You can also grab a binary, built for _linux x86_64_, from the releases page, or:
```sh
$ export RELEASE=0.0.1-alpha
$ wget https://github.com/runcom/skopeo/releases/download/v$RELEASE/skopeo
$ chmod +x skopeo
$ sudo mv skopeo /usr/local/bin/skopeo
```
TODO
-
- show repo tags via flag or when reference isn't tagged or digested

View File

@ -11,7 +11,7 @@ import (
)
const (
version = "0.0.1-alpha"
version = "0.1.0"
usage = "inspect images on a registry"
)

14
man/skopeo.1.md Normal file
View File

@ -0,0 +1,14 @@
% SKOPEO(1)
% Antonio Murdaca
% JANUARY 2016
# NAME
skopeo - Inspect Docker images and repositories on registries
# SYNOPSIS
# DESCRIPTION
# ARGUMENTS
# AUTHORS
Antonio Murdaca <runcom@redhat.com>

37
skopeo.spec Normal file
View File

@ -0,0 +1,37 @@
Name: skopeo
Version: 0.1.0
Release: 1%{?dist}
Summary: Inspect Docker images and repositories on registries
License: MIT
URL: https://github.com/runcom/skopeo
Source: https://github.com/runcom/skopeo/archive/v%{version}.tar.gz
BuildRequires: golang
BuildRequires: golang-github-cpuguy83-go-md2man
%global debug_package %{nil}
%description
Get information about a Docker image or repository without pulling it
%prep
%setup -q -n %{name}-%{version}
%build
mkdir -p src/github.com/runcom
ln -s ../../../ src/github.com/runcom/skopeo
export GOPATH=$(pwd):%{gopath}
make %{?_smp_mflags}
%install
mkdir -p $RPM_BUILD_ROOT/%{_mandir}/man1
make DESTDIR=%{buildroot} install
%files
%{_bindir}/skopeo
%{_mandir}/man1/skopeo.1*
%doc README.md LICENSE
%changelog
* Thu Jan 21 2016 Antonio Murdaca <runcom@redhat.com> - 0.1.0
- v0.1.0