mirror of
https://github.com/docker/docker-credential-helpers.git
synced 2026-06-28 15:21:29 +05:30
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5128fa1bad | |||
| fee9277aa7 | |||
| d80b179703 | |||
| c4fc9c07dd | |||
| 2c63e775b4 | |||
| 00703eb6db | |||
| df8c7a02f1 | |||
| 4b8917b1cf | |||
| 039c315f22 | |||
| 6bbb56ae3c | |||
| ec937eebe2 | |||
| fa24bb8912 | |||
| 76055c2deb |
@@ -4,6 +4,10 @@ This changelog tracks the releases of docker-credential-helpers.
|
||||
This project includes different binaries per platform.
|
||||
The platform released is identified after the tag name.
|
||||
|
||||
## v0.3.0 (Go client)
|
||||
|
||||
- Add Go client library to talk with the native programs.
|
||||
|
||||
## v0.2.0 (Mac OS X, Windows, Linux)
|
||||
|
||||
- Initial release of docker-credential-secretservice for Linux.
|
||||
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
# docker-credential-helpers maintainers file
|
||||
#
|
||||
# This file describes who runs the docker/docker-credential-helpers project and how.
|
||||
# This is a living document - if you see something out of date or missing, speak up!
|
||||
#
|
||||
# It is structured to be consumable by both humans and programs.
|
||||
# To extract its contents programmatically, use any TOML-compliant parser.
|
||||
#
|
||||
# This file is compiled into the MAINTAINERS file in docker/opensource.
|
||||
#
|
||||
[Org]
|
||||
[Org."Core maintainers"]
|
||||
people = [
|
||||
"aaronlehmann",
|
||||
"calavera",
|
||||
"coolljt0725",
|
||||
"cpuguy83",
|
||||
"crosbymichael",
|
||||
"dnephin",
|
||||
"dongluochen",
|
||||
"duglin",
|
||||
"estesp",
|
||||
"icecrime",
|
||||
"jhowardmsft",
|
||||
"lk4d4",
|
||||
"mavenugo",
|
||||
"mhbauer",
|
||||
"runcom",
|
||||
"stevvooe",
|
||||
"thajeztah",
|
||||
"tianon",
|
||||
"tibor",
|
||||
"tonistiigi",
|
||||
"unclejack",
|
||||
"vdemeester",
|
||||
"vieux"
|
||||
]
|
||||
|
||||
[people]
|
||||
|
||||
# A reference list of all people associated with the project.
|
||||
# All other sections should refer to people by their canonical key
|
||||
# in the people section.
|
||||
|
||||
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
|
||||
|
||||
[people.aaronlehmann]
|
||||
Name = "Aaron Lehmann"
|
||||
Email = "aaron.lehmann@docker.com"
|
||||
GitHub = "aaronlehmann"
|
||||
|
||||
[people.calavera]
|
||||
Name = "David Calavera"
|
||||
Email = "david.calavera@gmail.com"
|
||||
GitHub = "calavera"
|
||||
|
||||
[people.coolljt0725]
|
||||
Name = "Lei Jitang"
|
||||
Email = "leijitang@huawei.com"
|
||||
GitHub = "coolljt0725"
|
||||
|
||||
[people.cpuguy83]
|
||||
Name = "Brian Goff"
|
||||
Email = "cpuguy83@gmail.com"
|
||||
Github = "cpuguy83"
|
||||
|
||||
[people.crosbymichael]
|
||||
Name = "Michael Crosby"
|
||||
Email = "crosbymichael@gmail.com"
|
||||
GitHub = "crosbymichael"
|
||||
|
||||
[people.dnephin]
|
||||
Name = "Daniel Nephin"
|
||||
Email = "dnephin@gmail.com"
|
||||
GitHub = "dnephin"
|
||||
|
||||
[people.dongluochen]
|
||||
Name = "Dongluo Chen"
|
||||
Email = "dongluo.chen@docker.com"
|
||||
GitHub = "dongluochen"
|
||||
|
||||
[people.duglin]
|
||||
Name = "Doug Davis"
|
||||
Email = "dug@us.ibm.com"
|
||||
GitHub = "duglin"
|
||||
|
||||
[people.estesp]
|
||||
Name = "Phil Estes"
|
||||
Email = "estesp@linux.vnet.ibm.com"
|
||||
GitHub = "estesp"
|
||||
|
||||
[people.icecrime]
|
||||
Name = "Arnaud Porterie"
|
||||
Email = "arnaud@docker.com"
|
||||
GitHub = "icecrime"
|
||||
|
||||
[people.jhowardmsft]
|
||||
Name = "John Howard"
|
||||
Email = "jhoward@microsoft.com"
|
||||
GitHub = "jhowardmsft"
|
||||
|
||||
[people.lk4d4]
|
||||
Name = "Alexander Morozov"
|
||||
Email = "lk4d4@docker.com"
|
||||
GitHub = "lk4d4"
|
||||
|
||||
[people.mavenugo]
|
||||
Name = "Madhu Venugopal"
|
||||
Email = "madhu@docker.com"
|
||||
GitHub = "mavenugo"
|
||||
|
||||
[people.mhbauer]
|
||||
Name = "Morgan Bauer"
|
||||
Email = "mbauer@us.ibm.com"
|
||||
GitHub = "mhbauer"
|
||||
|
||||
[people.runcom]
|
||||
Name = "Antonio Murdaca"
|
||||
Email = "runcom@redhat.com"
|
||||
GitHub = "runcom"
|
||||
|
||||
[people.stevvooe]
|
||||
Name = "Stephen Day"
|
||||
Email = "stephen.day@docker.com"
|
||||
GitHub = "stevvooe"
|
||||
|
||||
[people.thajeztah]
|
||||
Name = "Sebastiaan van Stijn"
|
||||
Email = "github@gone.nl"
|
||||
GitHub = "thaJeztah"
|
||||
|
||||
[people.tianon]
|
||||
Name = "Tianon Gravi"
|
||||
Email = "admwiggin@gmail.com"
|
||||
GitHub = "tianon"
|
||||
|
||||
[people.tibor]
|
||||
Name = "Tibor Vass"
|
||||
Email = "tibor@docker.com"
|
||||
GitHub = "tiborvass"
|
||||
|
||||
[people.tonistiigi]
|
||||
Name = "Tõnis Tiigi"
|
||||
Email = "tonis@docker.com"
|
||||
GitHub = "tonistiigi"
|
||||
|
||||
[people.unclejack]
|
||||
Name = "Cristian Staretu"
|
||||
Email = "cristian.staretu@gmail.com"
|
||||
GitHub = "unclejack"
|
||||
|
||||
[people.vdemeester]
|
||||
Name = "Vincent Demeester"
|
||||
Email = "vincent@sbr.pm"
|
||||
GitHub = "vdemeester"
|
||||
|
||||
[people.vieux]
|
||||
Name = "Victor Vieux"
|
||||
Email = "vieux@docker.com"
|
||||
GitHub = "vieux"
|
||||
@@ -4,7 +4,7 @@ docker-credential-helpers is a suite of programs to use native stores to keep Do
|
||||
|
||||
## Installation
|
||||
|
||||
Go to the [Releases](https://github.com/calavera/docker-credential-helpers/releases) page and download the binary that works better for you. Put that binary in your `$PATH`, so Docker can find it.
|
||||
Go to the [Releases](https://github.com/docker/docker-credential-helpers/releases) page and download the binary that works better for you. Put that binary in your `$PATH`, so Docker can find it.
|
||||
|
||||
### Building from scratch
|
||||
|
||||
@@ -13,13 +13,13 @@ The programs in this repository are written with the Go programming language. Th
|
||||
1 - Download the source and put it in your `$GOPATH` with `go get`.
|
||||
|
||||
```
|
||||
$ go get github.com/calavera/docker-credential-helpers
|
||||
$ go get github.com/docker/docker-credential-helpers
|
||||
```
|
||||
|
||||
2 - Use `make` to build the program you want. That will leave any executable in the `bin` directory inside the repository.
|
||||
|
||||
```
|
||||
$ cd $GOPATH/calavera/docker-credentials-helpers
|
||||
$ cd $GOPATH/docker/docker-credentials-helpers
|
||||
$ make osxkeychain
|
||||
```
|
||||
|
||||
@@ -27,6 +27,8 @@ $ make osxkeychain
|
||||
|
||||
## Usage
|
||||
|
||||
### With the Docker Engine
|
||||
|
||||
Set the `credsStore` option in your `.docker/config.json` file with the suffix of the program you want to use. For instance, set it to `osxkeychain` if you want to use `docker-credential-osxkeychain`.
|
||||
|
||||
```json
|
||||
@@ -35,11 +37,24 @@ Set the `credsStore` option in your `.docker/config.json` file with the suffix o
|
||||
}
|
||||
```
|
||||
|
||||
### With other command line applications
|
||||
|
||||
The sub-package [client](https://godoc.org/github.com/docker/docker-credential-helpers/client) includes
|
||||
functions to call external programs from your own command line applications.
|
||||
|
||||
There are three things you need to know if you need to interact with a helper:
|
||||
|
||||
1. The name of the program to execute, for instance `docker-credential-osxkeychain`.
|
||||
2. The server address to identify the credentials, for instance `https://example.com`.
|
||||
3. The username and secret to store, when you want to store credentials.
|
||||
|
||||
You can see examples of each function in the [client](https://godoc.org/github.com/docker/docker-credential-helpers/client) documentation.
|
||||
|
||||
### Available programs
|
||||
|
||||
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store.
|
||||
1. secretservice: Provides a helper to use the D-Bus secret service as credentials store.
|
||||
2. wincred: Provides a helper to use Windows credentials manager as store.
|
||||
2. secretservice: Provides a helper to use the D-Bus secret service as credentials store.
|
||||
3. wincred: Provides a helper to use Windows credentials manager as store.
|
||||
|
||||
## Development
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ artifacts:
|
||||
|
||||
deploy:
|
||||
# All the zipped artifacts will be deployed
|
||||
description: "Visit the [Changelog](https://github.com/docker/docker-credential-helpers/blob/master/CHANGELOG.md) for a detailed description of what's new in this release."
|
||||
artifact: /.*\.zip/
|
||||
auth_token:
|
||||
secure: ixWmTXZs8aV5+9s6vPXziIcdMMLd+lBVINJ0K/Sy++2wllpRxUec4/TPVKUGLqvL
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// Store uses an external program to save credentials.
|
||||
func Store(program ProgramFunc, credentials *credentials.Credentials) error {
|
||||
cmd := program("store")
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(credentials); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Input(buffer)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
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()
|
||||
}
|
||||
|
||||
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 credentails 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))
|
||||
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
const (
|
||||
validServerAddress = "https://index.docker.io/v1"
|
||||
validServerAddress2 = "https://example.com:5002"
|
||||
invalidServerAddress = "https://foobar.example.com"
|
||||
missingCredsAddress = "https://missing.docker.io/v1"
|
||||
)
|
||||
|
||||
var errProgramExited = fmt.Errorf("exited 1")
|
||||
|
||||
// mockProgram simulates interactions between the docker client and a remote
|
||||
// credentials helper.
|
||||
// Unit tests inject this mocked command into the remote to control execution.
|
||||
type mockProgram struct {
|
||||
arg string
|
||||
input io.Reader
|
||||
}
|
||||
|
||||
// Output returns responses from the remote credentials helper.
|
||||
// It mocks those responses based in the input in the mock.
|
||||
func (m *mockProgram) Output() ([]byte, error) {
|
||||
in, err := ioutil.ReadAll(m.input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inS := string(in)
|
||||
|
||||
switch m.arg {
|
||||
case "erase":
|
||||
switch inS {
|
||||
case validServerAddress:
|
||||
return nil, nil
|
||||
default:
|
||||
return []byte("program failed"), errProgramExited
|
||||
}
|
||||
case "get":
|
||||
switch inS {
|
||||
case validServerAddress:
|
||||
return []byte(`{"Username": "foo", "Secret": "bar"}`), nil
|
||||
case validServerAddress2:
|
||||
return []byte(`{"Username": "<token>", "Secret": "abcd1234"}`), nil
|
||||
case missingCredsAddress:
|
||||
return []byte(credentials.NewErrCredentialsNotFound().Error()), errProgramExited
|
||||
case invalidServerAddress:
|
||||
return []byte("program failed"), errProgramExited
|
||||
}
|
||||
case "store":
|
||||
var c credentials.Credentials
|
||||
err := json.NewDecoder(strings.NewReader(inS)).Decode(&c)
|
||||
if err != nil {
|
||||
return []byte("error storing credentials"), errProgramExited
|
||||
}
|
||||
switch c.ServerURL {
|
||||
case validServerAddress:
|
||||
return nil, nil
|
||||
case validServerAddress2:
|
||||
return nil, nil
|
||||
default:
|
||||
return []byte("error storing credentials"), errProgramExited
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(fmt.Sprintf("unknown argument %q with %q", m.arg, inS)), errProgramExited
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
func (m *mockProgram) Input(in io.Reader) {
|
||||
m.input = in
|
||||
}
|
||||
|
||||
func mockProgramFn(args ...string) Program {
|
||||
return &mockProgram{
|
||||
arg: args[0],
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleStore() {
|
||||
p := NewShellProgramFunc("docker-credential-secretservice")
|
||||
|
||||
c := &credentials.Credentials{
|
||||
ServerURL: "https://example.com",
|
||||
Username: "calavera",
|
||||
Secret: "my super secret token",
|
||||
}
|
||||
|
||||
if err := Store(p, c); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStore(t *testing.T) {
|
||||
valid := []credentials.Credentials{
|
||||
{validServerAddress, "foo", "bar"},
|
||||
{validServerAddress2, "<token>", "abcd1234"},
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
if err := Store(mockProgramFn, &v); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
invalid := []credentials.Credentials{
|
||||
{invalidServerAddress, "foo", "bar"},
|
||||
}
|
||||
|
||||
for _, v := range invalid {
|
||||
if err := Store(mockProgramFn, &v); err == nil {
|
||||
t.Fatalf("Expected error for server %s, got nil", v.ServerURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleGet() {
|
||||
p := NewShellProgramFunc("docker-credential-secretservice")
|
||||
|
||||
creds, err := Get(p, "https://example.com")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Got credentials for user `%s` in `%s`\n", creds.Username, creds.ServerURL)
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
valid := []credentials.Credentials{
|
||||
{validServerAddress, "foo", "bar"},
|
||||
{validServerAddress2, "<token>", "abcd1234"},
|
||||
}
|
||||
|
||||
for _, v := range valid {
|
||||
c, err := Get(mockProgramFn, v.ServerURL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if c.Username != v.Username {
|
||||
t.Fatalf("expected username `%s`, got %s", v.Username, c.Username)
|
||||
}
|
||||
if c.Secret != v.Secret {
|
||||
t.Fatalf("expected secret `%s`, got %s", v.Secret, c.Secret)
|
||||
}
|
||||
}
|
||||
|
||||
invalid := []struct {
|
||||
serverURL string
|
||||
err string
|
||||
}{
|
||||
{missingCredsAddress, credentials.NewErrCredentialsNotFound().Error()},
|
||||
{invalidServerAddress, "error getting credentials - err: exited 1, out: `program failed`"},
|
||||
}
|
||||
|
||||
for _, v := range invalid {
|
||||
_, err := Get(mockProgramFn, v.serverURL)
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error for server %s, got nil", v.serverURL)
|
||||
}
|
||||
if err.Error() != v.err {
|
||||
t.Fatalf("Expected error `%s`, got `%v`", v.err, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleErase() {
|
||||
p := NewShellProgramFunc("docker-credential-secretservice")
|
||||
|
||||
if err := Erase(p, "https://example.com"); err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestErase(t *testing.T) {
|
||||
if err := Erase(mockProgramFn, validServerAddress); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := Erase(mockProgramFn, invalidServerAddress); err == nil {
|
||||
t.Fatalf("Expected error for server %s, got nil", invalidServerAddress)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"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 func(args ...string) Program {
|
||||
return &Shell{cmd: exec.Command(name, args...)}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
+37
-18
@@ -10,36 +10,50 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type credentialsGetResponse struct {
|
||||
Username string
|
||||
Secret string
|
||||
// Credentials holds the information shared between docker and the credentials store.
|
||||
type Credentials struct {
|
||||
ServerURL string
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if err := handleCommand(helper); err != nil {
|
||||
var err error
|
||||
if len(os.Args) != 2 {
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase>", 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)
|
||||
}
|
||||
}
|
||||
|
||||
func handleCommand(helper Helper) error {
|
||||
if len(os.Args) != 2 {
|
||||
return fmt.Errorf("Usage: %s <store|get|erase>", os.Args[0])
|
||||
}
|
||||
|
||||
switch os.Args[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, os.Stdin)
|
||||
return Store(helper, in)
|
||||
case "get":
|
||||
return get(helper, os.Stdin, os.Stdout)
|
||||
return Get(helper, in, out)
|
||||
case "erase":
|
||||
return erase(helper, os.Stdin)
|
||||
return Erase(helper, in)
|
||||
}
|
||||
return fmt.Errorf("Usage: %s <store|get|erase>", os.Args[0])
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
|
||||
func store(helper Helper, reader io.Reader) error {
|
||||
// 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)
|
||||
@@ -59,7 +73,10 @@ func store(helper Helper, reader io.Reader) error {
|
||||
return helper.Add(&creds)
|
||||
}
|
||||
|
||||
func get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
// 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)
|
||||
@@ -78,7 +95,7 @@ func get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := credentialsGetResponse{
|
||||
resp := Credentials{
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
@@ -92,7 +109,9 @@ func get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func erase(helper Helper, reader io.Reader) error {
|
||||
// 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)
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestStore(t *testing.T) {
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
if err := store(h, in); err != nil {
|
||||
if err := Store(h, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -82,13 +82,13 @@ func TestGet(t *testing.T) {
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
if err := store(h, in); err != nil {
|
||||
if err := Store(h, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := strings.NewReader(serverURL)
|
||||
w := new(bytes.Buffer)
|
||||
if err := get(h, buf, w); err != nil {
|
||||
if err := Get(h, buf, w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ func TestGet(t *testing.T) {
|
||||
t.Fatalf("expected output in the writer, got %d", w.Len())
|
||||
}
|
||||
|
||||
var c credentialsGetResponse
|
||||
var c Credentials
|
||||
if err := json.NewDecoder(w).Decode(&c); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -124,17 +124,17 @@ func TestErase(t *testing.T) {
|
||||
in := bytes.NewReader(b)
|
||||
|
||||
h := newMemoryStore()
|
||||
if err := store(h, in); err != nil {
|
||||
if err := Store(h, in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := strings.NewReader(serverURL)
|
||||
if err := erase(h, buf); err != nil {
|
||||
if err := Erase(h, buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
w := new(bytes.Buffer)
|
||||
if err := get(h, buf, w); err == nil {
|
||||
if err := Get(h, buf, w); err == nil {
|
||||
t.Fatal("expected error getting missing creds, got empty")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package credentials
|
||||
|
||||
// ErrCredentialsNotFound standarizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
const errCredentialsNotFoundMessage = "credentials not found in native keychain"
|
||||
|
||||
// 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
|
||||
}
|
||||
+4
-13
@@ -1,21 +1,12 @@
|
||||
package credentials
|
||||
|
||||
import "errors"
|
||||
|
||||
// Credentials holds the information shared between docker and the credentials store.
|
||||
type Credentials struct {
|
||||
ServerURL string
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// ErrCredentialsNotFound standarizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
var ErrCredentialsNotFound = errors.New("credentials not found in native keychain")
|
||||
|
||||
@@ -6,5 +6,5 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
credentials.Serve(osxkeychain.New())
|
||||
credentials.Serve(osxkeychain.Osxkeychain{})
|
||||
}
|
||||
|
||||
@@ -22,15 +22,11 @@ import (
|
||||
// when the credentials are not in the keychain.
|
||||
const errCredentialsNotFound = "The specified item could not be found in the keychain."
|
||||
|
||||
type osxkeychain struct{}
|
||||
|
||||
// New creates a new osxkeychain.
|
||||
func New() credentials.Helper {
|
||||
return osxkeychain{}
|
||||
}
|
||||
// Osxkeychain handles secrets using the OS X Keychain as store.
|
||||
type Osxkeychain struct{}
|
||||
|
||||
// Add adds new credentials to the keychain.
|
||||
func (h osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
func (h Osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
s, err := splitServer(creds.ServerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -52,7 +48,7 @@ func (h osxkeychain) Add(creds *credentials.Credentials) error {
|
||||
}
|
||||
|
||||
// Delete removes credentials from the keychain.
|
||||
func (h osxkeychain) Delete(serverURL string) error {
|
||||
func (h Osxkeychain) Delete(serverURL string) error {
|
||||
s, err := splitServer(serverURL)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -69,7 +65,7 @@ func (h osxkeychain) Delete(serverURL string) error {
|
||||
}
|
||||
|
||||
// Get returns the username and secret to use for a given registry server URL.
|
||||
func (h osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
func (h Osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
s, err := splitServer(serverURL)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
@@ -89,7 +85,7 @@ func (h osxkeychain) Get(serverURL string) (string, string, error) {
|
||||
goMsg := C.GoString(errMsg)
|
||||
|
||||
if goMsg == errCredentialsNotFound {
|
||||
return "", "", credentials.ErrCredentialsNotFound
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
return "", "", errors.New(goMsg)
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
|
||||
helper := New()
|
||||
helper := Osxkeychain{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,9 +37,9 @@ func TestOSXKeychainHelper(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := New()
|
||||
helper := Osxkeychain{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
if err != credentials.ErrCredentialsNotFound {
|
||||
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err)
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
credentials.Serve(secretservice.New())
|
||||
credentials.Serve(secretservice.Secretservice{})
|
||||
}
|
||||
|
||||
@@ -14,15 +14,11 @@ import (
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
type secretservice struct{}
|
||||
|
||||
// New creates a new secretservice.
|
||||
func New() credentials.Helper {
|
||||
return secretservice{}
|
||||
}
|
||||
// Secretservice handles secrets using Linux secret-service as a store.
|
||||
type Secretservice struct{}
|
||||
|
||||
// Add adds new credentials to the keychain.
|
||||
func (h secretservice) Add(creds *credentials.Credentials) error {
|
||||
func (h Secretservice) Add(creds *credentials.Credentials) error {
|
||||
if creds == nil {
|
||||
return errors.New("missing credentials")
|
||||
}
|
||||
@@ -41,8 +37,8 @@ func (h secretservice) Add(creds *credentials.Credentials) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes credentials from the keychain.
|
||||
func (h secretservice) Delete(serverURL string) error {
|
||||
// Delete removes credentials from the store.
|
||||
func (h Secretservice) Delete(serverURL string) error {
|
||||
if serverURL == "" {
|
||||
return errors.New("missing server url")
|
||||
}
|
||||
@@ -58,7 +54,7 @@ func (h secretservice) Delete(serverURL string) error {
|
||||
}
|
||||
|
||||
// Get returns the username and secret to use for a given registry server URL.
|
||||
func (h secretservice) Get(serverURL string) (string, string, error) {
|
||||
func (h Secretservice) Get(serverURL string) (string, string, error) {
|
||||
if serverURL == "" {
|
||||
return "", "", errors.New("missing server url")
|
||||
}
|
||||
@@ -78,7 +74,7 @@ func (h secretservice) Get(serverURL string) (string, string, error) {
|
||||
user := C.GoString(username)
|
||||
pass := C.GoString(secret)
|
||||
if pass == "" {
|
||||
return "", "", credentials.ErrCredentialsNotFound
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
return user, pass, nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
|
||||
helper := New()
|
||||
helper := Secretservice{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -41,9 +41,9 @@ func TestSecretServiceHelper(t *testing.T) {
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
t.Skip("test requires gnome-keyring but travis CI doesn't have it")
|
||||
|
||||
helper := New()
|
||||
helper := Secretservice{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
if err != credentials.ErrCredentialsNotFound {
|
||||
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err)
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,5 +6,5 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
credentials.Serve(wincred.New())
|
||||
credentials.Serve(wincred.Wincred{})
|
||||
}
|
||||
|
||||
@@ -5,15 +5,11 @@ import (
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
type wincred struct{}
|
||||
|
||||
// New creates a new wincred.
|
||||
func New() credentials.Helper {
|
||||
return wincred{}
|
||||
}
|
||||
// Wincred handles secrets using the Windows credential service.
|
||||
type Wincred struct{}
|
||||
|
||||
// Add adds new credentials to the windows credentials manager.
|
||||
func (h wincred) Add(creds *credentials.Credentials) error {
|
||||
func (h Wincred) Add(creds *credentials.Credentials) error {
|
||||
g := winc.NewGenericCredential(creds.ServerURL)
|
||||
g.UserName = creds.Username
|
||||
g.CredentialBlob = []byte(creds.Secret)
|
||||
@@ -22,7 +18,7 @@ func (h wincred) Add(creds *credentials.Credentials) error {
|
||||
}
|
||||
|
||||
// Delete removes credentials from the windows credentials manager.
|
||||
func (h wincred) Delete(serverURL string) error {
|
||||
func (h Wincred) Delete(serverURL string) error {
|
||||
g, err := winc.GetGenericCredential(serverURL)
|
||||
if g == nil {
|
||||
return nil
|
||||
@@ -34,10 +30,10 @@ func (h wincred) Delete(serverURL string) error {
|
||||
}
|
||||
|
||||
// Get retrieves credentials from the windows credentials manager.
|
||||
func (h wincred) Get(serverURL string) (string, string, error) {
|
||||
func (h Wincred) Get(serverURL string) (string, string, error) {
|
||||
g, _ := winc.GetGenericCredential(serverURL)
|
||||
if g == nil {
|
||||
return "", "", credentials.ErrCredentialsNotFound
|
||||
return "", "", credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
return g.UserName, string(g.CredentialBlob), nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ func TestWinCredHelper(t *testing.T) {
|
||||
Secret: "foobarbaz",
|
||||
}
|
||||
|
||||
helper := New()
|
||||
helper := Wincred{}
|
||||
if err := helper.Add(creds); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -37,9 +37,9 @@ func TestWinCredHelper(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMissingCredentials(t *testing.T) {
|
||||
helper := New()
|
||||
helper := Wincred{}
|
||||
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
|
||||
if err != credentials.ErrCredentialsNotFound {
|
||||
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err)
|
||||
if !credentials.IsErrCredentialsNotFound(err) {
|
||||
t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user