Skip to content

Commit b70b863

Browse files
authored
Merge pull request scaleway#47 from terraform-providers/ssh-keys
add support for managing ssh keys
2 parents 53ae9b6 + 96ad338 commit b70b863

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+16497
-0
lines changed

scaleway/provider.go

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func Provider() terraform.ResourceProvider {
4545

4646
ResourcesMap: map[string]*schema.Resource{
4747
"scaleway_server": resourceScalewayServer(),
48+
"scaleway_ssh_key": resourceScalewaySSHKey(),
4849
"scaleway_ip": resourceScalewayIP(),
4950
"scaleway_security_group": resourceScalewaySecurityGroup(),
5051
"scaleway_security_group_rule": resourceScalewaySecurityGroupRule(),

scaleway/resource_ssh_key.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package scaleway
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/hashicorp/terraform/helper/schema"
8+
api "github.com/nicolai86/scaleway-sdk"
9+
"golang.org/x/crypto/ssh"
10+
)
11+
12+
func resourceScalewaySSHKey() *schema.Resource {
13+
return &schema.Resource{
14+
Create: resourceScalewaySSHKeyCreate,
15+
Read: resourceScalewaySSHKeyRead,
16+
Delete: resourceScalewaySSHKeyDelete,
17+
Importer: &schema.ResourceImporter{
18+
State: schema.ImportStatePassthrough,
19+
},
20+
21+
Schema: map[string]*schema.Schema{
22+
"key": {
23+
Type: schema.TypeString,
24+
Required: true,
25+
ForceNew: true,
26+
Description: "The ssh key",
27+
},
28+
},
29+
}
30+
}
31+
32+
func getSSHKeyFingerprint(key []byte) (string, error) {
33+
pubkey, _, _, _, err := ssh.ParseAuthorizedKey(key)
34+
if err != nil {
35+
return "", err
36+
}
37+
return ssh.FingerprintLegacyMD5(pubkey), nil
38+
}
39+
40+
func resourceScalewaySSHKeyCreate(d *schema.ResourceData, m interface{}) error {
41+
scaleway := m.(*Client).scaleway
42+
43+
mu.Lock()
44+
defer mu.Unlock()
45+
fingerprint, err := getSSHKeyFingerprint([]byte(d.Get("key").(string)))
46+
if err != nil {
47+
return err
48+
}
49+
50+
user, err := scaleway.GetUser()
51+
if err != nil {
52+
return err
53+
}
54+
55+
keys := []api.KeyDefinition{}
56+
exists := false
57+
for _, key := range user.SSHPublicKeys {
58+
exists = exists || key.Key == d.Get("key").(string)
59+
keys = append(keys, api.KeyDefinition{
60+
Key: key.Key,
61+
})
62+
}
63+
64+
// remote already contains the key, nothing to do
65+
if exists {
66+
d.SetId(fingerprint)
67+
return nil
68+
}
69+
70+
user, err = scaleway.PatchUserSSHKey(user.ID, api.UserPatchSSHKeyDefinition{
71+
SSHPublicKeys: append(keys, api.KeyDefinition{
72+
Key: d.Get("key").(string),
73+
}),
74+
})
75+
76+
if err != nil {
77+
return err
78+
}
79+
80+
d.SetId(fingerprint)
81+
return nil
82+
}
83+
84+
func resourceScalewaySSHKeyRead(d *schema.ResourceData, m interface{}) error {
85+
scaleway := m.(*Client).scaleway
86+
87+
user, err := scaleway.GetUser()
88+
if err != nil {
89+
return err
90+
}
91+
92+
exists := false
93+
for _, key := range user.SSHPublicKeys {
94+
exists = exists || strings.Contains(key.Fingerprint, d.Id())
95+
if exists {
96+
d.Set("key", key.Key)
97+
break
98+
}
99+
}
100+
if !exists {
101+
return fmt.Errorf("ssh key does not exist anymore")
102+
}
103+
104+
return nil
105+
}
106+
107+
func resourceScalewaySSHKeyDelete(d *schema.ResourceData, m interface{}) error {
108+
scaleway := m.(*Client).scaleway
109+
110+
mu.Lock()
111+
defer mu.Unlock()
112+
113+
user, err := scaleway.GetUser()
114+
if err != nil {
115+
return err
116+
}
117+
118+
keys := []api.KeyDefinition{}
119+
for _, key := range user.SSHPublicKeys {
120+
if !strings.Contains(key.Fingerprint, d.Id()) {
121+
keys = append(keys, api.KeyDefinition{
122+
Key: key.Key,
123+
})
124+
}
125+
}
126+
user, err = scaleway.PatchUserSSHKey(user.ID, api.UserPatchSSHKeyDefinition{
127+
SSHPublicKeys: keys,
128+
})
129+
130+
if err != nil {
131+
return err
132+
}
133+
d.SetId("")
134+
return nil
135+
}

scaleway/resource_ssh_key_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package scaleway
2+
3+
import (
4+
"errors"
5+
"strings"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform/helper/resource"
9+
"github.com/hashicorp/terraform/terraform"
10+
)
11+
12+
func TestGetSSHKeyFingerprint(t *testing.T) {
13+
key := []byte("ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYpDmIzRs5c+xs0jmljMbNYVcgV8fRruMCRDA4HKjGN2lqLTZhngGDXsdt/2kTNQQPAq2sR4N8mfX5wMRT/+jNb+8esPyY5WlElni0zmD7oLoPW4lYRES6f7EeAv6NttLfkDO42r15OtMnglcgWk1u4o3lOXuLbhzJT1qdicpDja22X3uR/xUy1AYhKBOoiSlQbkb7NhL0lA1xQNwerdaJJS8tFB+wViVDyP0f1HaIRxViFlTGuTbTuIJNR/7VJ9VBBuTnYXaRkPxz64sUXrtdVK8U0+4KsisyXwmgQKnvZBDj91wxz12OOzFSQ52iFprIj1JbkzuBmNWXUGKYzXJZ nicolai86@test")
14+
fingerprint, err := getSSHKeyFingerprint(key)
15+
16+
if err != nil {
17+
t.Errorf("Expected no error, but got %v", err.Error())
18+
}
19+
if fingerprint != "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f" {
20+
t.Errorf("Expected fingerprint of %q, but got %q", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f", fingerprint)
21+
}
22+
}
23+
24+
func TestAccScalewaySSHKey_Basic(t *testing.T) {
25+
resource.Test(t, resource.TestCase{
26+
PreCheck: func() { testAccPreCheck(t) },
27+
Providers: testAccProviders,
28+
CheckDestroy: testAccCheckScalewaySSHKeyDestroy,
29+
Steps: []resource.TestStep{
30+
resource.TestStep{
31+
Config: testAccCheckScalewaySSHKeyConfig,
32+
Check: resource.ComposeTestCheckFunc(
33+
resource.TestCheckResourceAttr(
34+
"scaleway_ssh_key.test", "id", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f"),
35+
),
36+
},
37+
resource.TestStep{
38+
Config: testAccCheckScalewaySSHKeysConfig,
39+
Check: resource.ComposeTestCheckFunc(
40+
resource.TestCheckResourceAttr(
41+
"scaleway_ssh_key.test", "id", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f"),
42+
resource.TestCheckResourceAttr(
43+
"scaleway_ssh_key.test2", "id", "71:a9:e9:ec:5a:43:bc:49:0c:59:1d:74:0d:bb:a4:24"),
44+
),
45+
},
46+
resource.TestStep{
47+
Config: testAccCheckScalewaySSHKeyConfig,
48+
Check: resource.ComposeTestCheckFunc(
49+
resource.TestCheckResourceAttr(
50+
"scaleway_ssh_key.test", "id", "d1:4c:45:59:a8:ee:e6:41:10:fb:3c:3e:54:98:5b:6f"),
51+
),
52+
},
53+
},
54+
})
55+
}
56+
57+
func testAccCheckScalewaySSHKeyDestroy(s *terraform.State) error {
58+
client := testAccProvider.Meta().(*Client).scaleway
59+
60+
for _, rs := range s.RootModule().Resources {
61+
if rs.Type != "scaleway" {
62+
continue
63+
}
64+
65+
user, err := client.GetUser()
66+
if err != nil {
67+
return err
68+
}
69+
for _, key := range user.SSHPublicKeys {
70+
if strings.Contains(key.Fingerprint, rs.Primary.ID) {
71+
return errors.New("key still exists.")
72+
}
73+
}
74+
return nil
75+
}
76+
77+
return nil
78+
}
79+
80+
var testAccCheckScalewaySSHKeyConfig = `
81+
resource "scaleway_ssh_key" "test" {
82+
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYpDmIzRs5c+xs0jmljMbNYVcgV8fRruMCRDA4HKjGN2lqLTZhngGDXsdt/2kTNQQPAq2sR4N8mfX5wMRT/+jNb+8esPyY5WlElni0zmD7oLoPW4lYRES6f7EeAv6NttLfkDO42r15OtMnglcgWk1u4o3lOXuLbhzJT1qdicpDja22X3uR/xUy1AYhKBOoiSlQbkb7NhL0lA1xQNwerdaJJS8tFB+wViVDyP0f1HaIRxViFlTGuTbTuIJNR/7VJ9VBBuTnYXaRkPxz64sUXrtdVK8U0+4KsisyXwmgQKnvZBDj91wxz12OOzFSQ52iFprIj1JbkzuBmNWXUGKYzXJZ test"
83+
}
84+
`
85+
86+
var testAccCheckScalewaySSHKeysConfig = `
87+
resource "scaleway_ssh_key" "test" {
88+
key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDYpDmIzRs5c+xs0jmljMbNYVcgV8fRruMCRDA4HKjGN2lqLTZhngGDXsdt/2kTNQQPAq2sR4N8mfX5wMRT/+jNb+8esPyY5WlElni0zmD7oLoPW4lYRES6f7EeAv6NttLfkDO42r15OtMnglcgWk1u4o3lOXuLbhzJT1qdicpDja22X3uR/xUy1AYhKBOoiSlQbkb7NhL0lA1xQNwerdaJJS8tFB+wViVDyP0f1HaIRxViFlTGuTbTuIJNR/7VJ9VBBuTnYXaRkPxz64sUXrtdVK8U0+4KsisyXwmgQKnvZBDj91wxz12OOzFSQ52iFprIj1JbkzuBmNWXUGKYzXJZ test"
89+
}
90+
91+
resource "scaleway_ssh_key" "test2" {
92+
key = "ssh-dss AAAAB3NzaC1kc3MAAACBAOU48/Wjx5JYNdcxdbb0mfJ3vtRU5wzPXmJcaa5CbpWeG6x3wD2+1aasxgO54YdSfEJwVXcoqPwx5gQfpAEgZmYi3M7Yurv+iwAJaSk+CFHOdhxUNPdwWKxsuIA1vk+edhqTKPC5fMFPpMQU/QDr5XegLhCUq11oRjnpfhzmi96/AAAAFQCTRSG8CPxOGVfYbZF/NjRGRgDWMQAAAIArEya6WPd7Bz19rn6u0KC0LeBHmxjoe0M9hblrFHjL4sLpxW1qipUKN+zwXKR9lv4Y/voyzirc7a8DPrEIyMy6SjPu/CiTwx7zDv08nE4qx20V8X0FrusvPbm5jzQJBweUvUZFZcM7Ybvk7RwawaLCGBGZ6Mg/P2YWTfor88NnjwAAAIBUfM9wfQvn0bHso8bsxFFtdME0eZbyeIRlU8JPjOatei4/eyFMHYvfqeGxiGgox1/E7/qX+3rXuypQFTa8DlhyteZCproysFa8NRh8PJZ7uchWrgPZHuXISW+UwlJ/5cJpFxn3ijzdOzEj5EiDM+LBtFYtA5/obIq68eqK6tqM0w== test2"
93+
}
94+
`

vendor/golang.org/x/crypto/curve25519/const_amd64.h

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/golang.org/x/crypto/curve25519/const_amd64.s

+20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vendor/golang.org/x/crypto/curve25519/cswap_amd64.s

+65
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)