Skip to content
This repository was archived by the owner on Feb 16, 2023. It is now read-only.

Commit d29a942

Browse files
authored
Merge pull request #396 from secrethub/eddy/395-cli-2
Add support for 1Password CLI 2 in the migration
2 parents 3962e85 + cd7d62a commit d29a942

4 files changed

Lines changed: 305 additions & 111 deletions

File tree

internals/onepassword/cli_v1.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package onepassword
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"fmt"
7+
)
8+
9+
type OPV1CLI struct{}
10+
11+
func (op *OPV1CLI) IsV2() bool {
12+
return false
13+
}
14+
15+
func (op *OPV1CLI) CreateVault(name string) error {
16+
_, err := execOP("create", "vault", name)
17+
if err != nil {
18+
return fmt.Errorf("could not create vault '%s': %s", name, err)
19+
}
20+
return nil
21+
}
22+
23+
func (op *OPV1CLI) CreateItem(vault string, template *ItemTemplate, title string) error {
24+
jsonTemplate, err := json.Marshal(template)
25+
if err != nil {
26+
return err
27+
}
28+
29+
encodedTemplate := base64.RawURLEncoding.EncodeToString(jsonTemplate)
30+
31+
_, err = execOP("create", "item", "apicredential", "--vault="+vault, encodedTemplate, "title="+title)
32+
return err
33+
}
34+
35+
func (op *OPV1CLI) SetField(vault, item, field, value string) error {
36+
_, err := execOP("edit", "item", item, fmt.Sprintf(`%s=%s`, field, value), "--vault="+vault)
37+
if err != nil {
38+
return fmt.Errorf("could not set field '%s'.'%s'.'%s'", vault, item, field)
39+
}
40+
return nil
41+
}
42+
43+
// GetFields returns a title-to-value map of the fields from the first section of the given 1Password item.
44+
// The rest of the fields are ignored as the migration tool only stores information in the first
45+
// section of each item.
46+
func (op *OPV1CLI) GetFields(vault, item string) (map[string]string, error) {
47+
opItem := struct {
48+
Details ItemTemplate `json:"details"`
49+
}{}
50+
opItemJSON, err := execOP("get", "item", item, "--vault="+vault)
51+
if err != nil {
52+
return nil, fmt.Errorf("could not get item '%s'.'%s' from 1Password: %s", vault, item, err)
53+
}
54+
err = json.Unmarshal(opItemJSON, &opItem)
55+
if err != nil {
56+
return nil, fmt.Errorf("unexpected format of 1Password item in `op get item` command output: %s", err)
57+
}
58+
59+
fields := make(map[string]string, len(opItem.Details.Sections[0].Fields))
60+
for _, field := range opItem.Details.Sections[0].Fields {
61+
fields[field.Title] = field.Value
62+
}
63+
return fields, nil
64+
}
65+
66+
func (op *OPV1CLI) ExistsVault(vaultName string) (bool, error) {
67+
vaultsBytes, err := execOP("list", "vaults")
68+
if err != nil {
69+
return false, fmt.Errorf("could not list vaults: %s", err)
70+
}
71+
72+
vaultsJSON := make([]struct {
73+
UUID string `json:"uuid"`
74+
Name string `json:"name"`
75+
}, 0)
76+
77+
err = json.Unmarshal(vaultsBytes, &vaultsJSON)
78+
if err != nil {
79+
return false, fmt.Errorf("unexpected format of `op list vaults`: %s", vaultsBytes)
80+
}
81+
82+
for _, vault := range vaultsJSON {
83+
if vault.Name == vaultName {
84+
return true, nil
85+
}
86+
}
87+
88+
return false, nil
89+
}
90+
91+
func (op *OPV1CLI) ExistsItemInVault(vault string, itemName string) (bool, error) {
92+
itemsBytes, err := execOP("list", "items", "--vault", vault)
93+
if err != nil {
94+
return false, fmt.Errorf("could not list items in vault %s: %s", vault, err)
95+
}
96+
97+
itemsJSON := make([]struct {
98+
Overview struct {
99+
Title string `json:"title"`
100+
} `json:"overview"`
101+
}, 0)
102+
103+
err = json.Unmarshal(itemsBytes, &itemsJSON)
104+
if err != nil {
105+
return false, fmt.Errorf("unexpected format of `op list items`: %s", itemsBytes)
106+
}
107+
108+
for _, item := range itemsJSON {
109+
if item.Overview.Title == itemName {
110+
return true, nil
111+
}
112+
}
113+
114+
return false, nil
115+
}

