1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370
|
package hcloud
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"time"
"github.com/hetznercloud/hcloud-go/v2/hcloud/schema"
)
// CertificateType is the type of available certificate types.
type CertificateType string
// Available certificate types.
const (
CertificateTypeUploaded CertificateType = "uploaded"
CertificateTypeManaged CertificateType = "managed"
)
// CertificateStatusType is defines the type for the various managed
// certificate status.
type CertificateStatusType string
// Possible certificate status.
const (
CertificateStatusTypePending CertificateStatusType = "pending"
CertificateStatusTypeFailed CertificateStatusType = "failed"
// only in issuance.
CertificateStatusTypeCompleted CertificateStatusType = "completed"
// only in renewal.
CertificateStatusTypeScheduled CertificateStatusType = "scheduled"
CertificateStatusTypeUnavailable CertificateStatusType = "unavailable"
)
// CertificateUsedByRefType is the type of used by references for
// certificates.
type CertificateUsedByRefType string
// Possible users of certificates.
const (
CertificateUsedByRefTypeLoadBalancer CertificateUsedByRefType = "load_balancer"
)
// CertificateUsedByRef points to a resource that uses this certificate.
type CertificateUsedByRef struct {
ID int64
Type CertificateUsedByRefType
}
// CertificateStatus indicates the status of a managed certificate.
type CertificateStatus struct {
Issuance CertificateStatusType
Renewal CertificateStatusType
Error *Error
}
// IsFailed returns true if either the Issuance or the Renewal of a certificate
// failed. In this case the FailureReason field details the nature of the
// failure.
func (st *CertificateStatus) IsFailed() bool {
return st.Issuance == CertificateStatusTypeFailed || st.Renewal == CertificateStatusTypeFailed
}
// Certificate represents a certificate in the Hetzner Cloud.
type Certificate struct {
ID int64
Name string
Labels map[string]string
Type CertificateType
Certificate string
Created time.Time
NotValidBefore time.Time
NotValidAfter time.Time
DomainNames []string
Fingerprint string
Status *CertificateStatus
UsedBy []CertificateUsedByRef
}
// CertificateCreateResult is the result of creating a certificate.
type CertificateCreateResult struct {
Certificate *Certificate
Action *Action
}
// CertificateClient is a client for the Certificates API.
type CertificateClient struct {
client *Client
Action *ResourceActionClient
}
// GetByID retrieves a Certificate by its ID. If the Certificate does not exist, nil is returned.
func (c *CertificateClient) GetByID(ctx context.Context, id int64) (*Certificate, *Response, error) {
req, err := c.client.NewRequest(ctx, "GET", fmt.Sprintf("/certificates/%d", id), nil)
if err != nil {
return nil, nil, err
}
var body schema.CertificateGetResponse
resp, err := c.client.Do(req, &body)
if err != nil {
if IsError(err, ErrorCodeNotFound) {
return nil, resp, nil
}
return nil, nil, err
}
return CertificateFromSchema(body.Certificate), resp, nil
}
// GetByName retrieves a Certificate by its name. If the Certificate does not exist, nil is returned.
func (c *CertificateClient) GetByName(ctx context.Context, name string) (*Certificate, *Response, error) {
if name == "" {
return nil, nil, nil
}
Certificate, response, err := c.List(ctx, CertificateListOpts{Name: name})
if len(Certificate) == 0 {
return nil, response, err
}
return Certificate[0], response, err
}
// Get retrieves a Certificate by its ID if the input can be parsed as an integer, otherwise it
// retrieves a Certificate by its name. If the Certificate does not exist, nil is returned.
func (c *CertificateClient) Get(ctx context.Context, idOrName string) (*Certificate, *Response, error) {
if id, err := strconv.ParseInt(idOrName, 10, 64); err == nil {
return c.GetByID(ctx, id)
}
return c.GetByName(ctx, idOrName)
}
// CertificateListOpts specifies options for listing Certificates.
type CertificateListOpts struct {
ListOpts
Name string
Sort []string
}
func (l CertificateListOpts) values() url.Values {
vals := l.ListOpts.Values()
if l.Name != "" {
vals.Add("name", l.Name)
}
for _, sort := range l.Sort {
vals.Add("sort", sort)
}
return vals
}
// List returns a list of Certificates for a specific page.
//
// Please note that filters specified in opts are not taken into account
// when their value corresponds to their zero value or when they are empty.
func (c *CertificateClient) List(ctx context.Context, opts CertificateListOpts) ([]*Certificate, *Response, error) {
path := "/certificates?" + opts.values().Encode()
req, err := c.client.NewRequest(ctx, "GET", path, nil)
if err != nil {
return nil, nil, err
}
var body schema.CertificateListResponse
resp, err := c.client.Do(req, &body)
if err != nil {
return nil, nil, err
}
Certificates := make([]*Certificate, 0, len(body.Certificates))
for _, s := range body.Certificates {
Certificates = append(Certificates, CertificateFromSchema(s))
}
return Certificates, resp, nil
}
// All returns all Certificates.
func (c *CertificateClient) All(ctx context.Context) ([]*Certificate, error) {
return c.AllWithOpts(ctx, CertificateListOpts{ListOpts: ListOpts{PerPage: 50}})
}
// AllWithOpts returns all Certificates for the given options.
func (c *CertificateClient) AllWithOpts(ctx context.Context, opts CertificateListOpts) ([]*Certificate, error) {
allCertificates := []*Certificate{}
err := c.client.all(func(page int) (*Response, error) {
opts.Page = page
Certificates, resp, err := c.List(ctx, opts)
if err != nil {
return resp, err
}
allCertificates = append(allCertificates, Certificates...)
return resp, nil
})
if err != nil {
return nil, err
}
return allCertificates, nil
}
// CertificateCreateOpts specifies options for creating a new Certificate.
type CertificateCreateOpts struct {
Name string
Type CertificateType
Certificate string
PrivateKey string
Labels map[string]string
DomainNames []string
}
// Validate checks if options are valid.
func (o CertificateCreateOpts) Validate() error {
if o.Name == "" {
return errors.New("missing name")
}
switch o.Type {
case "", CertificateTypeUploaded:
return o.validateUploaded()
case CertificateTypeManaged:
return o.validateManaged()
default:
return fmt.Errorf("invalid type: %s", o.Type)
}
}
func (o CertificateCreateOpts) validateManaged() error {
if len(o.DomainNames) == 0 {
return errors.New("no domain names")
}
return nil
}
func (o CertificateCreateOpts) validateUploaded() error {
if o.Certificate == "" {
return errors.New("missing certificate")
}
if o.PrivateKey == "" {
return errors.New("missing private key")
}
return nil
}
// Create creates a new uploaded certificate.
//
// Create returns an error for certificates of any other type. Use
// CreateCertificate to create such certificates.
func (c *CertificateClient) Create(ctx context.Context, opts CertificateCreateOpts) (*Certificate, *Response, error) {
if !(opts.Type == "" || opts.Type == CertificateTypeUploaded) {
return nil, nil, fmt.Errorf("invalid certificate type: %s", opts.Type)
}
result, resp, err := c.CreateCertificate(ctx, opts)
if err != nil {
return nil, resp, err
}
return result.Certificate, resp, nil
}
// CreateCertificate creates a new certificate of any type.
func (c *CertificateClient) CreateCertificate(
ctx context.Context, opts CertificateCreateOpts,
) (CertificateCreateResult, *Response, error) {
var (
action *Action
reqBody schema.CertificateCreateRequest
)
if err := opts.Validate(); err != nil {
return CertificateCreateResult{}, nil, err
}
reqBody.Name = opts.Name
switch opts.Type {
case "", CertificateTypeUploaded:
reqBody.Type = string(CertificateTypeUploaded)
reqBody.Certificate = opts.Certificate
reqBody.PrivateKey = opts.PrivateKey
case CertificateTypeManaged:
reqBody.Type = string(CertificateTypeManaged)
reqBody.DomainNames = opts.DomainNames
default:
return CertificateCreateResult{}, nil, fmt.Errorf("invalid certificate type: %v", opts.Type)
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return CertificateCreateResult{}, nil, err
}
req, err := c.client.NewRequest(ctx, "POST", "/certificates", bytes.NewReader(reqBodyData))
if err != nil {
return CertificateCreateResult{}, nil, err
}
respBody := schema.CertificateCreateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return CertificateCreateResult{}, resp, err
}
cert := CertificateFromSchema(respBody.Certificate)
if respBody.Action != nil {
action = ActionFromSchema(*respBody.Action)
}
return CertificateCreateResult{Certificate: cert, Action: action}, resp, nil
}
// CertificateUpdateOpts specifies options for updating a Certificate.
type CertificateUpdateOpts struct {
Name string
Labels map[string]string
}
// Update updates a Certificate.
func (c *CertificateClient) Update(ctx context.Context, certificate *Certificate, opts CertificateUpdateOpts) (*Certificate, *Response, error) {
reqBody := schema.CertificateUpdateRequest{}
if opts.Name != "" {
reqBody.Name = &opts.Name
}
if opts.Labels != nil {
reqBody.Labels = &opts.Labels
}
reqBodyData, err := json.Marshal(reqBody)
if err != nil {
return nil, nil, err
}
path := fmt.Sprintf("/certificates/%d", certificate.ID)
req, err := c.client.NewRequest(ctx, "PUT", path, bytes.NewReader(reqBodyData))
if err != nil {
return nil, nil, err
}
respBody := schema.CertificateUpdateResponse{}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, resp, err
}
return CertificateFromSchema(respBody.Certificate), resp, nil
}
// Delete deletes a certificate.
func (c *CertificateClient) Delete(ctx context.Context, certificate *Certificate) (*Response, error) {
req, err := c.client.NewRequest(ctx, "DELETE", fmt.Sprintf("/certificates/%d", certificate.ID), nil)
if err != nil {
return nil, err
}
return c.client.Do(req, nil)
}
// RetryIssuance retries the issuance of a failed managed certificate.
func (c *CertificateClient) RetryIssuance(ctx context.Context, certificate *Certificate) (*Action, *Response, error) {
var respBody schema.CertificateIssuanceRetryResponse
req, err := c.client.NewRequest(ctx, "POST", fmt.Sprintf("/certificates/%d/actions/retry", certificate.ID), nil)
if err != nil {
return nil, nil, err
}
resp, err := c.client.Do(req, &respBody)
if err != nil {
return nil, nil, err
}
action := ActionFromSchema(respBody.Action)
return action, resp, nil
}
|