1
0
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 Message Date
David Calavera 5128fa1bad Release v0.3.0.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-05-31 17:41:15 -07:00
Vincent Demeester fee9277aa7 Merge pull request #15 from calavera/client
Add client functions to allow integrations within other CLIs.
2016-06-01 00:08:41 +02:00
David Calavera d80b179703 Merge pull request #16 from calavera/maintainers_file
Add maintainers file.
2016-05-31 14:52:43 -07:00
David Calavera c4fc9c07dd Add client functions to allow integrations within other CLIs.
This is a simplification of how the docker engine implements
this feature, but it will be ported there once this is merged.

Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-05-30 12:32:26 -07:00
David Calavera 2c63e775b4 Add maintainers file.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-05-27 18:19:08 -07:00
David Calavera 00703eb6db Merge pull request #13 from calavera/export_helpers
Publish helper structs and methods.
2016-03-24 15:56:00 -07:00
David Calavera df8c7a02f1 Publish helper structs and methods.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-03-24 15:08:17 -07:00
David Calavera 4b8917b1cf Merge pull request #12 from calavera/export_library
Export credentials functions to be able to use them as a library.
2016-03-24 13:32:25 -07:00
David Calavera 039c315f22 Export credentials functions to be able to use them as a library.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-03-24 14:52:17 -04:00
David Calavera 6bbb56ae3c Merge pull request #11 from mgreau/fix-doc
Update related to the move to Docker Organization
2016-03-18 13:21:15 -07:00
Maxime Gréau ec937eebe2 Update related to the move to Docker Organization
- Fix link to project releases
  - Fix go command

