From 395d94e98a078c7f118369ba418a2e2cf8f77bbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Tue, 13 Aug 2024 09:33:26 +0200 Subject: [PATCH 1/8] Add IPPool and LoadBalancer resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit related-to: harvester/harvester#4813 related-to: harvester/harvester#4814 Signed-off-by: Moritz Röhrich --- go.mod | 3 +- go.sum | 2 + internal/provider/ippool/datasource_ippool.go | 34 +++++ internal/provider/ippool/resource_ippool.go | 127 ++++++++++++++++++ .../ippool/resource_ippool_constructor.go | 93 +++++++++++++ internal/provider/ippool/schema_ippool.go | 111 +++++++++++++++ .../loadbalancer/datasource_loadbalancer.go | 35 +++++ .../loadbalancer/resource_loadbalancer.go | 77 +++++++++++ .../resource_loadbalancer_constructor.go | 48 +++++++ .../loadbalancer/schema_loadbalancer.go | 100 ++++++++++++++ internal/provider/provider.go | 22 +-- .../tests/resource_clusternetwork_test.go | 15 +++ pkg/client/client.go | 32 +++-- pkg/constants/constants_ippool.go | 32 +++++ pkg/constants/constants_loadbalancer.go | 40 ++++++ pkg/importer/resource_ippool_importer.go | 20 +++ .../resource_loadbalancer_importer.go | 21 +++ 17 files changed, 791 insertions(+), 21 deletions(-) create mode 100644 internal/provider/ippool/datasource_ippool.go create mode 100644 internal/provider/ippool/resource_ippool.go create mode 100644 internal/provider/ippool/resource_ippool_constructor.go create mode 100644 internal/provider/ippool/schema_ippool.go create mode 100644 internal/provider/loadbalancer/datasource_loadbalancer.go create mode 100644 internal/provider/loadbalancer/resource_loadbalancer.go create mode 100644 internal/provider/loadbalancer/resource_loadbalancer_constructor.go create mode 100644 internal/provider/loadbalancer/schema_loadbalancer.go create mode 100644 internal/tests/resource_clusternetwork_test.go create mode 100644 pkg/constants/constants_ippool.go create mode 100644 pkg/constants/constants_loadbalancer.go create mode 100644 pkg/importer/resource_ippool_importer.go create mode 100644 pkg/importer/resource_loadbalancer_importer.go diff --git a/go.mod b/go.mod index 4b2d4a61..8b0d5f6e 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,9 @@ replace ( ) require ( + github.com/google/uuid v1.6.0 github.com/harvester/harvester v1.3.2 + github.com/harvester/harvester-load-balancer v0.3.0 github.com/harvester/harvester-network-controller v0.5.4 github.com/hashicorp/terraform-plugin-docs v0.4.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.6.1 @@ -101,7 +103,6 @@ require ( github.com/google/go-cmp v0.6.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect diff --git a/go.sum b/go.sum index ed22fd4d..a60063a3 100644 --- a/go.sum +++ b/go.sum @@ -2083,6 +2083,8 @@ github.com/harvester/go-common v0.0.0-20240627083535-c1208a490f89 h1:wqQaZqxxOQr github.com/harvester/go-common v0.0.0-20240627083535-c1208a490f89/go.mod h1:70bhzIm0iXpR1J8hr5jg2jzAvNkM4f2ZsU1R4Sbx9X4= github.com/harvester/harvester v1.3.2 h1:kKC8tL+wygB6U8s5q3YUtkJyr3uJmbR8spyw9OEC34A= github.com/harvester/harvester v1.3.2/go.mod h1:dCT/UePTJTW3QFzwyRfkhGGyr5ts0ZvkX/MsOlP2GdA= +github.com/harvester/harvester-load-balancer v0.3.0 h1:R4ymvCTFraic+zIMNYFPtIf8KAR3S9dnHdPRgHn85gk= +github.com/harvester/harvester-load-balancer v0.3.0/go.mod h1:bbsdXuGkVeoMuzb2yLrrc8RXmm4hpIe9zdoreziUUaA= github.com/harvester/harvester-network-controller v0.5.4 h1:EmVOo1M4n+TXFKoh4lwdx+pzaXZg5lE4XYUACUD1kMU= github.com/harvester/harvester-network-controller v0.5.4/go.mod h1:IeAcGckQpEEXq6CpXgm9sUrOJmqEkdeU2+DFoxbL084= github.com/harvester/node-manager v0.1.5-0.20230614075852-de2da3ef3aca h1:ySmceYltYR2wmeAvifoXtHQOK/N6v7z7Rkaq/6IPJ5A= diff --git a/internal/provider/ippool/datasource_ippool.go b/internal/provider/ippool/datasource_ippool.go new file mode 100644 index 00000000..21de9701 --- /dev/null +++ b/internal/provider/ippool/datasource_ippool.go @@ -0,0 +1,34 @@ +package ippool + +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 DataSourceIPPool() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceIPPoolRead, + Schema: DataSourceSchema(), + } +} + +func dataSourceIPPoolRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + name := data.Get(constants.FieldCommonName).(string) + + ippool, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + IPPools(). + Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + return diag.FromErr(resourceIPPoolImport(data, ippool)) +} diff --git a/internal/provider/ippool/resource_ippool.go b/internal/provider/ippool/resource_ippool.go new file mode 100644 index 00000000..08a3096d --- /dev/null +++ b/internal/provider/ippool/resource_ippool.go @@ -0,0 +1,127 @@ +package ippool + +import ( + "context" + "time" + + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "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 ResourceIPPool() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceIPPoolCreate, + ReadContext: resourceIPPoolRead, + UpdateContext: resourceIPPoolUpdate, + DeleteContext: resourceIPPoolDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: Schema(), + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(2 * time.Minute), + Read: schema.DefaultTimeout(2 * time.Minute), + Update: schema.DefaultTimeout(2 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), + Default: schema.DefaultTimeout(2 * time.Minute), + }, + } +} + +func resourceIPPoolCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + name := data.Get(constants.FieldCommonName).(string) + toCreate, err := util.ResourceConstruct(data, Creator(name)) + if err != nil { + return diag.FromErr(err) + } + ippool, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + IPPools(). + Create(ctx, toCreate.(*loadbalancerv1.IPPool), metav1.CreateOptions{}) + if err != nil { + return diag.FromErr(err) + } + data.SetId(helper.BuildID("", name)) + return diag.FromErr(resourceIPPoolImport(data, ippool)) +} + +func resourceIPPoolRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + _, name, err := helper.IDParts(data.Id()) + if err != nil { + return diag.FromErr(err) + } + + ippool, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + IPPools().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + + return diag.FromErr(resourceIPPoolImport(data, ippool)) +} + +func resourceIPPoolUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + _, name, err := helper.IDParts(data.Id()) + if err != nil { + return diag.FromErr(err) + } + + obj, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + IPPools().Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + + toUpdate, err := util.ResourceConstruct(data, Updater(obj)) + if err != nil { + return diag.FromErr(err) + } + + ippool, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + IPPools().Update(ctx, toUpdate.(*loadbalancerv1.IPPool), metav1.UpdateOptions{}) + if err != nil { + return diag.FromErr(err) + } + return diag.FromErr(resourceIPPoolImport(data, ippool)) +} + +func resourceIPPoolDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + _, name, err := helper.IDParts(data.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + IPPools().Delete(ctx, name, metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return diag.FromErr(err) + } + + return diag.FromErr(nil) +} + +func resourceIPPoolImport(data *schema.ResourceData, obj *loadbalancerv1.IPPool) error { + stateGetter, err := importer.ResourceIPPoolStateGetter(obj) + if err != nil { + return err + } + return util.ResourceStatesSet(data, stateGetter) +} diff --git a/internal/provider/ippool/resource_ippool_constructor.go b/internal/provider/ippool/resource_ippool_constructor.go new file mode 100644 index 00000000..fc26644f --- /dev/null +++ b/internal/provider/ippool/resource_ippool_constructor.go @@ -0,0 +1,93 @@ +package ippool + +import ( + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + + "github.com/harvester/terraform-provider-harvester/internal/util" + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +var ( + _ util.Constructor = &Constructor{} +) + +type Constructor struct { + IPPool *loadbalancerv1.IPPool +} + +func (c *Constructor) Setup() util.Processors { + processors := util.NewProcessors(). + Tags(&c.IPPool.Labels). + String(constants.FieldIPPoolDescription, &c.IPPool.Spec.Description, false) + + subresourceProcessors := []util.Processor{ + { + Field: constants.SubresourceTypeIPPoolRange, + Parser: c.subresourceIPPoolRangeParser, + Required: true, + }, + { + Field: constants.SubresourceTypeIPPoolSelector, + Parser: c.subresourceIPPoolSelectorParser, + Required: false, + }, + } + + return append(processors, subresourceProcessors...) +} + +func (c *Constructor) Validate() error { + return nil +} + +func (c *Constructor) Result() (interface{}, error) { + return c.IPPool, nil +} + +func newIPPoolConstructor(ippool *loadbalancerv1.IPPool) util.Constructor { + return &Constructor{ + IPPool: ippool, + } +} + +func Creator(name string) util.Constructor { + ippool := &loadbalancerv1.IPPool{ + ObjectMeta: util.NewObjectMeta("", name), + } + return newIPPoolConstructor(ippool) +} + +func Updater(ippool *loadbalancerv1.IPPool) util.Constructor { + ippool.Spec.Ranges = []loadbalancerv1.Range{} + ippool.Spec.Selector = loadbalancerv1.Selector{} + return newIPPoolConstructor(ippool) +} + +func (c *Constructor) subresourceIPPoolRangeParser(data interface{}) error { + ippoolRange := data.(map[string]interface{}) + start := ippoolRange[constants.FieldRangeStart].(string) + end := ippoolRange[constants.FieldRangeEnd].(string) + subnet := ippoolRange[constants.FieldRangeSubnet].(string) + gateway := ippoolRange[constants.FieldRangeGateway].(string) + + c.IPPool.Spec.Ranges = append(c.IPPool.Spec.Ranges, loadbalancerv1.Range{ + RangeStart: start, + RangeEnd: end, + Subnet: subnet, + Gateway: gateway, + }) + return nil +} + +func (c *Constructor) subresourceIPPoolSelectorParser(data interface{}) error { + ippoolSelector := data.(map[string]interface{}) + + priority := uint32(ippoolSelector[constants.FieldSelectorPriority].(int)) + network := ippoolSelector[constants.FieldSelectorNetwork].(string) + + c.IPPool.Spec.Selector = loadbalancerv1.Selector{ + Priority: priority, + Network: network, + } + return nil +} diff --git a/internal/provider/ippool/schema_ippool.go b/internal/provider/ippool/schema_ippool.go new file mode 100644 index 00000000..44a6a67f --- /dev/null +++ b/internal/provider/ippool/schema_ippool.go @@ -0,0 +1,111 @@ +package ippool + +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.FieldIPPoolDescription: { + Type: schema.TypeString, + Optional: true, + }, + constants.SubresourceTypeIPPoolRange: { + Type: schema.TypeList, + Required: true, + MinItems: 1, + Elem: &schema.Resource{ + Schema: subresourceSchemaIPPoolRange(), + }, + Description: "IP Range belonging to this pool, can be given multiple times", + }, + constants.SubresourceTypeIPPoolSelector: { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: subresourceSchemaIPPoolSelector(), + }, + }, + } + util.NonNamespacedSchemaWrap(s) + return s +} + +func DataSourceSchema() map[string]*schema.Schema { + return util.DataSourceSchemaWrap(Schema()) +} + +func subresourceSchemaIPPoolRange() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldRangeStart: { + Type: schema.TypeString, + Required: true, + Description: "", + }, + constants.FieldRangeEnd: { + Type: schema.TypeString, + Required: true, + Description: "", + }, + constants.FieldRangeSubnet: { + Type: schema.TypeString, + Required: true, + Description: "", + }, + constants.FieldRangeGateway: { + Type: schema.TypeString, + Required: true, + Description: "", + }, + } + return s +} + +func subresourceSchemaIPPoolSelector() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldSelectorPriority: { + Type: schema.TypeInt, + Optional: true, + Description: "Priority of the IP pool. Large numbers have higher priority", + }, + constants.FieldSelectorNetwork: { + Type: schema.TypeString, + Optional: true, + Description: "Namespace/name of the VM network", + }, + constants.FieldSelectorScope: { + Type: schema.TypeList, + Optional: true, + Description: "Scope of the IP pool", + Elem: &schema.Resource{ + Schema: subresourceSchemaIPPoolSelectorScope(), + }, + }, + } + return s +} + +func subresourceSchemaIPPoolSelectorScope() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldScopeProject: { + Type: schema.TypeString, + Optional: true, + Description: "Name of the project", + }, + constants.FieldScopeNamespace: { + Type: schema.TypeString, + Optional: true, + Description: "Namespace of the VMs of the guest cluster", + }, + constants.FieldScopeGuestCluster: { + Type: schema.TypeString, + Optional: true, + Description: "Name of the guest cluster", + }, + } + return s +} diff --git a/internal/provider/loadbalancer/datasource_loadbalancer.go b/internal/provider/loadbalancer/datasource_loadbalancer.go new file mode 100644 index 00000000..7fdd7b7e --- /dev/null +++ b/internal/provider/loadbalancer/datasource_loadbalancer.go @@ -0,0 +1,35 @@ +package loadbalancer + +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 DataSourceLoadBalancer() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceLoadBalancerRead, + Schema: DataSourceSchema(), + } +} + +func dataSourceLoadBalancerRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace := data.Get(constants.FieldCommonNamespace).(string) + name := data.Get(constants.FieldCommonName).(string) + + loadbalancer, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + LoadBalancers(namespace). + Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + return diag.FromErr(resourceLoadBalancerImport(data, loadbalancer)) +} diff --git a/internal/provider/loadbalancer/resource_loadbalancer.go b/internal/provider/loadbalancer/resource_loadbalancer.go new file mode 100644 index 00000000..6e439c8f --- /dev/null +++ b/internal/provider/loadbalancer/resource_loadbalancer.go @@ -0,0 +1,77 @@ +package loadbalancer + +import ( + "context" + "time" + + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "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 ResourceLoadBalancer() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceLoadBalancerCreate, + ReadContext: resourceLoadBalancerRead, + UpdateContext: resourceLoadBalancerUpdate, + DeleteContext: resourceLoadBalancerDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: Schema(), + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(2 * time.Minute), + Read: schema.DefaultTimeout(2 * time.Minute), + Update: schema.DefaultTimeout(2 * time.Minute), + Delete: schema.DefaultTimeout(2 * time.Minute), + Default: schema.DefaultTimeout(2 * time.Minute), + }, + } +} + +func resourceLoadBalancerCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace := data.Get(constants.FieldCommonNamespace).(string) + name := data.Get(constants.FieldCommonName).(string) + toCreate, err := util.ResourceConstruct(data, Creator(namespace, name)) + if err != nil { + return diag.FromErr(err) + } + lb, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + LoadBalancers(namespace). + Create(ctx, toCreate.(*loadbalancerv1.LoadBalancer), metav1.CreateOptions{}) + if err != nil { + return diag.FromErr(err) + } + data.SetId(helper.BuildID(namespace, name)) + return diag.FromErr(resourceLoadBalancerImport(data, lb)) +} + +func resourceLoadBalancerRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + return diag.FromErr(nil) +} + +func resourceLoadBalancerUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + return diag.FromErr(nil) +} + +func resourceLoadBalancerDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + return diag.FromErr(nil) +} + +func resourceLoadBalancerImport(data *schema.ResourceData, obj *loadbalancerv1.LoadBalancer) error { + stateGetter, err := importer.ResourceLoadBalancerStateGetter(obj) + if err != nil { + return err + } + return util.ResourceStatesSet(data, stateGetter) +} diff --git a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go new file mode 100644 index 00000000..16300c58 --- /dev/null +++ b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go @@ -0,0 +1,48 @@ +package loadbalancer + +import ( + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + + "github.com/harvester/terraform-provider-harvester/internal/util" + "github.com/harvester/terraform-provider-harvester/pkg/constants" +) + +var ( + _ util.Constructor = &Constructor{} +) + +type Constructor struct { + LoadBalancer *loadbalancerv1.LoadBalancer +} + +func (c *Constructor) Setup() util.Processors { + return util.NewProcessors(). + Tags(&c.LoadBalancer.Labels). + Description(&c.LoadBalancer.Annotations). + String(constants.FieldLoadBalancerDescription, &c.LoadBalancer.Spec.Description, false) +} + +func (c *Constructor) Validate() error { + return nil +} + +func (c *Constructor) Result() (interface{}, error) { + return c.LoadBalancer, nil +} + +func newLoadBalancerConstructor(loadbalancer *loadbalancerv1.LoadBalancer) util.Constructor { + return &Constructor{ + LoadBalancer: loadbalancer, + } +} + +func Creator(namespace, name string) util.Constructor { + loadbalancer := &loadbalancerv1.LoadBalancer{ + ObjectMeta: util.NewObjectMeta(namespace, name), + } + return newLoadBalancerConstructor(loadbalancer) +} + +func Updater(loadbalancer *loadbalancerv1.LoadBalancer) util.Constructor { + return newLoadBalancerConstructor(loadbalancer) +} diff --git a/internal/provider/loadbalancer/schema_loadbalancer.go b/internal/provider/loadbalancer/schema_loadbalancer.go new file mode 100644 index 00000000..041175c3 --- /dev/null +++ b/internal/provider/loadbalancer/schema_loadbalancer.go @@ -0,0 +1,100 @@ +package loadbalancer + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" + + "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.FieldLoadBalancerDescription: { + Type: schema.TypeString, + Optional: true, + }, + constants.FieldLoadBalancerWorkloadType: { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + constants.LoadBalancerWorkloadTypeVM, + constants.LoadBalancerWorkloadTypeCluster, + }, false), + Description: "Can be `vm` or `cluster`", + }, + constants.FieldLoadBalancerIPAM: { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + constants.LoadBalancerIPAMPool, + constants.LoadBalancerIPAMDHCP, + }, false), + Description: "Where the load balancer gets its IP address from. Can be `dhcp` or `pool`.", + }, + constants.FieldLoadBalancerIPPool: { + Type: schema.TypeString, + Optional: true, + Description: "Which IP pool to get the IP address from.", + }, + constants.SubresourceTypeLoadBalancerListener: { + Type: schema.TypeList, + Required: true, + MinItems: 1, + Description: "", + Elem: &schema.Resource{ + Schema: subresourceSchemaLoadBalancerListener(), + }, + }, + constants.FieldLoadBalancerBackendServerSelector: { + Type: schema.TypeMap, + Optional: true, + Description: "", + }, + constants.SubresourceTypeLoadBalancerHealthCheck: { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Description: "", + Elem: &schema.Resource{ + Schema: subresourceSchemaLoadBalancerHealthCheck(), + }, + }, + } + util.NamespacedSchemaWrap(s, false) + return s +} + +func DataSourceSchema() map[string]*schema.Schema { + return util.DataSourceSchemaWrap(Schema()) +} + +func subresourceSchemaLoadBalancerListener() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldListenerName: { + Type: schema.TypeString, + Optional: true, + }, + constants.FieldListenerPort: { + Type: schema.TypeInt, + Required: true, + Description: "", + }, + constants.FieldListenerProtocol: { + Type: schema.TypeString, + Required: true, + Description: "", + }, + constants.FieldListenerBackendPort: { + Type: schema.TypeInt, + Required: true, + Description: "", + }, + } + return s +} + +func subresourceSchemaLoadBalancerHealthCheck() map[string]*schema.Schema { + s := map[string]*schema.Schema{} + return s +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 0206e983..9a66e3ad 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -13,7 +13,9 @@ import ( "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/ippool" "github.com/harvester/terraform-provider-harvester/internal/provider/keypair" + "github.com/harvester/terraform-provider-harvester/internal/provider/loadbalancer" "github.com/harvester/terraform-provider-harvester/internal/provider/network" "github.com/harvester/terraform-provider-harvester/internal/provider/storageclass" "github.com/harvester/terraform-provider-harvester/internal/provider/virtualmachine" @@ -40,26 +42,30 @@ func Provider() *schema.Provider { }, }, DataSourcesMap: map[string]*schema.Resource{ + constants.ResourceTypeCloudInitSecret: cloudinitsecret.DataSourceCloudInitSecret(), + constants.ResourceTypeClusterNetwork: clusternetwork.DataSourceClusterNetwork(), constants.ResourceTypeImage: image.DataSourceImage(), + constants.ResourceTypeIPPool: ippool.DataSourceIPPool(), constants.ResourceTypeKeyPair: keypair.DataSourceKeypair(), + constants.ResourceTypeLoadBalancer: loadbalancer.DataSourceLoadBalancer(), 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(), + constants.ResourceTypeVirtualMachine: virtualmachine.DataSourceVirtualMachine(), + constants.ResourceTypeVolume: volume.DataSourceVolume(), }, ResourcesMap: map[string]*schema.Resource{ + constants.ResourceTypeCloudInitSecret: cloudinitsecret.ResourceCloudInitSecret(), + constants.ResourceTypeClusterNetwork: clusternetwork.ResourceClusterNetwork(), constants.ResourceTypeImage: image.ResourceImage(), + constants.ResourceTypeIPPool: ippool.ResourceIPPool(), constants.ResourceTypeKeyPair: keypair.ResourceKeypair(), + constants.ResourceTypeLoadBalancer: loadbalancer.ResourceLoadBalancer(), 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(), + constants.ResourceTypeVirtualMachine: virtualmachine.ResourceVirtualMachine(), + constants.ResourceTypeVolume: volume.ResourceVolume(), }, ConfigureContextFunc: providerConfig, } diff --git a/internal/tests/resource_clusternetwork_test.go b/internal/tests/resource_clusternetwork_test.go new file mode 100644 index 00000000..4160b494 --- /dev/null +++ b/internal/tests/resource_clusternetwork_test.go @@ -0,0 +1,15 @@ +package tests + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestClusterNetworkRead(t *testing.T) { + resource.Test(t, resource.TestCase{ + Steps: []resource.TestStep{ + {}, + }, + }) +} diff --git a/pkg/client/client.go b/pkg/client/client.go index e9c159bd..591053bb 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -9,15 +9,18 @@ import ( "k8s.io/client-go/kubernetes" storageclient "k8s.io/client-go/kubernetes/typed/storage/v1" "k8s.io/client-go/rest" + + harvloadbalancerclient "github.com/harvester/harvester-load-balancer/pkg/generated/clientset/versioned" ) type Client struct { - RestConfig *rest.Config - KubeVirtSubresourceClient *rest.RESTClient - KubeClient *kubernetes.Clientset - StorageClassClient *storageclient.StorageV1Client - HarvesterClient *harvclient.Clientset - HarvesterNetworkClient *harvnetworkclient.Clientset + RestConfig *rest.Config + KubeVirtSubresourceClient *rest.RESTClient + KubeClient *kubernetes.Clientset + StorageClassClient *storageclient.StorageV1Client + HarvesterClient *harvclient.Clientset + HarvesterNetworkClient *harvnetworkclient.Clientset + HarvesterLoadbalancerClient *harvloadbalancerclient.Clientset } func NewClient(kubeConfig, kubeContext string) (*Client, error) { @@ -50,12 +53,17 @@ func NewClient(kubeConfig, kubeContext string) (*Client, error) { if err != nil { return nil, err } + harvLoadbalancerClient, err := harvloadbalancerclient.NewForConfig(restConfig) + if err != nil { + return nil, err + } return &Client{ - RestConfig: restConfig, - KubeVirtSubresourceClient: restClient, - KubeClient: kubeClient, - StorageClassClient: storageClassClient, - HarvesterClient: harvClient, - HarvesterNetworkClient: harvNetworkClient, + RestConfig: restConfig, + KubeVirtSubresourceClient: restClient, + KubeClient: kubeClient, + StorageClassClient: storageClassClient, + HarvesterClient: harvClient, + HarvesterNetworkClient: harvNetworkClient, + HarvesterLoadbalancerClient: harvLoadbalancerClient, }, nil } diff --git a/pkg/constants/constants_ippool.go b/pkg/constants/constants_ippool.go new file mode 100644 index 00000000..e2f847bf --- /dev/null +++ b/pkg/constants/constants_ippool.go @@ -0,0 +1,32 @@ +package constants + +const ( + ResourceTypeIPPool = "harvester_ippool" + + FieldIPPoolDescription = "description" +) + +const ( + SubresourceTypeIPPoolRange = "range" + + FieldRangeStart = "start" + FieldRangeEnd = "end" + FieldRangeSubnet = "subnet" + FieldRangeGateway = "gateway" +) + +const ( + SubresourceTypeIPPoolSelector = "selector" + + FieldSelectorPriority = "priority" + FieldSelectorNetwork = "network" + FieldSelectorScope = "scope" +) + +const ( + SubresourceTypeIPPoolSelectorScope = "scope" + + FieldScopeProject = "project" + FieldScopeNamespace = "namespace" + FieldScopeGuestCluster = "guest_cluster" +) diff --git a/pkg/constants/constants_loadbalancer.go b/pkg/constants/constants_loadbalancer.go new file mode 100644 index 00000000..d1a46d64 --- /dev/null +++ b/pkg/constants/constants_loadbalancer.go @@ -0,0 +1,40 @@ +package constants + +const ( + ResourceTypeLoadBalancer = "harvester_loadbalancer" + + FieldLoadBalancerDescription = "description" + FieldLoadBalancerWorkloadType = "workload_type" + FieldLoadBalancerIPAM = "ipam" + FieldLoadBalancerIPPool = "ippool" + FieldLoadBalancerBackendServerSelector = "backend_server_selector" +) + +const ( + SubresourceTypeLoadBalancerListener = "listener" + + FieldListenerName = "name" + FieldListenerPort = "port" + FieldListenerProtocol = "protocol" + FieldListenerBackendPort = "backend_port" +) + +const ( + SubresourceTypeLoadBalancerHealthCheck = "healthcheck" + + FieldHealthcheckPort = "port" + FieldHealthcheckSuccessThreshold = "success_threshold" + FieldHealthcheckFailureThreshold = "failure_threshold" + FieldHealthcheckPeriodSeconds = "period_seconds" + FieldHealthcheckTimeoutSeconds = "timeout_seconds" +) + +const ( + LoadBalancerWorkloadTypeVM = "vm" + LoadBalancerWorkloadTypeCluster = "cluster" +) + +const ( + LoadBalancerIPAMPool = "pool" + LoadBalancerIPAMDHCP = "dhcp" +) diff --git a/pkg/importer/resource_ippool_importer.go b/pkg/importer/resource_ippool_importer.go new file mode 100644 index 00000000..b114cdf6 --- /dev/null +++ b/pkg/importer/resource_ippool_importer.go @@ -0,0 +1,20 @@ +package importer + +import ( + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + + "github.com/harvester/terraform-provider-harvester/pkg/constants" + "github.com/harvester/terraform-provider-harvester/pkg/helper" +) + +func ResourceIPPoolStateGetter(obj *loadbalancerv1.IPPool) (*StateGetter, error) { + states := map[string]interface{}{ + constants.FieldCommonName: obj.Name, + } + return &StateGetter{ + ID: helper.BuildID("", obj.Name), + Name: obj.Name, + ResourceType: constants.ResourceTypeIPPool, + States: states, + }, nil +} diff --git a/pkg/importer/resource_loadbalancer_importer.go b/pkg/importer/resource_loadbalancer_importer.go new file mode 100644 index 00000000..71471435 --- /dev/null +++ b/pkg/importer/resource_loadbalancer_importer.go @@ -0,0 +1,21 @@ +package importer + +import ( + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + + "github.com/harvester/terraform-provider-harvester/pkg/constants" + "github.com/harvester/terraform-provider-harvester/pkg/helper" +) + +func ResourceLoadBalancerStateGetter(obj *loadbalancerv1.LoadBalancer) (*StateGetter, error) { + states := map[string]interface{}{ + constants.FieldCommonNamespace: obj.Namespace, + constants.FieldCommonName: obj.Name, + } + return &StateGetter{ + ID: helper.BuildID(obj.Namespace, obj.Name), + Name: obj.Name, + ResourceType: constants.ResourceTypeLoadBalancer, + States: states, + }, nil +} From 2e493a479ac2cbc4da4042a85e13b9f74d70d2c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Wed, 14 Aug 2024 15:36:25 +0200 Subject: [PATCH 2/8] Enable tests Enable tests, add test scaffolding for IPPool resource, speed up compilation Signed-Off-By: Moritz Roehrich --- .../ippool/resource_ippool_constructor.go | 17 +++++ internal/provider/ippool/schema_ippool.go | 2 +- .../resource_loadbalancer_constructor.go | 75 ++++++++++++++++++- internal/tests/resource_ippool_test.go | 40 ++++++++++ pkg/constants/constants_ippool.go | 1 - scripts/build | 4 +- scripts/validate | 6 +- 7 files changed, 136 insertions(+), 9 deletions(-) create mode 100644 internal/tests/resource_ippool_test.go diff --git a/internal/provider/ippool/resource_ippool_constructor.go b/internal/provider/ippool/resource_ippool_constructor.go index fc26644f..54bb516f 100644 --- a/internal/provider/ippool/resource_ippool_constructor.go +++ b/internal/provider/ippool/resource_ippool_constructor.go @@ -85,9 +85,26 @@ func (c *Constructor) subresourceIPPoolSelectorParser(data interface{}) error { priority := uint32(ippoolSelector[constants.FieldSelectorPriority].(int)) network := ippoolSelector[constants.FieldSelectorNetwork].(string) + scopesData := ippoolSelector[constants.SubresourceTypeIPPoolSelectorScope].([]interface{}) + + scopes := []loadbalancerv1.Tuple{} + for _, scopeData := range scopesData { + scope := scopeData.(map[string]interface{}) + scopeProject := scope[constants.FieldScopeProject].(string) + scopeNamespace := scope[constants.FieldScopeNamespace].(string) + scopeGuestCluster := scope[constants.FieldScopeGuestCluster].(string) + + scopes = append(scopes, loadbalancerv1.Tuple{ + Project: scopeProject, + Namespace: scopeNamespace, + GuestCluster: scopeGuestCluster, + }) + } + c.IPPool.Spec.Selector = loadbalancerv1.Selector{ Priority: priority, Network: network, + Scope: scopes, } return nil } diff --git a/internal/provider/ippool/schema_ippool.go b/internal/provider/ippool/schema_ippool.go index 44a6a67f..4aa0298c 100644 --- a/internal/provider/ippool/schema_ippool.go +++ b/internal/provider/ippool/schema_ippool.go @@ -77,7 +77,7 @@ func subresourceSchemaIPPoolSelector() map[string]*schema.Schema { Optional: true, Description: "Namespace/name of the VM network", }, - constants.FieldSelectorScope: { + constants.SubresourceTypeIPPoolSelectorScope: { Type: schema.TypeList, Optional: true, Description: "Scope of the IP pool", diff --git a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go index 16300c58..4f105a17 100644 --- a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go +++ b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go @@ -1,7 +1,10 @@ package loadbalancer import ( + "fmt" + loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + corev1 "k8s.io/api/core/v1" "github.com/harvester/terraform-provider-harvester/internal/util" "github.com/harvester/terraform-provider-harvester/pkg/constants" @@ -16,10 +19,35 @@ type Constructor struct { } func (c *Constructor) Setup() util.Processors { - return util.NewProcessors(). + processors := util.NewProcessors(). Tags(&c.LoadBalancer.Labels). Description(&c.LoadBalancer.Annotations). String(constants.FieldLoadBalancerDescription, &c.LoadBalancer.Spec.Description, false) + + subresourceProcessors := []util.Processor{ + { + Field: constants.FieldLoadBalancerWorkloadType, + Parser: c.subresourceLoadBalancerWorkloadTypeParser, + Required: false, + }, + { + Field: constants.FieldLoadBalancerIPAM, + Parser: c.subresourceLoadBalancerIPAMParser, + Required: false, + }, + { + Field: constants.SubresourceTypeLoadBalancerListener, + Parser: c.subresourceLoadBalancerListenerParser, + Required: true, + }, + { + Field: constants.SubresourceTypeLoadBalancerHealthCheck, + Parser: c.subresourceLoadBalancerHealthCheckParser, + Required: false, + }, + } + + return append(processors, subresourceProcessors...) } func (c *Constructor) Validate() error { @@ -46,3 +74,48 @@ func Creator(namespace, name string) util.Constructor { func Updater(loadbalancer *loadbalancerv1.LoadBalancer) util.Constructor { return newLoadBalancerConstructor(loadbalancer) } + +func (c *Constructor) subresourceLoadBalancerWorkloadTypeParser(data interface{}) error { + workloadType := data.(string) + + if workloadType != "vm" && workloadType != "cluster" { + return fmt.Errorf("invalid value for workload type: %v", workloadType) + } + + c.LoadBalancer.Spec.WorkloadType = loadbalancerv1.WorkloadType(workloadType) + + return nil +} + +func (c *Constructor) subresourceLoadBalancerIPAMParser(data interface{}) error { + ipam := data.(string) + + if ipam != "dhcp" && ipam != "cluster" { + return fmt.Errorf("invalid value for IPAM: %v", ipam) + } + + c.LoadBalancer.Spec.IPAM = loadbalancerv1.IPAM(ipam) + return nil +} + +func (c *Constructor) subresourceLoadBalancerListenerParser(data interface{}) error { + listener := data.(map[string]interface{}) + + name := listener[constants.FieldListenerName].(string) + port := int32(listener[constants.FieldListenerPort].(int)) + protocol := corev1.Protocol(listener[constants.FieldListenerProtocol].(string)) + backendPort := int32(listener[constants.FieldListenerBackendPort].(int)) + + c.LoadBalancer.Spec.Listeners = append(c.LoadBalancer.Spec.Listeners, loadbalancerv1.Listener{ + Name: name, + Port: port, + Protocol: protocol, + BackendPort: backendPort, + }) + + return nil +} + +func (c *Constructor) subresourceLoadBalancerHealthCheckParser(data interface{}) error { + return nil +} diff --git a/internal/tests/resource_ippool_test.go b/internal/tests/resource_ippool_test.go new file mode 100644 index 00000000..e7d2f130 --- /dev/null +++ b/internal/tests/resource_ippool_test.go @@ -0,0 +1,40 @@ +package tests + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + // loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" +) + +func TestIPPoolBasic(t *testing.T) { + // var ( + // ippool *loadbalancerv1.IPPool + // ctx = context.Background() + // ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: ` +resource harvester_ippool \"test_ippool\" { + name = \"test_ippool\" + + range { + range_start = \"192.168.0.1\" + range_end = \"192.168.0.254\" + range_subnet = \"192.168.0.1/24\" + range_gateway = \"192.168.0.1\" + } +} +`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "name", "test_ippool"), + ), + }, + }, + }) +} diff --git a/pkg/constants/constants_ippool.go b/pkg/constants/constants_ippool.go index e2f847bf..649da435 100644 --- a/pkg/constants/constants_ippool.go +++ b/pkg/constants/constants_ippool.go @@ -20,7 +20,6 @@ const ( FieldSelectorPriority = "priority" FieldSelectorNetwork = "network" - FieldSelectorScope = "scope" ) const ( diff --git a/scripts/build b/scripts/build index 60a461d1..b602f0c6 100755 --- a/scripts/build +++ b/scripts/build @@ -7,5 +7,5 @@ cd $(dirname $0)/.. mkdir -p bin [ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s" -CGO_ENABLED=0 GOARCH=amd64 go build -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-amd64 -CGO_ENABLED=0 GOARCH=arm64 go build -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-arm64 +CGO_ENABLED=0 GOARCH=amd64 go build -cover -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-amd64 +# CGO_ENABLED=0 GOARCH=arm64 go build -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-arm64 diff --git a/scripts/validate b/scripts/validate index ceb63d05..5fdf20fa 100755 --- a/scripts/validate +++ b/scripts/validate @@ -12,10 +12,8 @@ if ! command -v golangci-lint; then exit fi -echo Running validation - -echo Running: golangci-lint +echo Running validation: golangci-lint golangci-lint run --timeout 5m -echo Running: go fmt +echo Running validation: go fmt test -z "$(go fmt ${PACKAGES} | tee /dev/stderr)" From 6f0a3cf84dce6e8a963e27a2cd9b46a12046e359 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Thu, 22 Aug 2024 13:51:30 +0200 Subject: [PATCH 3/8] IPPool and LoadBalancer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit More functionality, especially around the LoadBalancer resource Signed-off-by: Moritz Röhrich --- internal/provider/ippool/resource_ippool.go | 12 ++-- .../ippool/resource_ippool_constructor.go | 2 +- .../loadbalancer/resource_loadbalancer.go | 58 ++++++++++++++++++- .../resource_loadbalancer_constructor.go | 23 +++++++- internal/tests/resource_ippool_test.go | 3 +- internal/tests/resource_loadbalancer_test.go | 38 ++++++++++++ pkg/constants/constants_loadbalancer.go | 10 ++-- scripts/build | 4 +- 8 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 internal/tests/resource_loadbalancer_test.go diff --git a/internal/provider/ippool/resource_ippool.go b/internal/provider/ippool/resource_ippool.go index 08a3096d..bd2659d1 100644 --- a/internal/provider/ippool/resource_ippool.go +++ b/internal/provider/ippool/resource_ippool.go @@ -65,7 +65,8 @@ func resourceIPPoolRead(ctx context.Context, data *schema.ResourceData, meta int ippool, err := c.HarvesterLoadbalancerClient. LoadbalancerV1beta1(). - IPPools().Get(ctx, name, metav1.GetOptions{}) + IPPools(). + Get(ctx, name, metav1.GetOptions{}) if err != nil { return diag.FromErr(err) } @@ -82,7 +83,8 @@ func resourceIPPoolUpdate(ctx context.Context, data *schema.ResourceData, meta i obj, err := c.HarvesterLoadbalancerClient. LoadbalancerV1beta1(). - IPPools().Get(ctx, name, metav1.GetOptions{}) + IPPools(). + Get(ctx, name, metav1.GetOptions{}) if err != nil { return diag.FromErr(err) } @@ -94,7 +96,8 @@ func resourceIPPoolUpdate(ctx context.Context, data *schema.ResourceData, meta i ippool, err := c.HarvesterLoadbalancerClient. LoadbalancerV1beta1(). - IPPools().Update(ctx, toUpdate.(*loadbalancerv1.IPPool), metav1.UpdateOptions{}) + IPPools(). + Update(ctx, toUpdate.(*loadbalancerv1.IPPool), metav1.UpdateOptions{}) if err != nil { return diag.FromErr(err) } @@ -110,7 +113,8 @@ func resourceIPPoolDelete(ctx context.Context, data *schema.ResourceData, meta i err = c.HarvesterLoadbalancerClient. LoadbalancerV1beta1(). - IPPools().Delete(ctx, name, metav1.DeleteOptions{}) + IPPools(). + Delete(ctx, name, metav1.DeleteOptions{}) if err != nil && !apierrors.IsNotFound(err) { return diag.FromErr(err) } diff --git a/internal/provider/ippool/resource_ippool_constructor.go b/internal/provider/ippool/resource_ippool_constructor.go index 54bb516f..c183cf65 100644 --- a/internal/provider/ippool/resource_ippool_constructor.go +++ b/internal/provider/ippool/resource_ippool_constructor.go @@ -82,7 +82,7 @@ func (c *Constructor) subresourceIPPoolRangeParser(data interface{}) error { func (c *Constructor) subresourceIPPoolSelectorParser(data interface{}) error { ippoolSelector := data.(map[string]interface{}) - priority := uint32(ippoolSelector[constants.FieldSelectorPriority].(int)) + priority := ippoolSelector[constants.FieldSelectorPriority].(uint32) network := ippoolSelector[constants.FieldSelectorNetwork].(string) scopesData := ippoolSelector[constants.SubresourceTypeIPPoolSelectorScope].([]interface{}) diff --git a/internal/provider/loadbalancer/resource_loadbalancer.go b/internal/provider/loadbalancer/resource_loadbalancer.go index 6e439c8f..818950f2 100644 --- a/internal/provider/loadbalancer/resource_loadbalancer.go +++ b/internal/provider/loadbalancer/resource_loadbalancer.go @@ -5,6 +5,7 @@ import ( "time" loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" + apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -57,14 +58,67 @@ func resourceLoadBalancerCreate(ctx context.Context, data *schema.ResourceData, } func resourceLoadBalancerRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - return diag.FromErr(nil) + c := meta.(*client.Client) + namespace, name, err := helper.IDParts(data.Id()) + if err != nil { + return diag.FromErr(err) + } + + loadbalancer, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + LoadBalancers(namespace). + Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + + return diag.FromErr(resourceLoadBalancerImport(data, loadbalancer)) } func resourceLoadBalancerUpdate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - return diag.FromErr(nil) + c := meta.(*client.Client) + namespace, name, err := helper.IDParts(data.Id()) + if err != nil { + return diag.FromErr(err) + } + + obj, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + LoadBalancers(namespace). + Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return diag.FromErr(err) + } + + toUpdate, err := util.ResourceConstruct(data, Updater(obj)) + if err != nil { + return diag.FromErr(err) + } + + loadbalancer, err := c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + LoadBalancers(namespace). + Update(ctx, toUpdate.(*loadbalancerv1.LoadBalancer), metav1.UpdateOptions{}) + if err != nil { + return diag.FromErr(err) + } + return diag.FromErr(resourceLoadBalancerImport(data, loadbalancer)) } func resourceLoadBalancerDelete(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { + c := meta.(*client.Client) + namespace, name, err := helper.IDParts(data.Id()) + if err != nil { + return diag.FromErr(err) + } + + err = c.HarvesterLoadbalancerClient. + LoadbalancerV1beta1(). + LoadBalancers(namespace). + Delete(ctx, name, metav1.DeleteOptions{}) + if err != nil && !apierrors.IsNotFound(err) { + return diag.FromErr(err) + } return diag.FromErr(nil) } diff --git a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go index 4f105a17..14516214 100644 --- a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go +++ b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go @@ -72,6 +72,9 @@ func Creator(namespace, name string) util.Constructor { } func Updater(loadbalancer *loadbalancerv1.LoadBalancer) util.Constructor { + loadbalancer.Spec.Listeners = []loadbalancerv1.Listener{} + loadbalancer.Spec.HealthCheck = &loadbalancerv1.HealthCheck{} + return newLoadBalancerConstructor(loadbalancer) } @@ -102,9 +105,9 @@ func (c *Constructor) subresourceLoadBalancerListenerParser(data interface{}) er listener := data.(map[string]interface{}) name := listener[constants.FieldListenerName].(string) - port := int32(listener[constants.FieldListenerPort].(int)) + port := listener[constants.FieldListenerPort].(int32) protocol := corev1.Protocol(listener[constants.FieldListenerProtocol].(string)) - backendPort := int32(listener[constants.FieldListenerBackendPort].(int)) + backendPort := listener[constants.FieldListenerBackendPort].(int32) c.LoadBalancer.Spec.Listeners = append(c.LoadBalancer.Spec.Listeners, loadbalancerv1.Listener{ Name: name, @@ -117,5 +120,21 @@ func (c *Constructor) subresourceLoadBalancerListenerParser(data interface{}) er } func (c *Constructor) subresourceLoadBalancerHealthCheckParser(data interface{}) error { + healthcheck := data.(map[string]interface{}) + + port := healthcheck[constants.FieldHealthCheckPort].(uint) + success := healthcheck[constants.FieldHealthCheckSuccessThreshold].(uint) + failure := healthcheck[constants.FieldHealthCheckFailureThreshold].(uint) + period := healthcheck[constants.FieldHealthCheckPeriodSeconds].(uint) + timeout := healthcheck[constants.FieldHealthCheckTimeoutSeconds].(uint) + + c.LoadBalancer.Spec.HealthCheck = &loadbalancerv1.HealthCheck{ + Port: port, + SuccessThreshold: success, + FailureThreshold: failure, + PeriodSeconds: period, + TimeoutSeconds: timeout, + } + return nil } diff --git a/internal/tests/resource_ippool_test.go b/internal/tests/resource_ippool_test.go index e7d2f130..b6a0c0c8 100644 --- a/internal/tests/resource_ippool_test.go +++ b/internal/tests/resource_ippool_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - // loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" ) @@ -15,7 +14,7 @@ func TestIPPoolBasic(t *testing.T) { // ) resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ { diff --git a/internal/tests/resource_loadbalancer_test.go b/internal/tests/resource_loadbalancer_test.go new file mode 100644 index 00000000..43b23c0c --- /dev/null +++ b/internal/tests/resource_loadbalancer_test.go @@ -0,0 +1,38 @@ +package tests + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + // loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" +) + +func TestLoadBalancerBasic(t *testing.T) { + // var ( + // loadbalancer *loadbalancerv1.LoadBalancer + // ctx = context.Background() + // ) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: ` +resource harvester_loadbalancer \"test_loadbalancer\" { + name = \"test_loadbalancer\" + + listener { + port = 443 + protocol = \"tcp\" + backend_port = 8080 + } +} +`, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "name", "test_loadbalancer"), + ), + }, + }, + }) +} diff --git a/pkg/constants/constants_loadbalancer.go b/pkg/constants/constants_loadbalancer.go index d1a46d64..51fe19e4 100644 --- a/pkg/constants/constants_loadbalancer.go +++ b/pkg/constants/constants_loadbalancer.go @@ -22,11 +22,11 @@ const ( const ( SubresourceTypeLoadBalancerHealthCheck = "healthcheck" - FieldHealthcheckPort = "port" - FieldHealthcheckSuccessThreshold = "success_threshold" - FieldHealthcheckFailureThreshold = "failure_threshold" - FieldHealthcheckPeriodSeconds = "period_seconds" - FieldHealthcheckTimeoutSeconds = "timeout_seconds" + FieldHealthCheckPort = "port" + FieldHealthCheckSuccessThreshold = "success_threshold" + FieldHealthCheckFailureThreshold = "failure_threshold" + FieldHealthCheckPeriodSeconds = "period_seconds" + FieldHealthCheckTimeoutSeconds = "timeout_seconds" ) const ( diff --git a/scripts/build b/scripts/build index b602f0c6..60a461d1 100755 --- a/scripts/build +++ b/scripts/build @@ -7,5 +7,5 @@ cd $(dirname $0)/.. mkdir -p bin [ "$(uname)" != "Darwin" ] && LINKFLAGS="-extldflags -static -s" -CGO_ENABLED=0 GOARCH=amd64 go build -cover -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-amd64 -# CGO_ENABLED=0 GOARCH=arm64 go build -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-arm64 +CGO_ENABLED=0 GOARCH=amd64 go build -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-amd64 +CGO_ENABLED=0 GOARCH=arm64 go build -ldflags "-X main.VERSION=$VERSION $LINKFLAGS" -o bin/terraform-provider-harvester-arm64 From db2aa67a7eb220f8b32b8d7b4bee0d7420edccb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Fri, 23 Aug 2024 12:20:21 +0200 Subject: [PATCH 4/8] Execute tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Moritz Röhrich --- .../harvester_ippool/data-source.tf | 10 +++++++ .../resources/harvester_ippool/resources.tf | 27 +++++++++++++++++++ .../resource_loadbalancer_constructor.go | 4 +-- internal/tests/resource_ippool_test.go | 23 +++++++++------- internal/tests/resource_loadbalancer_test.go | 14 ++++++---- 5 files changed, 62 insertions(+), 16 deletions(-) create mode 100644 examples/data-sources/harvester_ippool/data-source.tf create mode 100644 examples/resources/harvester_ippool/resources.tf diff --git a/examples/data-sources/harvester_ippool/data-source.tf b/examples/data-sources/harvester_ippool/data-source.tf new file mode 100644 index 00000000..f4d225bd --- /dev/null +++ b/examples/data-sources/harvester_ippool/data-source.tf @@ -0,0 +1,10 @@ +data "harvester_ippool" "service_ips" { + name = "service_ips" + + range { + start = "192.168.0.1" + end = "192.168.0.254" + subnet = "192.168.0.1/24" + gateway = "192.168.0.1" + } +} diff --git a/examples/resources/harvester_ippool/resources.tf b/examples/resources/harvester_ippool/resources.tf new file mode 100644 index 00000000..1b82f710 --- /dev/null +++ b/examples/resources/harvester_ippool/resources.tf @@ -0,0 +1,27 @@ +resource "harvester_ippool" "service_ips" { + name = "service_ips" + + range { + start = "10.11.0.1" + end = "10.11.0.254" + subnet = "10.11.0.1/24" + gateway = "10.11.0.1" + } + + range { + start = "10.12.0.1" + end = "10.12.0.254" + subnet = "10.12.0.1/24" + gateway = "10.12.0.1" + } + + selector { + priority = 100 + network = "vm-network" + scope { + project = "services" + namespace = "prod-default" + guest_cluster = "prod-services" + } + } +} diff --git a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go index 14516214..d23b21cf 100644 --- a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go +++ b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go @@ -105,9 +105,9 @@ func (c *Constructor) subresourceLoadBalancerListenerParser(data interface{}) er listener := data.(map[string]interface{}) name := listener[constants.FieldListenerName].(string) - port := listener[constants.FieldListenerPort].(int32) + port := int32(listener[constants.FieldListenerPort].(int)) protocol := corev1.Protocol(listener[constants.FieldListenerProtocol].(string)) - backendPort := listener[constants.FieldListenerBackendPort].(int32) + backendPort := int32(listener[constants.FieldListenerBackendPort].(int)) c.LoadBalancer.Spec.Listeners = append(c.LoadBalancer.Spec.Listeners, loadbalancerv1.Listener{ Name: name, diff --git a/internal/tests/resource_ippool_test.go b/internal/tests/resource_ippool_test.go index b6a0c0c8..0a10c52f 100644 --- a/internal/tests/resource_ippool_test.go +++ b/internal/tests/resource_ippool_test.go @@ -7,7 +7,7 @@ import ( // loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" ) -func TestIPPoolBasic(t *testing.T) { +func TestAccIPPool_basic(t *testing.T) { // var ( // ippool *loadbalancerv1.IPPool // ctx = context.Background() @@ -19,19 +19,24 @@ func TestIPPoolBasic(t *testing.T) { Steps: []resource.TestStep{ { Config: ` -resource harvester_ippool \"test_ippool\" { - name = \"test_ippool\" +resource "harvester_ippool" "test_ippool" { + name = "test-ippool" range { - range_start = \"192.168.0.1\" - range_end = \"192.168.0.254\" - range_subnet = \"192.168.0.1/24\" - range_gateway = \"192.168.0.1\" + start = "192.168.0.1" + end = "192.168.0.254" + subnet = "192.168.0.1/24" + gateway = "192.168.0.1" } } `, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "name", "test_ippool"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "name", "test-ippool"), + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "range.#", "1"), + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "range.0.start", "192.168.0.1"), + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "range.0.end", "192.168.0.254"), + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "range.0.subnet", "192.168.0.1/24"), + resource.TestCheckResourceAttr("harvester_ippool.test_ippool", "range.0.gateway", "192.168.0.1"), ), }, }, diff --git a/internal/tests/resource_loadbalancer_test.go b/internal/tests/resource_loadbalancer_test.go index 43b23c0c..2711c99c 100644 --- a/internal/tests/resource_loadbalancer_test.go +++ b/internal/tests/resource_loadbalancer_test.go @@ -19,18 +19,22 @@ func TestLoadBalancerBasic(t *testing.T) { Steps: []resource.TestStep{ { Config: ` -resource harvester_loadbalancer \"test_loadbalancer\" { - name = \"test_loadbalancer\" +resource "harvester_loadbalancer" "test_loadbalancer" { + name = "test-loadbalancer" listener { port = 443 - protocol = \"tcp\" + protocol = "tcp" backend_port = 8080 } } `, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "name", "test_loadbalancer"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "name", "test-loadbalancer"), + resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "listener.#", "1"), + resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "listener.0.port", "443"), + resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "listener.0.protocol", "tcp"), + resource.TestCheckResourceAttr("harvester_loadbalancer.test_loadbalancer", "listener.0.backend_port", "8080"), ), }, }, From 94884bc1881f281936312a6d9137a33e0f04885b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Fri, 23 Aug 2024 12:20:51 +0200 Subject: [PATCH 5/8] Fix Volume Resource MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix delete procedure for volume resource. Upon deletion, the volume (PVC) does not produce a usable event. Thus watching the K8s resource for state change will never yeild the expected result. As a consequence the Terraform provider will eventually time-out and produce an error. The fix is to watch for a resource state change instead of an event. This will propagate the deletion of the resource appropriately and the Terraform provider will succeed with the deletion. Signed-off-by: Moritz Röhrich --- internal/util/schema.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/util/schema.go b/internal/util/schema.go index 914945e5..ea97680b 100644 --- a/internal/util/schema.go +++ b/internal/util/schema.go @@ -58,7 +58,7 @@ func IsValidName(i interface{}, k string) ([]string, []error) { } if errs := validation.IsDNS1123Subdomain(v); len(errs) > 0 { - return nil, []error{fmt.Errorf("expected %q to not be an kubernetes valid name", k)} + return nil, []error{fmt.Errorf("expected %q to be an valid DNS1123 subdomain", k)} } return nil, nil From 4c7255bf06addddeef70ac4d28d4b198394de591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Fri, 23 Aug 2024 14:48:27 +0200 Subject: [PATCH 6/8] Fix tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix VM Network and Storage Class tests Signed-off-by: Moritz Röhrich --- .../network/resource_network_constructor.go | 9 +++++---- internal/tests/resource_clusternetwork_test.go | 15 --------------- 2 files changed, 5 insertions(+), 19 deletions(-) delete mode 100644 internal/tests/resource_clusternetwork_test.go diff --git a/internal/provider/network/resource_network_constructor.go b/internal/provider/network/resource_network_constructor.go index b702efe7..d05e18cb 100644 --- a/internal/provider/network/resource_network_constructor.go +++ b/internal/provider/network/resource_network_constructor.go @@ -118,11 +118,11 @@ func (c *Constructor) Result() (interface{}, error) { return c.Network, nil } -func newNetworkConstructor(c *client.Client, ctx context.Context, network *nadv1.NetworkAttachmentDefinition) util.Constructor { +func newNetworkConstructor(c *client.Client, ctx context.Context, clusterNetworkName string, network *nadv1.NetworkAttachmentDefinition) util.Constructor { return &Constructor{ Client: c, Context: ctx, - ClusterNetworkName: network.Labels[networkutils.KeyClusterNetworkLabel], + ClusterNetworkName: clusterNetworkName, Network: network, Layer3NetworkConf: &networkutils.Layer3NetworkConf{}, } @@ -133,9 +133,10 @@ func Creator(c *client.Client, ctx context.Context, namespace, name, clusterNetw ObjectMeta: util.NewObjectMeta(namespace, name), } network.Labels[networkutils.KeyClusterNetworkLabel] = clusterNetworkName - return newNetworkConstructor(c, ctx, network) + return newNetworkConstructor(c, ctx, clusterNetworkName, network) } func Updater(c *client.Client, ctx context.Context, network *nadv1.NetworkAttachmentDefinition) util.Constructor { - return newNetworkConstructor(c, ctx, network) + clusterNetworkName := network.Labels[networkutils.KeyClusterNetworkLabel] + return newNetworkConstructor(c, ctx, clusterNetworkName, network) } diff --git a/internal/tests/resource_clusternetwork_test.go b/internal/tests/resource_clusternetwork_test.go deleted file mode 100644 index 4160b494..00000000 --- a/internal/tests/resource_clusternetwork_test.go +++ /dev/null @@ -1,15 +0,0 @@ -package tests - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestClusterNetworkRead(t *testing.T) { - resource.Test(t, resource.TestCase{ - Steps: []resource.TestStep{ - {}, - }, - }) -} From cd9d09447f76d9a2a48ac202bf3ebfc3d7cd9dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Fri, 23 Aug 2024 15:22:47 +0200 Subject: [PATCH 7/8] Add negative tests for IPPool and Loadbalancer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add negative test cases for IPPool and LoadBalancer resources. This ensures that if mandatory parameters are not given, the program exits with an appropriate error. Signed-off-by: Moritz Röhrich --- internal/tests/resource_ippool_test.go | 31 ++++++++++++++++---- internal/tests/resource_loadbalancer_test.go | 31 ++++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/internal/tests/resource_ippool_test.go b/internal/tests/resource_ippool_test.go index 0a10c52f..1891ccfc 100644 --- a/internal/tests/resource_ippool_test.go +++ b/internal/tests/resource_ippool_test.go @@ -1,18 +1,37 @@ package tests import ( + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - // loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" ) -func TestAccIPPool_basic(t *testing.T) { - // var ( - // ippool *loadbalancerv1.IPPool - // ctx = context.Background() - // ) +func TestAccIPPool_invalid(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: ` +resource "harvester_ippool" "test_ippool" { +} +`, + ExpectError: regexp.MustCompile(`The argument "name" is required, but no definition was found.`), + }, + { + Config: ` +resource "harvester_ippool" "test_ippool" { + name = "test-ippool" +} +`, + ExpectError: regexp.MustCompile(`The argument "range" is required, but no definition was found.`), + }, + }, + }) +} +func TestAccIPPool_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, diff --git a/internal/tests/resource_loadbalancer_test.go b/internal/tests/resource_loadbalancer_test.go index 2711c99c..3128c186 100644 --- a/internal/tests/resource_loadbalancer_test.go +++ b/internal/tests/resource_loadbalancer_test.go @@ -1,18 +1,37 @@ package tests import ( + "regexp" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - // loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" ) -func TestLoadBalancerBasic(t *testing.T) { - // var ( - // loadbalancer *loadbalancerv1.LoadBalancer - // ctx = context.Background() - // ) +func TestAccLoadBalancer_invalid(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: ` +resource "harvester_loadbalancer" "test_loadbalancer" { +} +`, + ExpectError: regexp.MustCompile(`The argument "name" is required, but no definition was found.`), + }, + { + Config: ` +resource "harvester_loadbalancer" "test_loadbalancer" { + name = "test-loadbalancer" +} +`, + ExpectError: regexp.MustCompile(`The argument "listener" is required, but no definition was found.`), + }, + }, + }) +} +func TestAccLoadBalancer_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, From 4a845ce486a6a2cb23efc28beb35caf560d56480 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Moritz=20R=C3=B6hrich?= Date: Tue, 3 Sep 2024 16:22:17 +0200 Subject: [PATCH 8/8] Add VM tags test, LoadBalancer test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add acceptance tests for VirtualMachine labels (tags) and LoadBalancer resources. The labeling/tagging mechnism is crucial for these tests to work, since a LoadBalancer is required by the admission webhook to have at least one VM that matches its selectors. Also improve documentation. Signed-off-by: Moritz Röhrich --- docs/resources/virtualmachine.md | 9 +- .../resources/harvester_ippool/resources.tf | 2 +- .../harvester_loadbalancer/resources.tf | 47 ++++++++ .../resource_loadbalancer_constructor.go | 32 ++++++ .../loadbalancer/schema_loadbalancer.go | 47 +++++++- .../virtualmachine/resource_virtualmachine.go | 1 + internal/tests/resource_loadbalancer_test.go | 36 ++++++ .../tests/resource_virtualmachine_test.go | 106 +++++++++++++++--- pkg/constants/constants_loadbalancer.go | 16 ++- 9 files changed, 273 insertions(+), 23 deletions(-) create mode 100644 examples/resources/harvester_loadbalancer/resources.tf diff --git a/docs/resources/virtualmachine.md b/docs/resources/virtualmachine.md index 7f68930a..9256c054 100644 --- a/docs/resources/virtualmachine.md +++ b/docs/resources/virtualmachine.md @@ -211,7 +211,7 @@ resource "harvester_virtualmachine" "opensuse154" { - **secure_boot** (Boolean) EFI must be enabled to use this feature - **ssh_keys** (List of String) - **start** (Boolean, Deprecated) -- **tags** (Map of String) +- **tags** (Map of String) (otherwise known as labels, see [below](#tags)) - **tpm** (Block List, Max: 1) (see [below for nested schema](#nestedblock--tpm)) ### Read-Only @@ -299,6 +299,13 @@ Optional: - **name** (String) just add this field for doc generation + +### Tags / Labels + +Optional, map of strings. The keys of the tags will appear as labels +`tags.harvesterhci.io/${KEY}`. The values of the map will be the +corresponding values of the labels. + ## Import Import is supported using the following syntax: diff --git a/examples/resources/harvester_ippool/resources.tf b/examples/resources/harvester_ippool/resources.tf index 1b82f710..62b16a80 100644 --- a/examples/resources/harvester_ippool/resources.tf +++ b/examples/resources/harvester_ippool/resources.tf @@ -1,5 +1,5 @@ resource "harvester_ippool" "service_ips" { - name = "service_ips" + name = "service-ips" range { start = "10.11.0.1" diff --git a/examples/resources/harvester_loadbalancer/resources.tf b/examples/resources/harvester_loadbalancer/resources.tf new file mode 100644 index 00000000..62e5fb5f --- /dev/null +++ b/examples/resources/harvester_loadbalancer/resources.tf @@ -0,0 +1,47 @@ +resource "harvester_loadbalancer" "service_loadbalancer" { + name = "service-loadbalancer" + + # This ensures correct ordering for the creation of the resources. + # The loadbalancer resource will be rejected by the admission webhook, if not + # at least one virtual machine with labels matching the backend_selector(s) + # already exists. This dependency ordering can be used to create that virtual + # machine with the same Terraform file. + depends_on = [ + harvester_virtualmachine.name + ] + + listener { + port = 443 + protocol = "tcp" + backend_port = 8080 + } + + listener { + port = 80 + protocol = "tcp" + backend_port = 8080 + } + + ipam = "ippool" + ippool = "service-ips" + + workload_type = "vm" + + backend_selector { + key = "app" + values = [ "test" ] + } + + backend_selector { + key = "component" + values = [ "frontend", "ui" ] + } + + healthcheck { + port = 443 + success_threshold = 1 + failure_threshold = 3 + period_seconds = 10 + timeout_seconds = 5 + } +} diff --git a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go index d23b21cf..616322cc 100644 --- a/internal/provider/loadbalancer/resource_loadbalancer_constructor.go +++ b/internal/provider/loadbalancer/resource_loadbalancer_constructor.go @@ -6,6 +6,8 @@ import ( loadbalancerv1 "github.com/harvester/harvester-load-balancer/pkg/apis/loadbalancer.harvesterhci.io/v1beta1" corev1 "k8s.io/api/core/v1" + "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" ) @@ -40,6 +42,11 @@ func (c *Constructor) Setup() util.Processors { Parser: c.subresourceLoadBalancerListenerParser, Required: true, }, + { + Field: constants.SubresourceTypeLoadBalancerBackendSelector, + Parser: c.subresourceLoadBalancerBackendSelectorParser, + Required: false, + }, { Field: constants.SubresourceTypeLoadBalancerHealthCheck, Parser: c.subresourceLoadBalancerHealthCheckParser, @@ -119,6 +126,31 @@ func (c *Constructor) subresourceLoadBalancerListenerParser(data interface{}) er return nil } +func (c *Constructor) subresourceLoadBalancerBackendSelectorParser(data interface{}) error { + backendServerSelector := make(map[string][]string) + + selectorSet := data.(*schema.Set) + + selectors := selectorSet.List() + + for _, selectorData := range selectors { + selector := selectorData.(map[string]interface{}) + + key := selector[constants.FieldBackendSelectorKey].(string) + valuesData := selector[constants.FieldBackendSelectorValues].([]interface{}) + + values := make([]string, 0) + + for _, valueData := range valuesData { + values = append(values, valueData.(string)) + } + + backendServerSelector[key] = values + } + c.LoadBalancer.Spec.BackendServerSelector = backendServerSelector + return nil +} + func (c *Constructor) subresourceLoadBalancerHealthCheckParser(data interface{}) error { healthcheck := data.(map[string]interface{}) diff --git a/internal/provider/loadbalancer/schema_loadbalancer.go b/internal/provider/loadbalancer/schema_loadbalancer.go index 041175c3..cdf4efca 100644 --- a/internal/provider/loadbalancer/schema_loadbalancer.go +++ b/internal/provider/loadbalancer/schema_loadbalancer.go @@ -46,10 +46,13 @@ func Schema() map[string]*schema.Schema { Schema: subresourceSchemaLoadBalancerListener(), }, }, - constants.FieldLoadBalancerBackendServerSelector: { - Type: schema.TypeMap, + constants.SubresourceTypeLoadBalancerBackendSelector: { + Type: schema.TypeSet, Optional: true, Description: "", + Elem: &schema.Resource{ + Schema: subresourceSchemaLoadBalancerBackendSelector(), + }, }, constants.SubresourceTypeLoadBalancerHealthCheck: { Type: schema.TypeList, @@ -94,7 +97,45 @@ func subresourceSchemaLoadBalancerListener() map[string]*schema.Schema { return s } +func subresourceSchemaLoadBalancerBackendSelector() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + constants.FieldBackendSelectorKey: { + Type: schema.TypeString, + Required: true, + }, + constants.FieldBackendSelectorValues: { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + } + return s +} + func subresourceSchemaLoadBalancerHealthCheck() map[string]*schema.Schema { - s := map[string]*schema.Schema{} + s := map[string]*schema.Schema{ + constants.FieldHealthCheckPort: { + Type: schema.TypeInt, + Required: true, + }, + constants.FieldHealthCheckSuccessThreshold: { + Type: schema.TypeInt, + Optional: true, + }, + constants.FieldHealthCheckFailureThreshold: { + Type: schema.TypeInt, + Optional: true, + }, + constants.FieldHealthCheckPeriodSeconds: { + Type: schema.TypeInt, + Optional: true, + }, + constants.FieldHealthCheckTimeoutSeconds: { + Type: schema.TypeInt, + Optional: true, + }, + } return s } diff --git a/internal/provider/virtualmachine/resource_virtualmachine.go b/internal/provider/virtualmachine/resource_virtualmachine.go index 1efd04bc..173e3366 100644 --- a/internal/provider/virtualmachine/resource_virtualmachine.go +++ b/internal/provider/virtualmachine/resource_virtualmachine.go @@ -196,6 +196,7 @@ func resourceVirtualMachineDelete(ctx context.Context, d *schema.ResourceData, m if err != nil { return diag.FromErr(err) } + d.SetId("") return nil } diff --git a/internal/tests/resource_loadbalancer_test.go b/internal/tests/resource_loadbalancer_test.go index 3128c186..d874f8d6 100644 --- a/internal/tests/resource_loadbalancer_test.go +++ b/internal/tests/resource_loadbalancer_test.go @@ -38,14 +38,50 @@ func TestAccLoadBalancer_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: ` +resource "harvester_virtualmachine" "test_vm" { + name = "test-vm" + namespace = "default" + + tags = { + app = "testlb" + } + + cpu = 1 + memory = "1Gi" + machine_type = "q35" + run_strategy = "RerunOnFailure" + + network_interface { + name = "default" + } + + disk { + name = "rootdisk" + type = "disk" + bus = "virtio" + boot_order = 1 + + container_image_name = "kubevirt/fedora-cloud-container-disk-demo:v0.35.0" + } +} + resource "harvester_loadbalancer" "test_loadbalancer" { name = "test-loadbalancer" + depends_on = [ + harvester_virtualmachine.test_vm + ] + listener { port = 443 protocol = "tcp" backend_port = 8080 } + + backend_selector { + key = "tag.harvesterhci.io/app" + values = [ "testlb" ] + } } `, Check: resource.ComposeAggregateTestCheckFunc( diff --git a/internal/tests/resource_virtualmachine_test.go b/internal/tests/resource_virtualmachine_test.go index 2e1e5f2c..70c5366c 100644 --- a/internal/tests/resource_virtualmachine_test.go +++ b/internal/tests/resource_virtualmachine_test.go @@ -199,33 +199,113 @@ resource harvester_virtualmachine "disk_test" { }) } -func testAccVirtualMachineExists(ctx context.Context, n string, vm *kubevirtv1.VirtualMachine) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Resource %s not found. ", n) +func TestAccVirtualMachine_labels(t *testing.T) { + var ( + vm *kubevirtv1.VirtualMachine + ctx = context.Background() + expectedLabels = map[string]string{ + "tag.harvesterhci.io/Foobar": "barfoo", } + ) + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVirtualMachineDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: ` +resource "harvester_virtualmachine" "test-acc-labels" { + name = "test-vm" + namespace = "default" - if rs.Primary.ID == "" { - return fmt.Errorf("Resource %s ID not set. ", n) - } + tags = { + Foobar = "barfoo" + } + + cpu = 1 + memory = "1Gi" - id := rs.Primary.ID - c := testAccProvider.Meta().(*client.Client) + run_strategy = "RerunOnFailure" + machine_type = "q35" + + network_interface { + name = "default" + } - namespace, name, err := helper.IDParts(id) + disk { + name = "rootdisk" + type = "disk" + bus = "virtio" + boot_order = 1 + + container_image_name = "kubevirt/fedora-cloud-container-disk-demo:v0.35.0" + } +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + testAccVirtualMachineExists(ctx, "harvester_virtualmachine.test-acc-labels", vm), + testAccVirtualMachineLabels(ctx, "harvester_virtualmachine.test-acc-labels", expectedLabels), + ), + }, + }, + }) +} + +func testAccVirtualMachineExists(ctx context.Context, n string, vm *kubevirtv1.VirtualMachine) resource.TestCheckFunc { + return func(s *terraform.State) error { + foundVM, err := testAccGetVirtualMachine(ctx, s, n) if err != nil { return err } - foundVM, err := c.HarvesterClient.KubevirtV1().VirtualMachines(namespace).Get(ctx, name, metav1.GetOptions{}) + vm = foundVM + return nil + } +} + +func testAccVirtualMachineLabels(ctx context.Context, n string, labels map[string]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + vm, err := testAccGetVirtualMachine(ctx, s, n) if err != nil { return err } - vm = foundVM + + for key := range labels { + val, ok := vm.Labels[key] + if !ok { + return fmt.Errorf("Label %s not found", key) + } + + if val != labels[key] { + return fmt.Errorf("Label %s contains unexpected value: %s", key, val) + } + } return nil } } +func testAccGetVirtualMachine(ctx context.Context, state *terraform.State, resourceName string) (*kubevirtv1.VirtualMachine, error) { + resource, ok := state.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("Resource not found: %s", resourceName) + } + if resource.Primary.ID == "" { + return nil, fmt.Errorf("Resource ID not set: %s", resourceName) + } + + id := resource.Primary.ID + c := testAccProvider.Meta().(*client.Client) + + namespace, name, err := helper.IDParts(id) + if err != nil { + return nil, err + } + + return c.HarvesterClient. + KubevirtV1(). + VirtualMachines(namespace). + Get(ctx, name, metav1.GetOptions{}) +} + func testAccCheckVirtualMachineDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { for _, rs := range s.RootModule().Resources { diff --git a/pkg/constants/constants_loadbalancer.go b/pkg/constants/constants_loadbalancer.go index 51fe19e4..2be49bc5 100644 --- a/pkg/constants/constants_loadbalancer.go +++ b/pkg/constants/constants_loadbalancer.go @@ -3,11 +3,10 @@ package constants const ( ResourceTypeLoadBalancer = "harvester_loadbalancer" - FieldLoadBalancerDescription = "description" - FieldLoadBalancerWorkloadType = "workload_type" - FieldLoadBalancerIPAM = "ipam" - FieldLoadBalancerIPPool = "ippool" - FieldLoadBalancerBackendServerSelector = "backend_server_selector" + FieldLoadBalancerDescription = "description" + FieldLoadBalancerWorkloadType = "workload_type" + FieldLoadBalancerIPAM = "ipam" + FieldLoadBalancerIPPool = "ippool" ) const ( @@ -29,6 +28,13 @@ const ( FieldHealthCheckTimeoutSeconds = "timeout_seconds" ) +const ( + SubresourceTypeLoadBalancerBackendSelector = "backend_selector" + + FieldBackendSelectorKey = "key" + FieldBackendSelectorValues = "values" +) + const ( LoadBalancerWorkloadTypeVM = "vm" LoadBalancerWorkloadTypeCluster = "cluster"