1package hcloud 2 3import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "net/url" 10 "strconv" 11 "time" 12 13 "github.com/hetznercloud/hcloud-go/hcloud/schema" 14) 15 16// SSHKey represents a SSH key in the Hetzner Cloud. 17type SSHKey struct { 18 ID int 19 Name string 20 Fingerprint string 21 PublicKey string 22 Labels map[string]string 23 Created time.Time 24} 25 26// SSHKeyClient is a client for the SSH keys API. 27type SSHKeyClient struct { 28 client *Client 29} 30 31// GetByID retrieves a SSH key by its ID. If the SSH key does not exist, nil is returned. 32func (c *SSHKeyClient) GetByID(ctx context.Context, id int) (*SSHKey, *Response, error) { 33 req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/ssh_keys/%d", id), nil) 34 if err != nil { 35 return nil, nil, err 36 } 37 38 var body schema.SSHKeyGetResponse 39 resp, err := c.client.Do(req, &body) 40 if err != nil { 41 if IsError(err, ErrorCodeNotFound) { 42 return nil, resp, nil 43 } 44 return nil, nil, err 45 } 46 return SSHKeyFromSchema(body.SSHKey), resp, nil 47} 48 49// GetByName retrieves a SSH key by its name. If the SSH key does not exist, nil is returned. 50func (c *SSHKeyClient) GetByName(ctx context.Context, name string) (*SSHKey, *Response, error) { 51 if name == "" { 52 return nil, nil, nil 53 } 54 sshKeys, response, err := c.List(ctx, SSHKeyListOpts{Name: name}) 55 if len(sshKeys) == 0 { 56 return nil, response, err 57 } 58 return sshKeys[0], response, err 59} 60 61// GetByFingerprint retreives a SSH key by its fingerprint. If the SSH key does not exist, nil is returned. 62func (c *SSHKeyClient) GetByFingerprint(ctx context.Context, fingerprint string) (*SSHKey, *Response, error) { 63 sshKeys, response, err := c.List(ctx, SSHKeyListOpts{Fingerprint: fingerprint}) 64 if len(sshKeys) == 0 { 65 return nil, response, err 66 } 67 return sshKeys[0], response, err 68} 69 70// Get retrieves a SSH key by its ID if the input can be parsed as an integer, otherwise it 71// retrieves a SSH key by its name. If the SSH key does not exist, nil is returned. 72func (c *SSHKeyClient) Get(ctx context.Context, idOrName string) (*SSHKey, *Response, error) { 73 if id, err := strconv.Atoi(idOrName); err == nil { 74 return c.GetByID(ctx, int(id)) 75 } 76 return c.GetByName(ctx, idOrName) 77} 78 79// SSHKeyListOpts specifies options for listing SSH keys. 80type SSHKeyListOpts struct { 81 ListOpts 82 Name string 83 Fingerprint string 84} 85 86func (l SSHKeyListOpts) values() url.Values { 87 vals := l.ListOpts.values() 88 if l.Name != "" { 89 vals.Add("name", l.Name) 90 } 91 if l.Fingerprint != "" { 92 vals.Add("fingerprint", l.Fingerprint) 93 } 94 return vals 95} 96 97// List returns a list of SSH keys for a specific page. 98// 99// Please note that filters specified in opts are not taken into account 100// when their value corresponds to their zero value or when they are empty. 101func (c *SSHKeyClient) List(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, *Response, error) { 102 path := "/ssh_keys?" + opts.values().Encode() 103 req, err := c.client.NewRequest(ctx, "GET", path, nil) 104 if err != nil { 105 return nil, nil, err 106 } 107 108 var body schema.SSHKeyListResponse 109 resp, err := c.client.Do(req, &body) 110 if err != nil { 111 return nil, nil, err 112 } 113 sshKeys := make([]*SSHKey, 0, len(body.SSHKeys)) 114 for _, s := range body.SSHKeys { 115 sshKeys = append(sshKeys, SSHKeyFromSchema(s)) 116 } 117 return sshKeys, resp, nil 118} 119 120// All returns all SSH keys. 121func (c *SSHKeyClient) All(ctx context.Context) ([]*SSHKey, error) { 122 return c.AllWithOpts(ctx, SSHKeyListOpts{ListOpts: ListOpts{PerPage: 50}}) 123} 124 125// AllWithOpts returns all SSH keys with the given options. 126func (c *SSHKeyClient) AllWithOpts(ctx context.Context, opts SSHKeyListOpts) ([]*SSHKey, error) { 127 allSSHKeys := []*SSHKey{} 128 129 err := c.client.all(func(page int) (*Response, error) { 130 opts.Page = page 131 sshKeys, resp, err := c.List(ctx, opts) 132 if err != nil { 133 return resp, err 134 } 135 allSSHKeys = append(allSSHKeys, sshKeys...) 136 return resp, nil 137 }) 138 if err != nil { 139 return nil, err 140 } 141 142 return allSSHKeys, nil 143} 144 145// SSHKeyCreateOpts specifies parameters for creating a SSH key. 146type SSHKeyCreateOpts struct { 147 Name string 148 PublicKey string 149 Labels map[string]string 150} 151 152// Validate checks if options are valid. 153func (o SSHKeyCreateOpts) Validate() error { 154 if o.Name == "" { 155 return errors.New("missing name") 156 } 157 if o.PublicKey == "" { 158 return errors.New("missing public key") 159 } 160 return nil 161} 162 163// Create creates a new SSH key with the given options. 164func (c *SSHKeyClient) Create(ctx context.Context, opts SSHKeyCreateOpts) (*SSHKey, *Response, error) { 165 if err := opts.Validate(); err != nil { 166 return nil, nil, err 167 } 168 reqBody := schema.SSHKeyCreateRequest{ 169 Name: opts.Name, 170 PublicKey: opts.PublicKey, 171 } 172 if opts.Labels != nil { 173 reqBody.Labels = &opts.Labels 174 } 175 reqBodyData, err := json.Marshal(reqBody) 176 if err != nil { 177 return nil, nil, err 178 } 179 180 req, err := c.client.NewRequest(ctx, "POST", "/ssh_keys", bytes.NewReader(reqBodyData)) 181 if err != nil { 182 return nil, nil, err 183 } 184 185 var respBody schema.SSHKeyCreateResponse 186 resp, err := c.client.Do(req, &respBody) 187 if err != nil { 188 return nil, resp, err 189 } 190 return SSHKeyFromSchema(respBody.SSHKey), resp, nil 191} 192 193// Delete deletes a SSH key. 194func (c *SSHKeyClient) Delete(ctx context.Context, sshKey *SSHKey) (*Response, error) { 195 req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/ssh_keys/%d", sshKey.ID), nil) 196 if err != nil { 197 return nil, err 198 } 199 return c.client.Do(req, nil) 200} 201 202// SSHKeyUpdateOpts specifies options for updating a SSH key. 203type SSHKeyUpdateOpts struct { 204 Name string 205 Labels map[string]string 206} 207 208// Update updates a SSH key. 209func (c *SSHKeyClient) Update(ctx context.Context, sshKey *SSHKey, opts SSHKeyUpdateOpts) (*SSHKey, *Response, error) { 210 reqBody := schema.SSHKeyUpdateRequest{ 211 Name: opts.Name, 212 } 213 if opts.Labels != nil { 214 reqBody.Labels = &opts.Labels 215 } 216 reqBodyData, err := json.Marshal(reqBody) 217 if err != nil { 218 return nil, nil, err 219 } 220 221 path := fmt.Sprintf("/ssh_keys/%d", sshKey.ID) 222 req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData)) 223 if err != nil { 224 return nil, nil, err 225 } 226 227 respBody := schema.SSHKeyUpdateResponse{} 228 resp, err := c.client.Do(req, &respBody) 229 if err != nil { 230 return nil, resp, err 231 } 232 return SSHKeyFromSchema(respBody.SSHKey), resp, nil 233} 234