Signed-off-by: Maxime Gréau <greaumaxime@gmail.com>
2016-03-17 00:18:01 +01:00
David Calavera fa24bb8912 Merge pull request #10 from calavera/release_description
Add a link to the Changelog in each release.
2016-03-15 10:27:01 -07:00
David Calavera 76055c2deb Add a link to the Changelog in each release.
Signed-off-by: David Calavera <david.calavera@gmail.com>
2016-03-14 14:04:17 -04:00
20 changed files with 603 additions and 89 deletions
+4
View File
@@ -4,6 +4,10 @@ This changelog tracks the releases of docker-credential-helpers.
This project includes different binaries per platform. This project includes different binaries per platform.
The platform released is identified after the tag name. 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) ## v0.2.0 (Mac OS X, Windows, Linux)
- Initial release of docker-credential-secretservice for Linux. - Initial release of docker-credential-secretservice for Linux.
+160
View File
@@ -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"
+20 -5
View File
@@ -4,7 +4,7 @@ docker-credential-helpers is a suite of programs to use native stores to keep Do
## Installation ## 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 ### 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`. 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. 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 $ make osxkeychain
``` ```
@@ -27,6 +27,8 @@ $ make osxkeychain
## Usage ## 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`. 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 ```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 ### Available programs
1. osxkeychain: Provides a helper to use the OS X keychain as credentials store. 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. 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. 3. wincred: Provides a helper to use Windows credentials manager as store.
## Development ## Development
+1
View File
@@ -53,6 +53,7 @@ artifacts:
deploy: deploy:
# All the zipped artifacts will be deployed # 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/ artifact: /.*\.zip/
auth_token: auth_token:
secure: ixWmTXZs8aV5+9s6vPXziIcdMMLd+lBVINJ0K/Sy++2wllpRxUec4/TPVKUGLqvL secure: ixWmTXZs8aV5+9s6vPXziIcdMMLd+lBVINJ0K/Sy++2wllpRxUec4/TPVKUGLqvL
+70
View File
@@ -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
}
+192
View File
@@ -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)
}
}
+37
View File
@@ -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
}
+35 -16
View File
@@ -10,36 +10,50 @@ import (
"strings" "strings"
) )
type credentialsGetResponse struct { // Credentials holds the information shared between docker and the credentials store.
type Credentials struct {
ServerURL string
Username string Username string
Secret string Secret string
} }
// Serve initializes the credentials helper and parses the action argument. // 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) { 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) fmt.Fprintf(os.Stdout, "%v\n", err)
os.Exit(1) os.Exit(1)
} }
} }
func handleCommand(helper Helper) error { // HandleCommand uses a helper and a key to run a credential action.
if len(os.Args) != 2 { func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error {
return fmt.Errorf("Usage: %s <store|get|erase>", os.Args[0]) switch key {
}
switch os.Args[1] {
case "store": case "store":
return store(helper, os.Stdin) return Store(helper, in)
case "get": case "get":
return get(helper, os.Stdin, os.Stdout) return Get(helper, in, out)
case "erase": 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) scanner := bufio.NewScanner(reader)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
@@ -59,7 +73,10 @@ func store(helper Helper, reader io.Reader) error {
return helper.Add(&creds) 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) scanner := bufio.NewScanner(reader)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
@@ -78,7 +95,7 @@ func get(helper Helper, reader io.Reader, writer io.Writer) error {
return err return err
} }
resp := credentialsGetResponse{ resp := Credentials{
Username: username, Username: username,
Secret: secret, Secret: secret,
} }
@@ -92,7 +109,9 @@ func get(helper Helper, reader io.Reader, writer io.Writer) error {
return nil 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) scanner := bufio.NewScanner(reader)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
+7 -7
View File
@@ -50,7 +50,7 @@ func TestStore(t *testing.T) {
in := bytes.NewReader(b) in := bytes.NewReader(b)
h := newMemoryStore() h := newMemoryStore()
if err := store(h, in); err != nil { if err := Store(h, in); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -82,13 +82,13 @@ func TestGet(t *testing.T) {
in := bytes.NewReader(b) in := bytes.NewReader(b)
h := newMemoryStore() h := newMemoryStore()
if err := store(h, in); err != nil { if err := Store(h, in); err != nil {
t.Fatal(err) t.Fatal(err)
} }
buf := strings.NewReader(serverURL) buf := strings.NewReader(serverURL)
w := new(bytes.Buffer) w := new(bytes.Buffer)
if err := get(h, buf, w); err != nil { if err := Get(h, buf, w); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -96,7 +96,7 @@ func TestGet(t *testing.T) {
t.Fatalf("expected output in the writer, got %d", w.Len()) 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 { if err := json.NewDecoder(w).Decode(&c); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -124,17 +124,17 @@ func TestErase(t *testing.T) {
in := bytes.NewReader(b) in := bytes.NewReader(b)
h := newMemoryStore() h := newMemoryStore()
if err := store(h, in); err != nil { if err := Store(h, in); err != nil {
t.Fatal(err) t.Fatal(err)
} }
buf := strings.NewReader(serverURL) buf := strings.NewReader(serverURL)
if err := erase(h, buf); err != nil { if err := Erase(h, buf); err != nil {
t.Fatal(err) t.Fatal(err)
} }
w := new(bytes.Buffer) 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") t.Fatal("expected error getting missing creds, got empty")
} }
} }
+37
View File
@@ -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
View File
@@ -1,21 +1,12 @@
package credentials 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. // Helper is the interface a credentials store helper must implement.
type Helper interface { type Helper interface {
// Add appends credentials to the store.
Add(*Credentials) error Add(*Credentials) error
// Delete removes credentials from the store.
Delete(serverURL string) error Delete(serverURL string) error
// Get retrieves credentials from the store.
// It returns username and secret as strings.
Get(serverURL string) (string, string, error) 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")
+1 -1
View File
@@ -6,5 +6,5 @@ import (
) )
func main() { func main() {
credentials.Serve(osxkeychain.New()) credentials.Serve(osxkeychain.Osxkeychain{})
} }
+6 -10
View File
@@ -22,15 +22,11 @@ import (
// when the credentials are not in the keychain. // when the credentials are not in the keychain.
const errCredentialsNotFound = "The specified item could not be found in the keychain." const errCredentialsNotFound = "The specified item could not be found in the keychain."
type osxkeychain struct{} // Osxkeychain handles secrets using the OS X Keychain as store.
type Osxkeychain struct{}
// New creates a new osxkeychain.
func New() credentials.Helper {
return osxkeychain{}
}
// Add adds new credentials to the keychain. // 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) s, err := splitServer(creds.ServerURL)
if err != nil { if err != nil {
return err return err
@@ -52,7 +48,7 @@ func (h osxkeychain) Add(creds *credentials.Credentials) error {
} }
// Delete removes credentials from the keychain. // Delete removes credentials from the keychain.
func (h osxkeychain) Delete(serverURL string) error { func (h Osxkeychain) Delete(serverURL string) error {
s, err := splitServer(serverURL) s, err := splitServer(serverURL)
if err != nil { if err != nil {
return err 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. // 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) s, err := splitServer(serverURL)
if err != nil { if err != nil {
return "", "", err return "", "", err
@@ -89,7 +85,7 @@ func (h osxkeychain) Get(serverURL string) (string, string, error) {
goMsg := C.GoString(errMsg) goMsg := C.GoString(errMsg)
if goMsg == errCredentialsNotFound { if goMsg == errCredentialsNotFound {
return "", "", credentials.ErrCredentialsNotFound return "", "", credentials.NewErrCredentialsNotFound()
} }
return "", "", errors.New(goMsg) return "", "", errors.New(goMsg)
+4 -4
View File
@@ -13,7 +13,7 @@ func TestOSXKeychainHelper(t *testing.T) {
Secret: "foobarbaz", Secret: "foobarbaz",
} }
helper := New() helper := Osxkeychain{}
if err := helper.Add(creds); err != nil { if err := helper.Add(creds); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -37,9 +37,9 @@ func TestOSXKeychainHelper(t *testing.T) {
} }
func TestMissingCredentials(t *testing.T) { func TestMissingCredentials(t *testing.T) {
helper := New() helper := Osxkeychain{}
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if err != credentials.ErrCredentialsNotFound { if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err) t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
} }
} }
+1 -1
View File
@@ -6,5 +6,5 @@ import (
) )
func main() { func main() {
credentials.Serve(secretservice.New()) credentials.Serve(secretservice.Secretservice{})
} }
+7 -11
View File
@@ -14,15 +14,11 @@ import (
"github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/credentials"
) )
type secretservice struct{} // Secretservice handles secrets using Linux secret-service as a store.
type Secretservice struct{}
// New creates a new secretservice.
func New() credentials.Helper {
return secretservice{}
}
// Add adds new credentials to the keychain. // 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 { if creds == nil {
return errors.New("missing credentials") return errors.New("missing credentials")
} }
@@ -41,8 +37,8 @@ func (h secretservice) Add(creds *credentials.Credentials) error {
return nil return nil
} }
// Delete removes credentials from the keychain. // Delete removes credentials from the store.
func (h secretservice) Delete(serverURL string) error { func (h Secretservice) Delete(serverURL string) error {
if serverURL == "" { if serverURL == "" {
return errors.New("missing server url") 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. // 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 == "" { if serverURL == "" {
return "", "", errors.New("missing server url") return "", "", errors.New("missing server url")
} }
@@ -78,7 +74,7 @@ func (h secretservice) Get(serverURL string) (string, string, error) {
user := C.GoString(username) user := C.GoString(username)
pass := C.GoString(secret) pass := C.GoString(secret)
if pass == "" { if pass == "" {
return "", "", credentials.ErrCredentialsNotFound return "", "", credentials.NewErrCredentialsNotFound()
} }
return user, pass, nil return user, pass, nil
} }
+4 -4
View File
@@ -15,7 +15,7 @@ func TestSecretServiceHelper(t *testing.T) {
Secret: "foobarbaz", Secret: "foobarbaz",
} }
helper := New() helper := Secretservice{}
if err := helper.Add(creds); err != nil { if err := helper.Add(creds); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -41,9 +41,9 @@ func TestSecretServiceHelper(t *testing.T) {
func TestMissingCredentials(t *testing.T) { func TestMissingCredentials(t *testing.T) {
t.Skip("test requires gnome-keyring but travis CI doesn't have it") 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") _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if err != credentials.ErrCredentialsNotFound { if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err) t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
} }
} }
+1 -1
View File
@@ -6,5 +6,5 @@ import (
) )
func main() { func main() {
credentials.Serve(wincred.New()) credentials.Serve(wincred.Wincred{})
} }
+6 -10
View File
@@ -5,15 +5,11 @@ import (
"github.com/docker/docker-credential-helpers/credentials" "github.com/docker/docker-credential-helpers/credentials"
) )
type wincred struct{} // Wincred handles secrets using the Windows credential service.
type Wincred struct{}
// New creates a new wincred.
func New() credentials.Helper {
return wincred{}
}
// Add adds new credentials to the windows credentials manager. // 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 := winc.NewGenericCredential(creds.ServerURL)
g.UserName = creds.Username g.UserName = creds.Username
g.CredentialBlob = []byte(creds.Secret) 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. // 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) g, err := winc.GetGenericCredential(serverURL)
if g == nil { if g == nil {
return nil return nil
@@ -34,10 +30,10 @@ func (h wincred) Delete(serverURL string) error {
} }
// Get retrieves credentials from the windows credentials manager. // 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) g, _ := winc.GetGenericCredential(serverURL)
if g == nil { if g == nil {
return "", "", credentials.ErrCredentialsNotFound return "", "", credentials.NewErrCredentialsNotFound()
} }
return g.UserName, string(g.CredentialBlob), nil return g.UserName, string(g.CredentialBlob), nil
} }
+4 -4
View File
@@ -13,7 +13,7 @@ func TestWinCredHelper(t *testing.T) {
Secret: "foobarbaz", Secret: "foobarbaz",
} }
helper := New() helper := Wincred{}
if err := helper.Add(creds); err != nil { if err := helper.Add(creds); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -37,9 +37,9 @@ func TestWinCredHelper(t *testing.T) {
} }
func TestMissingCredentials(t *testing.T) { func TestMissingCredentials(t *testing.T) {
helper := New() helper := Wincred{}
_, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd") _, _, err := helper.Get("https://adsfasdf.wrewerwer.com/asdfsdddd")
if err != credentials.ErrCredentialsNotFound { if !credentials.IsErrCredentialsNotFound(err) {
t.Fatalf("exptected ErrCredentialsNotFound, got %v", err) t.Fatalf("expected ErrCredentialsNotFound, got %v", err)
} }
} }