mirror of
https://github.com/ysoftdevs/terraform-provider-bitbucketserver.git
synced 2026-03-13 05:45:14 +01:00
bitbucket/resource_plugin: Install plugins with URI Existing approach based on PostFileUpload() doesn't seem to work for Bitbucket server 6.10. Replace it with InstallPluginWithUri().
508 lines
14 KiB
Go
508 lines
14 KiB
Go
package bitbucket
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"path/filepath"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/hashicorp/terraform/helper/resource"
|
|
"github.com/hashicorp/terraform/helper/schema"
|
|
)
|
|
|
|
type Plugin struct {
|
|
Key string `json:"key,omitempty"`
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
EnabledByDefault bool `json:"enabledByDefault,omitempty"`
|
|
Version string `json:"version,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
UserInstalled bool `json:"userInstalled,omitempty"`
|
|
Optional bool `json:"optional,omitempty"`
|
|
Vendor struct {
|
|
Name string `json:"name,omitempty"`
|
|
MarketplaceLink string `json:"marketplaceLink,omitempty"`
|
|
Link string `json:"link,omitempty"`
|
|
} `json:"vendor,omitempty"`
|
|
}
|
|
|
|
type PluginLicense struct {
|
|
Valid bool `json:"valid,omitempty"`
|
|
Evaluation bool `json:"evaluation,omitempty"`
|
|
NearlyExpired bool `json:"nearlyExpired,omitempty"`
|
|
MaintenanceExpiryDate jsonTime `json:"maintenanceExpiryDate,omitempty"`
|
|
MaintenanceExpired bool `json:"maintenanceExpired,omitempty"`
|
|
LicenseType string `json:"licenseType,omitempty"`
|
|
ExpiryDate jsonTime `json:"expiryDate,omitempty"`
|
|
RawLicense string `json:"rawLicense,omitempty"`
|
|
Renewable bool `json:"renewable,omitempty"`
|
|
OrganizationName string `json:"organizationName,omitempty"`
|
|
ContactEmail string `json:"contactEmail,omitempty"`
|
|
Enterprise bool `json:"enterprise,omitempty"`
|
|
DataCenter bool `json:"dataCenter,omitempty"`
|
|
Subscription bool `json:"subscription,omitempty"`
|
|
Active bool `json:"active,omitempty"`
|
|
AutoRenewal bool `json:"autoRenewal,omitempty"`
|
|
Upgradable bool `json:"upgradable,omitempty"`
|
|
Crossgradeable bool `json:"crossgradeable,omitempty"`
|
|
PurchasePastServerCutoffDate bool `json:"purchasePastServerCutoffDate,omitempty"`
|
|
SupportEntitlementNumber string `json:"supportEntitlementNumber,omitempty"`
|
|
}
|
|
|
|
type PluginMarketplaceVersion struct {
|
|
Version string `json:"name,omitempty"`
|
|
Links struct {
|
|
Self struct {
|
|
Href string `json:"href,omitempty"`
|
|
} `json:"self,omitempty"`
|
|
} `json:"_links,omitempty"`
|
|
Embedded struct {
|
|
Artifact struct {
|
|
Links struct {
|
|
Self struct {
|
|
Href string `json:"href,omitempty"`
|
|
} `json:"self,omitempty"`
|
|
Binary struct {
|
|
Href string `json:"href,omitempty"`
|
|
} `json:"binary,omitempty"`
|
|
} `json:"_links,omitempty"`
|
|
} `json:"artifact,omitempty"`
|
|
} `json:"_embedded,omitempty"`
|
|
}
|
|
|
|
func (p *PluginMarketplaceVersion) Key() string {
|
|
re, _ := regexp.Compile("/rest/2/addons/([a-zA-Z0-9.-]*)/versions/build/.*")
|
|
values := re.FindStringSubmatch(p.Links.Self.Href)
|
|
if len(values) > 0 {
|
|
return values[1]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (p *PluginMarketplaceVersion) Filename() string {
|
|
ext := filepath.Ext(p.Embedded.Artifact.Links.Self.Href)
|
|
return fmt.Sprintf("%s-%s%s", p.Key(), p.Version, ext)
|
|
}
|
|
|
|
func resourcePlugin() *schema.Resource {
|
|
return &schema.Resource{
|
|
Create: resourcePluginCreate,
|
|
Update: resourcePluginUpdate,
|
|
Read: resourcePluginRead,
|
|
Exists: resourcePluginExists,
|
|
Delete: resourcePluginDelete,
|
|
Importer: &schema.ResourceImporter{
|
|
State: schema.ImportStatePassthrough,
|
|
},
|
|
|
|
Schema: map[string]*schema.Schema{
|
|
"key": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"version": {
|
|
Type: schema.TypeString,
|
|
Required: true,
|
|
ForceNew: true,
|
|
},
|
|
"enabled": {
|
|
Type: schema.TypeBool,
|
|
Optional: true,
|
|
Default: true,
|
|
},
|
|
"license": {
|
|
Type: schema.TypeString,
|
|
Optional: true,
|
|
},
|
|
"enabled_by_default": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"description": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"user_installed": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"optional": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"vendor": {
|
|
Type: schema.TypeMap,
|
|
Computed: true,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"name": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"link": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"marketplace_link": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"applied_license": {
|
|
Type: schema.TypeList,
|
|
Computed: true,
|
|
MaxItems: 1,
|
|
Elem: &schema.Resource{
|
|
Schema: map[string]*schema.Schema{
|
|
"valid": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"evaluation": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"nearly_expired": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"maintenance_expiry_date": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"maintenance_expired": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"license_type": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"expiry_date": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"raw_license": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
Sensitive: true,
|
|
},
|
|
"renewable": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"organization_name": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"contact_email": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
"enterprise": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"data_center": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"subscription": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"active": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"auto_renewal": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"upgradable": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"crossgradeable": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"purchase_past_server_cutoff_date": {
|
|
Type: schema.TypeBool,
|
|
Computed: true,
|
|
},
|
|
"support_entitlement_number": {
|
|
Type: schema.TypeString,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func resourcePluginCreate(d *schema.ResourceData, m interface{}) error {
|
|
provider := m.(*BitbucketServerProvider)
|
|
|
|
key := d.Get("key").(string)
|
|
version := d.Get("version").(string)
|
|
|
|
marketplacePluginVersion, err := readMarketplacePluginVersion(key, version, provider)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// first get a token for interacting with the UPM
|
|
resp, err := provider.BitbucketClient.Get("/rest/plugins/1.0/?os_authType=basic")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
upmToken := resp.Header.Get("upm-token")
|
|
|
|
pluginUri := marketplacePluginVersion.Embedded.Artifact.Links.Binary.Href
|
|
|
|
// now we can use the token to install plugin to Bitbucket
|
|
_, err = provider.BitbucketClient.InstallPluginWithUri("/rest/plugins/1.0/?token="+upmToken, pluginUri, d.Get("key").(string))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
d.SetId(key)
|
|
|
|
err = resource.Retry(time.Minute*2,
|
|
func() *resource.RetryError {
|
|
exists, err := resourcePluginExists(d, m)
|
|
if exists == false || err != nil {
|
|
return resource.RetryableError(fmt.Errorf("Waiting for plugin installation to finish..."))
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// need to also run an update loop to set enabled flags and license details
|
|
err = resource.Retry(time.Minute*2,
|
|
func() *resource.RetryError {
|
|
err := resourcePluginUpdate(d, m)
|
|
if err != nil {
|
|
return resource.RetryableError(fmt.Errorf("Waiting for plugin updates to finish..."))
|
|
} else {
|
|
return nil
|
|
}
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourcePluginUpdate(d *schema.ResourceData, m interface{}) error {
|
|
client := m.(*BitbucketServerProvider).BitbucketClient
|
|
|
|
key := d.Get("key").(string)
|
|
|
|
if d.IsNewResource() || d.HasChange("enabled") {
|
|
var plugin Plugin
|
|
req, err := client.Do("GET", fmt.Sprintf("/rest/plugins/1.0/%s-key?os_authType=basic", key), nil, "application/vnd.atl.plugins.plugin+json")
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
|
|
body, readErr := ioutil.ReadAll(req.Body)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
|
|
decodeErr := json.Unmarshal(body, &plugin)
|
|
if decodeErr != nil {
|
|
return decodeErr
|
|
}
|
|
|
|
plugin.Enabled = d.Get("enabled").(bool)
|
|
bytedata, err := json.Marshal(plugin)
|
|
_, err = client.Do("PUT", fmt.Sprintf("/rest/plugins/1.0/%s-key?os_authType=basic", key), bytes.NewBuffer(bytedata), "application/vnd.atl.plugins.plugin+json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if d.IsNewResource() || d.HasChange("license") {
|
|
license := d.Get("license").(string)
|
|
if license != "" {
|
|
licenseJson := map[string]string{"rawLicense": license}
|
|
bytedata, err := json.Marshal(licenseJson)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
req, err := client.Do("PUT", fmt.Sprintf("/rest/plugins/1.0/%s-key/license?os_authType=basic", key), bytes.NewBuffer(bytedata), "application/vnd.atl.plugins+json")
|
|
|
|
// ignore 400 errors as this happens if the license is already applied
|
|
if req == nil || (err != nil && req != nil && req.StatusCode != 400) {
|
|
return err
|
|
}
|
|
} else {
|
|
_, err := client.Do("DELETE", fmt.Sprintf("/rest/plugins/1.0/%s-key/license?os_authType=basic", key), nil, "application/vnd.atl.plugins+json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return resourcePluginRead(d, m)
|
|
}
|
|
|
|
func resourcePluginRead(d *schema.ResourceData, m interface{}) error {
|
|
id := d.Id()
|
|
if id != "" {
|
|
_ = d.Set("key", id)
|
|
}
|
|
|
|
client := m.(*BitbucketServerProvider).BitbucketClient
|
|
req, err := client.Get(fmt.Sprintf("/rest/plugins/1.0/%s-key", d.Get("key").(string)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var plugin Plugin
|
|
|
|
body, readErr := ioutil.ReadAll(req.Body)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
|
|
decodeErr := json.Unmarshal(body, &plugin)
|
|
if decodeErr != nil {
|
|
return decodeErr
|
|
}
|
|
|
|
_ = d.Set("enabled", plugin.Enabled)
|
|
_ = d.Set("enabled_by_default", plugin.EnabledByDefault)
|
|
_ = d.Set("version", plugin.Version)
|
|
_ = d.Set("description", plugin.Description)
|
|
_ = d.Set("name", plugin.Name)
|
|
_ = d.Set("user_installed", plugin.UserInstalled)
|
|
_ = d.Set("optional", plugin.Optional)
|
|
|
|
vendor := map[string]string{
|
|
"name": plugin.Vendor.Name,
|
|
"link": plugin.Vendor.Link,
|
|
"marketplace_link": plugin.Vendor.MarketplaceLink,
|
|
}
|
|
_ = d.Set("vendor", vendor)
|
|
|
|
// Hit the license API to get license details
|
|
|
|
req, err = client.Get(fmt.Sprintf("/rest/plugins/1.0/%s-key/license", d.Get("key").(string)))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var pluginLicense PluginLicense
|
|
|
|
body, readErr = ioutil.ReadAll(req.Body)
|
|
if readErr != nil {
|
|
return readErr
|
|
}
|
|
|
|
decodeErr = json.Unmarshal(body, &pluginLicense)
|
|
if decodeErr != nil {
|
|
return decodeErr
|
|
}
|
|
|
|
license := [1]map[string]interface{}{{
|
|
"valid": pluginLicense.Valid,
|
|
"evaluation": pluginLicense.Evaluation,
|
|
"nearly_expired": pluginLicense.NearlyExpired,
|
|
"maintenance_expiry_date": pluginLicense.MaintenanceExpiryDate.String(),
|
|
"maintenance_expired": pluginLicense.MaintenanceExpired,
|
|
"license_type": pluginLicense.LicenseType,
|
|
"expiry_date": pluginLicense.ExpiryDate.String(),
|
|
"raw_license": pluginLicense.RawLicense,
|
|
"renewable": pluginLicense.Renewable,
|
|
"organization_name": pluginLicense.OrganizationName,
|
|
"contact_email": pluginLicense.ContactEmail,
|
|
"enterprise": pluginLicense.Enterprise,
|
|
"data_center": pluginLicense.DataCenter,
|
|
"subscription": pluginLicense.Subscription,
|
|
"active": pluginLicense.Active,
|
|
"auto_renewal": pluginLicense.AutoRenewal,
|
|
"upgradable": pluginLicense.Upgradable,
|
|
"crossgradeable": pluginLicense.Crossgradeable,
|
|
"purchase_past_server_cutoff_date": pluginLicense.PurchasePastServerCutoffDate,
|
|
"support_entitlement_number": pluginLicense.SupportEntitlementNumber,
|
|
}}
|
|
_ = d.Set("applied_license", license)
|
|
|
|
return nil
|
|
}
|
|
|
|
func resourcePluginExists(d *schema.ResourceData, m interface{}) (bool, error) {
|
|
var key = ""
|
|
id := d.Id()
|
|
if id != "" {
|
|
key = id
|
|
} else {
|
|
key = d.Get("key").(string)
|
|
}
|
|
|
|
client := m.(*BitbucketServerProvider).BitbucketClient
|
|
req, err := client.Get(fmt.Sprintf("/rest/plugins/1.0/%s-key",
|
|
key,
|
|
))
|
|
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get plugin %s from bitbucket: %+v", key, err)
|
|
}
|
|
|
|
if req.StatusCode == 200 {
|
|
return true, nil
|
|
} else {
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
func resourcePluginDelete(d *schema.ResourceData, m interface{}) error {
|
|
client := m.(*BitbucketServerProvider).BitbucketClient
|
|
_, err := client.Delete(fmt.Sprintf("/rest/plugins/1.0/%s-key",
|
|
d.Get("key").(string),
|
|
))
|
|
|
|
return err
|
|
}
|
|
|
|
func readMarketplacePluginVersion(key string, version string, provider *BitbucketServerProvider) (*PluginMarketplaceVersion, error) {
|
|
marketplaceRequest, err := provider.MarketplaceClient.Get(fmt.Sprintf("/rest/2/addons/%s/versions/name/%s", key, version))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var marketplaceVersion PluginMarketplaceVersion
|
|
|
|
body, readerr := ioutil.ReadAll(marketplaceRequest.Body)
|
|
if readerr != nil {
|
|
return nil, readerr
|
|
}
|
|
|
|
decodeerr := json.Unmarshal(body, &marketplaceVersion)
|
|
if decodeerr != nil {
|
|
return nil, decodeerr
|
|
}
|
|
|
|
return &marketplaceVersion, nil
|
|
}
|