mirror of
https://github.com/containers/skopeo.git
synced 2025-06-23 13:27:45 +00:00
v0.1.0
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
f6bb924053
commit
7c8a3fdbe0
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
skopeo
|
||||
skopeo.1
|
||||
|
8
Godeps/Godeps.json
generated
8
Godeps/Godeps.json
generated
@ -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",
|
||||
|
277
Godeps/_workspace/src/github.com/docker/docker/cliconfig/config.go
generated
vendored
Normal file
277
Godeps/_workspace/src/github.com/docker/docker/cliconfig/config.go
generated
vendored
Normal 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
|
||||
}
|
6
Makefile
6
Makefile
@ -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
|
||||
|
@ -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
|
||||
|
2
main.go
2
main.go
@ -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
14
man/skopeo.1.md
Normal 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
37
skopeo.spec
Normal 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
|
Loading…
Reference in New Issue
Block a user