diff --git a/internal/provider/cloudinitsecret/datasource_cloudinitsecret.go b/internal/provider/cloudinitsecret/datasource_cloudinitsecret.go new file mode 100644 index 00000000..88d7d30d --- /dev/null +++ b/internal/provider/cloudinitsecret/datasource_cloudinitsecret.go @@ -0,0 +1,30 @@ +package cloudinitsecret + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +func DataSourceCloudInitSecret() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceCloudInitSecretRead, + Schema: DataSourceSchema(), + } +} + +func dataSourceCloudInitSecretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace := d.Get(constants.FieldCommonNamespace).(string) + name := d.Get(constants.FieldCommonName).(string) + secret, err := c.KubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + return diag.FromErr(resourceCloudInitSecretImport(d, secret)) +} diff --git a/internal/provider/cloudinitsecret/resource_cloudinitsecret.go b/internal/provider/cloudinitsecret/resource_cloudinitsecret.go new file mode 100644 index 00000000..bad0fd12 --- /dev/null +++ b/internal/provider/cloudinitsecret/resource_cloudinitsecret.go @@ -0,0 +1,131 @@ +package cloudinitsecret + +import ( + "context" + "encoding/base64" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/harvester/terraform-provider-harvester/internal/util" + "github.com/harvester/terraform-provider-harvester/pkg/client" + "github.com/harvester/terraform-provider-harvester/pkg/constants" + "github.com/harvester/terraform-provider-harvester/pkg/helper" + "github.com/harvester/terraform-provider-harvester/pkg/importer" +) + +func ResourceCloudInitSecret() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceCloudInitSecretCreate, + ReadContext: resourceCloudInitSecretRead, + DeleteContext: resourceCloudInitSecretDelete, + UpdateContext: resourceCloudInitSecretUpdate, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: Schema(), + } +} + +func resourceCloudInitSecretCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace := d.Get(constants.FieldCommonNamespace).(string) + name := d.Get(constants.FieldCommonName).(string) + toCreate, err := util.ResourceConstruct(d, Creator(namespace, name)) + if err != nil { + return diag.FromErr(err) + } + obj, err := c.KubeClient.CoreV1().Secrets(namespace).Create(ctx, toCreate.(*corev1.Secret), metav1.CreateOptions{}) + if err != nil { + return diag.FromErr(err) + } + + return diag.FromErr(resourceCloudInitSecretImport(d, obj)) +} + +func resourceCloudInitSecretUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace, name, err := helper.IDParts(d.Id()) + if err != nil { + return diag.FromErr(err) + } + obj, err := c.KubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + toUpdate, err := util.ResourceConstruct(d, Updater(obj)) + if err != nil { + return diag.FromErr(err) + } + _, err = c.KubeClient.CoreV1().Secrets(namespace).Update(ctx, toUpdate.(*corev1.Secret), metav1.UpdateOptions{}) + if err != nil { + return diag.FromErr(err) + } + + return resourceCloudInitSecretRead(ctx, d, meta) +} + +func resourceCloudInitSecretRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace, name, err := helper.IDParts(d.Id()) + if err != nil { + return diag.FromErr(err) + } + obj, err := c.KubeClient.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + d.SetId("") + return nil + } + return diag.FromErr(err) + } + return diag.FromErr(resourceCloudInitSecretImport(d, obj)) +} + +func resourceCloudInitSecretDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace, name, err := helper.IDParts(d.Id()) + if err != nil { + return diag.FromErr(err) + } + err = c.KubeClient.CoreV1().Secrets(namespace).Delete(ctx, name, metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return diag.FromErr(err) + } + d.SetId("") + return nil +} + +func resourceCloudInitSecretImport(d *schema.ResourceData, obj *corev1.Secret) error { + stateGetter, err := importer.ResourceCloudInitSecretStateGetter(obj) + if err != nil { + return err + } + + if d.Get(constants.FieldCloudInitSecretUserData) != "" && d.Get(constants.FieldCloudInitSecretUserDataBase64) == "" { + userdata, err := base64.StdEncoding.DecodeString(stateGetter.States[constants.FieldCloudInitSecretUserDataBase64].(string)) + if err != nil { + return fmt.Errorf("can't decode userdata: %v", err) + } + stateGetter.States[constants.FieldCloudInitSecretUserData] = string(userdata) + stateGetter.States[constants.FieldCloudInitSecretUserDataBase64] = "" + } + if d.Get(constants.FieldCloudInitSecretNetworkData) != "" && d.Get(constants.FieldCloudInitSecretNetworkDataBase64) == "" { + networkdata, err := base64.StdEncoding.DecodeString(stateGetter.States[constants.FieldCloudInitSecretNetworkDataBase64].(string)) + if err != nil { + return fmt.Errorf("can't decode userdata: %v", err) + } + stateGetter.States[constants.FieldCloudInitSecretUserData] = string(networkdata) + stateGetter.States[constants.FieldCloudInitSecretUserDataBase64] = "" + } + + return util.ResourceStatesSet(d, stateGetter) +} diff --git a/internal/provider/cloudinitsecret/resource_cloudinitsecret_constructor.go b/internal/provider/cloudinitsecret/resource_cloudinitsecret_constructor.go new file mode 100644 index 00000000..707a9dae --- /dev/null +++ b/internal/provider/cloudinitsecret/resource_cloudinitsecret_constructor.go @@ -0,0 +1,92 @@ +package cloudinitsecret + +import ( + "encoding/base64" + "fmt" + + corev1 "k8s.io/api/core/v1" + + "github.com/harvester/terraform-provider-harvester/internal/util" + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +var ( + _ util.Constructor = &Constructor{} +) + +type Constructor struct { + CloudInitSecret *corev1.Secret +} + +func (c *Constructor) Setup() util.Processors { + if c.CloudInitSecret.StringData == nil { + c.CloudInitSecret.StringData = map[string]string{} + } + c.CloudInitSecret.Data = map[string][]byte{} + + processors := util.NewProcessors().Tags(&c.CloudInitSecret.Labels).Description(&c.CloudInitSecret.Annotations) + customProcessors := []util.Processor{ + { + Field: constants.FieldCloudInitSecretUserData, + Parser: func(i interface{}) error { + c.CloudInitSecret.StringData["userdata"] = i.(string) + return nil + }, + }, + { + Field: constants.FieldCloudInitSecretUserDataBase64, + Parser: func(i interface{}) error { + value, err := base64.StdEncoding.DecodeString(i.(string)) + if err != nil { + return fmt.Errorf("failed to decode %s string: %w", constants.FieldCloudInitSecretUserDataBase64, err) + } + c.CloudInitSecret.StringData["userdata"] = string(value) + return nil + }, + }, + { + Field: constants.FieldCloudInitSecretNetworkData, + Parser: func(i interface{}) error { + c.CloudInitSecret.StringData["networkdata"] = i.(string) + return nil + }, + }, + { + Field: constants.FieldCloudInitSecretNetworkDataBase64, + Parser: func(i interface{}) error { + value, err := base64.StdEncoding.DecodeString(i.(string)) + if err != nil { + return fmt.Errorf("failed to decode %s string: %w", constants.FieldCloudInitSecretNetworkDataBase64, err) + } + c.CloudInitSecret.StringData["networkdata"] = string(value) + return nil + }, + }, + } + return append(processors, customProcessors...) +} + +func (c *Constructor) Validate() error { + return nil +} + +func (c *Constructor) Result() (interface{}, error) { + return c.CloudInitSecret, nil +} + +func newCloudInitSecretConstructor(cloudInitSecret *corev1.Secret) util.Constructor { + return &Constructor{ + CloudInitSecret: cloudInitSecret, + } +} + +func Creator(namespace, name string) util.Constructor { + cloudInitSecret := &corev1.Secret{ + ObjectMeta: util.NewObjectMeta(namespace, name), + } + return newCloudInitSecretConstructor(cloudInitSecret) +} + +func Updater(cloudInitSecret *corev1.Secret) util.Constructor { + return newCloudInitSecretConstructor(cloudInitSecret) +} diff --git a/internal/provider/cloudinitsecret/schema_cloudinitsecret.go b/internal/provider/cloudinitsecret/schema_cloudinitsecret.go new file mode 100644 index 00000000..efeba2e7 --- /dev/null +++ b/internal/provider/cloudinitsecret/schema_cloudinitsecret.go @@ -0,0 +1,35 @@ +package cloudinitsecret + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/harvester/terraform-provider-harvester/internal/util" + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +func Schema() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldCloudInitSecretUserData: { + Type: schema.TypeString, + Optional: true, + }, + constants.FieldCloudInitSecretUserDataBase64: { + Type: schema.TypeString, + Optional: true, + }, + constants.FieldCloudInitSecretNetworkData: { + Type: schema.TypeString, + Optional: true, + }, + constants.FieldCloudInitSecretNetworkDataBase64: { + Type: schema.TypeString, + Optional: true, + }, + } + util.NamespacedSchemaWrap(s, false) + return s +} + +func DataSourceSchema() map[string]*schema.Schema { + return util.DataSourceSchemaWrap(Schema()) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index ac2fc7c4..ef2e433a 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -10,6 +10,7 @@ import ( "github.com/mitchellh/go-homedir" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/harvester/terraform-provider-harvester/internal/provider/cloudinitsecret" "github.com/harvester/terraform-provider-harvester/internal/provider/clusternetwork" "github.com/harvester/terraform-provider-harvester/internal/provider/image" "github.com/harvester/terraform-provider-harvester/internal/provider/keypair" @@ -39,24 +40,26 @@ func Provider() *schema.Provider { }, }, DataSourcesMap: map[string]*schema.Resource{ - constants.ResourceTypeImage: image.DataSourceImage(), - constants.ResourceTypeKeyPair: keypair.DataSourceKeypair(), - constants.ResourceTypeNetwork: network.DataSourceNetwork(), - constants.ResourceTypeVirtualMachine: virtualmachine.DataSourceVirtualMachine(), - constants.ResourceTypeVolume: volume.DataSourceVolume(), - constants.ResourceTypeClusterNetwork: clusternetwork.DataSourceClusterNetwork(), - constants.ResourceTypeStorageClass: storageclass.DataSourceStorageClass(), - constants.ResourceTypeVLANConfig: vlanconfig.DataSourceVLANConfig(), + constants.ResourceTypeImage: image.DataSourceImage(), + constants.ResourceTypeKeyPair: keypair.DataSourceKeypair(), + constants.ResourceTypeNetwork: network.DataSourceNetwork(), + constants.ResourceTypeVirtualMachine: virtualmachine.DataSourceVirtualMachine(), + constants.ResourceTypeVolume: volume.DataSourceVolume(), + constants.ResourceTypeClusterNetwork: clusternetwork.DataSourceClusterNetwork(), + constants.ResourceTypeStorageClass: storageclass.DataSourceStorageClass(), + constants.ResourceTypeVLANConfig: vlanconfig.DataSourceVLANConfig(), + constants.ResourceTypeCloudInitSecret: cloudinitsecret.DataSourceCloudInitSecret(), }, ResourcesMap: map[string]*schema.Resource{ - constants.ResourceTypeImage: image.ResourceImage(), - constants.ResourceTypeKeyPair: keypair.ResourceKeypair(), - constants.ResourceTypeNetwork: network.ResourceNetwork(), - constants.ResourceTypeVirtualMachine: virtualmachine.ResourceVirtualMachine(), - constants.ResourceTypeVolume: volume.ResourceVolume(), - constants.ResourceTypeClusterNetwork: clusternetwork.ResourceClusterNetwork(), - constants.ResourceTypeStorageClass: storageclass.ResourceStorageClass(), - constants.ResourceTypeVLANConfig: vlanconfig.ResourceVLANConfig(), + constants.ResourceTypeImage: image.ResourceImage(), + constants.ResourceTypeKeyPair: keypair.ResourceKeypair(), + constants.ResourceTypeNetwork: network.ResourceNetwork(), + constants.ResourceTypeVirtualMachine: virtualmachine.ResourceVirtualMachine(), + constants.ResourceTypeVolume: volume.ResourceVolume(), + constants.ResourceTypeClusterNetwork: clusternetwork.ResourceClusterNetwork(), + constants.ResourceTypeStorageClass: storageclass.ResourceStorageClass(), + constants.ResourceTypeVLANConfig: vlanconfig.ResourceVLANConfig(), + constants.ResourceTypeCloudInitSecret: cloudinitsecret.ResourceCloudInitSecret(), }, } p.ConfigureContextFunc = configure(p) diff --git a/pkg/constants/constants_cloudinitsecret.go b/pkg/constants/constants_cloudinitsecret.go new file mode 100644 index 00000000..c11e7794 --- /dev/null +++ b/pkg/constants/constants_cloudinitsecret.go @@ -0,0 +1,10 @@ +package constants + +const ( + ResourceTypeCloudInitSecret = "harvester_cloudinit_secret" + + FieldCloudInitSecretUserData = "user_data" + FieldCloudInitSecretNetworkData = "network_data" + FieldCloudInitSecretUserDataBase64 = "user_data_base64" // #nosec G101 + FieldCloudInitSecretNetworkDataBase64 = "network_data_base64" +) diff --git a/pkg/importer/resource_cloudinitsecret_importer.go b/pkg/importer/resource_cloudinitsecret_importer.go new file mode 100644 index 00000000..6ff5f9ba --- /dev/null +++ b/pkg/importer/resource_cloudinitsecret_importer.go @@ -0,0 +1,28 @@ +package importer + +import ( + "encoding/base64" + + corev1 "k8s.io/api/core/v1" + + "github.com/harvester/terraform-provider-harvester/pkg/constants" + "github.com/harvester/terraform-provider-harvester/pkg/helper" +) + +func ResourceCloudInitSecretStateGetter(obj *corev1.Secret) (*StateGetter, error) { + states := map[string]interface{}{ + constants.FieldCommonNamespace: obj.Namespace, + constants.FieldCommonName: obj.Name, + constants.FieldCommonDescription: GetDescriptions(obj.Annotations), + constants.FieldCommonTags: GetTags(obj.Labels), + constants.FieldCloudInitSecretUserDataBase64: base64.StdEncoding.EncodeToString(obj.Data["userdata"]), + constants.FieldCloudInitSecretNetworkDataBase64: base64.StdEncoding.EncodeToString(obj.Data["networkdata"]), + } + + return &StateGetter{ + ID: helper.BuildID(obj.Namespace, obj.Name), + Name: obj.Name, + ResourceType: constants.ResourceTypeCloudInitSecret, + States: states, + }, nil +}