internals/onepassword/cli_v2.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package onepassword
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io/ioutil"
7+
"os"
8+
)
9+
10+
type OPV2CLI struct{}
11+
12+
func (op *OPV2CLI) IsV2() bool {
13+
return true
14+
}
15+
16+
func (op *OPV2CLI) CreateVault(name string) error {
17+
_, err := execOP("vault", "create", name)
18+
if err != nil {
19+
return fmt.Errorf("could not create vault '%s': %s", name, err)
20+
}
21+
return nil
22+
}
23+
24+
func (op *OPV2CLI) CreateItem(vault string, template *ItemTemplate, title string) error {
25+
jsonTemplate, err := json.Marshal(template)
26+
if err != nil {
27+
return err
28+
}
29+
30+
tempJSONFile, err := ioutil.TempFile(os.TempDir(), "jsonTemplate-")
31+
if err != nil {
32+
return err
33+
}
34+
defer os.Remove(tempJSONFile.Name())
35+
36+
if _, err = tempJSONFile.Write(jsonTemplate); err != nil {
37+
return err
38+
}
39+
40+
_, err = execOP("item", "create", "--category=apicredential", "--vault="+vault, "--template="+tempJSONFile.Name(), "--title="+title)
41+
if err != nil {
42+
return err
43+
}
44+
45+
err = tempJSONFile.Close()
46+
return err
47+
}
48+
49+
func (op *OPV2CLI) SetField(vault, item, field, value string) error {
50+
_, err := execOP("item", "edit", item, fmt.Sprintf(`%s=%s`, field, value), "--vault="+vault)
51+
if err != nil {
52+
return fmt.Errorf("could not set field '%s'.'%s'.'%s'", vault, item, field)
53+
}
54+
return nil
55+
}
56+
57+
// GetFields returns a title-to-value map of the fields from the first section of the given 1Password item.
58+
// The rest of the fields are ignored as the migration tool only stores information in the first
59+
// section of each item.
60+
func (op *OPV2CLI) GetFields(vault, item string) (map[string]string, error) {
61+
opItem := struct {
62+
Fields []v2ItemFieldTemplate `json:"fields"`
63+
}{}
64+
opItemJSON, err := execOP("item", "get", item, "--vault="+vault, "--format=json")
65+
if err != nil {
66+
return nil, fmt.Errorf("could not get item '%s'.'%s' from 1Password: %s", vault, item, err)
67+
}
68+
err = json.Unmarshal(opItemJSON, &opItem)
69+
if err != nil {
70+
return nil, fmt.Errorf("unexpected format of 1Password item in `op get item` command output: %s", err)
71+
}
72+
73+
fields := make(map[string]string, len(opItem.Fields))
74+
for _, field := range opItem.Fields {
75+
fields[field.Label] = field.Value
76+
}
77+
return fields, nil
78+
}
79+
80+
type v2ItemFieldTemplate struct {
81+
ID string `json:"id"`
82+
Type string `json:"type"`
83+
Label string `json:"label"`
84+
Value string `json:"value"`
85+
}
86+
87+
func (op *OPV2CLI) ExistsVault(vaultName string) (bool, error) {
88+
vaultsBytes, err := execOP("vault", "list", "--format=json")
89+
if err != nil {
90+
return false, fmt.Errorf("could not list vaults: %s", err)
91+
}
92+
93+
vaultsJSON := make([]struct {
94+
ID string `json:"id"`
95+
Name string `json:"name"`
96+
}, 0)
97+
98+
err = json.Unmarshal(vaultsBytes, &vaultsJSON)
99+
if err != nil {
100+
return false, fmt.Errorf("unexpected format of `op list vaults`: %s", vaultsBytes)
101+
}
102+
103+
for _, vault := range vaultsJSON {
104+
if vault.Name == vaultName {
105+
return true, nil
106+
}
107+
}
108+
109+
return false, nil
110+
}
111+
112+
func (op *OPV2CLI) ExistsItemInVault(vault string, itemName string) (bool, error) {
113+
itemsBytes, err := execOP("item", "list", "--vault="+vault, "--format=json")
114+
if err != nil {
115+
return false, fmt.Errorf("could not list items in vault %s: %s", vault, err)
116+
}
117+
118+
itemsJSON := make([]struct {
119+
Title string `json:"title"`
120+
}, 0)
121+
122+
err = json.Unmarshal(itemsBytes, &itemsJSON)
123+
if err != nil {
124+
return false, fmt.Errorf("unexpected format of `op list items`: %s", itemsBytes)
125+
}
126+
127+
for _, item := range itemsJSON {
128+
if item.Title == itemName {
129+
return true, nil
130+
}
131+
}
132+
133+
return false, nil
134+
}

internals/onepassword/onepassword.go

Lines changed: 17 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package onepassword
22

33
import (
44
"bytes"
5-
"encoding/base64"
65
"encoding/json"
76
"fmt"
87
"io/ioutil"
@@ -14,55 +13,30 @@ import (
1413
"github.com/mitchellh/go-homedir"
1514
)
1615

17-
func CreateVault(name string) error {
18-
_, err := execOP("create", "vault", name)
19-
if err != nil {
20-
return fmt.Errorf("could not create vault '%s': %s", name, err)
21-
}
22-
return nil
16+
type OPCLI interface {
17+
IsV2() bool
18+
CreateVault(name string) error
19+
CreateItem(vault string, template *ItemTemplate, title string) error
20+
SetField(vault, item, field, value string) error
21+
GetFields(vault, item string) (map[string]string, error)
22+
ExistsVault(vaultName string) (bool, error)
23+
ExistsItemInVault(vault string, itemName string) (bool, error)
2324
}
2425

25-
func CreateItem(vault string, template *ItemTemplate, title string) error {
26-
jsonTemplate, err := json.Marshal(template)
26+
func GetOPClient() (OPCLI, error) {
27+
out, err := execOP("--version")
2728
if err != nil {
28-
return err
29+
return nil, err
2930
}
3031

31-
encodedTemplate := base64.RawURLEncoding.EncodeToString(jsonTemplate)
32+
version := strings.TrimSpace(string(out))
3233

33-
_, err = execOP("create", "item", "apicredential", "--vault="+vault, encodedTemplate, "title="+title)
34-
return err
35-
}
36-
37-
func SetField(vault, item, field, value string) error {
38-
_, err := execOP("edit", "item", item, fmt.Sprintf(`%s=%s`, field, value), "--vault="+vault)
39-
if err != nil {
40-
return fmt.Errorf("could not set field '%s'.'%s'.'%s'", vault, item, field)
34+
if strings.HasPrefix(version, "2.") {
35+
return &OPV2CLI{}, nil
36+
} else if strings.HasPrefix(version, "1.") {
37+
return &OPV1CLI{}, nil
4138
}
42-
return nil
43-
}
44-
45-
// GetFields returns a title-to-value map of the fields from the first section of the given 1Password item.
46-
// The rest of the fields are ignored as the migration tool only stores information in the first
47-
// section of each item.
48-
func GetFields(vault, item string) (map[string]string, error) {
49-
opItem := struct {
50-
Details ItemTemplate `json:"details"`
51-
}{}
52-
opItemJSON, err := execOP("get", "item", item, "--vault="+vault)
53-
if err != nil {
54-
return nil, fmt.Errorf("could not get item '%s'.'%s' from 1Password: %s", vault, item, err)
55-
}
56-
err = json.Unmarshal(opItemJSON, &opItem)
57-
if err != nil {
58-
return nil, fmt.Errorf("unexpected format of 1Password item in `op get item` command output: %s", err)
59-
}
60-
61-
fields := make(map[string]string, len(opItem.Details.Sections[0].Fields))
62-
for _, field := range opItem.Details.Sections[0].Fields {
63-
fields[field.Title] = field.Value
64-
}
65-
return fields, nil
39+
return nil, fmt.Errorf("1password: op version not recognized")
6640
}
6741

6842
func NewItemTemplate() *ItemTemplate {
@@ -202,54 +176,3 @@ func GetSignInAddress() (string, error) {
202176

203177
return "", fmt.Errorf("unexpected format of 1password config file at %s: missing account entry for latest used account", path)
204178
}
205-
206-
func ExistsVault(vaultName string) (bool, error) {
207-
vaultsBytes, err := execOP("list", "vaults")
208-
if err != nil {
209-
return false, fmt.Errorf("could not list vaults: %s", err)
210-
}
211-
212-
vaultsJSON := make([]struct {
213-
UUID string `json:"uuid"`
214-
Name string `json:"name"`
215-
}, 0)
216-
217-
err = json.Unmarshal(vaultsBytes, &vaultsJSON)
218-
if err != nil {
219-
return false, fmt.Errorf("unexpected format of `op list vaults`: %s", vaultsBytes)
220-
}
221-
222-
for _, vault := range vaultsJSON {
223-
if vault.Name == vaultName {
224-
return true, nil
225-
}
226-
}
227-
228-
return false, nil
229-
}
230-
231-
func ExistsItemInVault(vault string, itemName string) (bool, error) {
232-
itemsBytes, err := execOP("list", "items", "--vault", vault)
233-
if err != nil {
234-
return false, fmt.Errorf("could not list items in vault %s: %s", vault, err)
235-
}
236-
237-
itemsJSON := make([]struct {
238-
Overview struct {
239-
Title string `json:"title"`
240-
} `json:"overview"`
241-
}, 0)
242-
243-
err = json.Unmarshal(itemsBytes, &itemsJSON)
244-
if err != nil {
245-
return false, fmt.Errorf("unexpected format of `op list items`: %s", itemsBytes)
246-
}
247-
248-
for _, item := range itemsJSON {
249-
if item.Overview.Title == itemName {
250-
return true, nil
251-
}
252-
}
253-
254-
return false, nil
255-
}

0 commit comments

Comments
 (0)