Skip to content

Commit

Permalink
Add kubernetes_resource data source
Browse files Browse the repository at this point in the history
  • Loading branch information
jrhouston committed Feb 11, 2022
1 parent 25fc9b8 commit 4115bde
Show file tree
Hide file tree
Showing 9 changed files with 477 additions and 6 deletions.
207 changes: 207 additions & 0 deletions manifest/provider/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package provider

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
"github.com/hashicorp/terraform-provider-kubernetes/manifest/morph"
"github.com/hashicorp/terraform-provider-kubernetes/manifest/payload"

apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
)

// ReadDataSource function
func (s *RawProviderServer) ReadDataSource(ctx context.Context, req *tfprotov5.ReadDataSourceRequest) (*tfprotov5.ReadDataSourceResponse, error) {
s.logger.Trace("[ReadDataSource][Request]\n%s\n", dump(*req))

resp := &tfprotov5.ReadDataSourceResponse{}

execDiag := s.canExecute()
if len(execDiag) > 0 {
resp.Diagnostics = append(resp.Diagnostics, execDiag...)
return resp, nil
}

rt, err := GetDataSourceType(req.TypeName)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to determine data source type",
Detail: err.Error(),
})
return resp, nil
}

config, err := req.Config.Unmarshal(rt)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to unmarshal data source configuration",
Detail: err.Error(),
})
return resp, nil
}

var dsConfig map[string]tftypes.Value
err = config.As(&dsConfig)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to extract attributes from data source configuration",
Detail: err.Error(),
})
return resp, nil
}

rm, err := s.getRestMapper()
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to get RESTMapper client",
Detail: err.Error(),
})
return resp, nil
}

client, err := s.getDynamicClient()
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "failed to get Dynamic client",
Detail: err.Error(),
})
return resp, nil
}

var apiVersion, kind string
dsConfig["api_version"].As(&apiVersion)
dsConfig["kind"].As(&kind)

gvr, err := getGVR(apiVersion, kind, rm)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to determine resource GroupVersion",
Detail: err.Error(),
})
return resp, nil
}

gvk := gvr.GroupVersion().WithKind(kind)
ns, err := IsResourceNamespaced(gvk, rm)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed determine if resource is namespaced",
Detail: err.Error(),
})
return resp, nil
}
rcl := client.Resource(gvr)

objectType, err := s.TFTypeFromOpenAPI(ctx, gvk, false)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to save resource state",
Detail: err.Error(),
})
return resp, nil
}

var metadataBlock []tftypes.Value
dsConfig["metadata"].As(&metadataBlock)

var metadata map[string]tftypes.Value
metadataBlock[0].As(&metadata)

var name string
metadata["name"].As(&name)

var res *unstructured.Unstructured
if ns {
var namespace string
metadata["namespace"].As(&namespace)
if namespace == "" {
namespace = "default"
}
res, err = rcl.Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
} else {
res, err = rcl.Get(ctx, name, metav1.GetOptions{})
}
if err != nil {
if apierrors.IsNotFound(err) {
return resp, nil
}
d := tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: fmt.Sprintf("Failed to get data source"),
Detail: err.Error(),
}
resp.Diagnostics = append(resp.Diagnostics, &d)
return resp, nil
}

fo := RemoveServerSideFields(res.Object)
nobj, err := payload.ToTFValue(fo, objectType, tftypes.NewAttributePath())
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to convert API response to Terraform value type",
Detail: err.Error(),
})
return resp, nil
}

nobj, err = morph.DeepUnknown(objectType, nobj, tftypes.NewAttributePath())
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to save resource state",
Detail: err.Error(),
})
return resp, nil
}
rawState := make(map[string]tftypes.Value)
err = config.As(&rawState)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to save resource state",
Detail: err.Error(),
})
return resp, nil
}
rawState["object"] = morph.UnknownToNull(nobj)

v := tftypes.NewValue(rt, rawState)
state, err := tfprotov5.NewDynamicValue(v.Type(), v)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to save resource state",
Detail: err.Error(),
})
return resp, nil
}
resp.State = &state
return resp, nil
}

func getGVR(apiVersion, kind string, m meta.RESTMapper) (schema.GroupVersionResource, error) {
gv, err := schema.ParseGroupVersion(apiVersion)
if err != nil {
return schema.GroupVersionResource{}, err
}
mapping, err := m.RESTMapping(gv.WithKind(kind).GroupKind(), gv.Version)
if err != nil {
return schema.GroupVersionResource{}, err
}
return mapping.Resource, err
}
6 changes: 4 additions & 2 deletions manifest/provider/getproviderschema.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ func (s *RawProviderServer) GetProviderSchema(ctx context.Context, req *tfprotov
cfgSchema := GetProviderConfigSchema()

resSchema := GetProviderResourceSchema()
dsSchema := GetProviderDataSourceSchema()

return &tfprotov5.GetProviderSchemaResponse{
Provider: cfgSchema,
ResourceSchemas: resSchema,
Provider: cfgSchema,
ResourceSchemas: resSchema,
DataSourceSchemas: dsSchema,
}, nil
}
67 changes: 67 additions & 0 deletions manifest/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ func GetResourceType(name string) (tftypes.Type, error) {
return GetObjectTypeFromSchema(rsch), nil
}

// GetDataSourceType returns the tftypes.Type of a datasource of type 'name'
func GetDataSourceType(name string) (tftypes.Type, error) {
sch := GetProviderDataSourceSchema()
rsch, ok := sch[name]
if !ok {
return tftypes.DynamicPseudoType, fmt.Errorf("unknown data source %q: cannot find schema", name)
}
return GetObjectTypeFromSchema(rsch), nil
}

// GetProviderResourceSchema contains the definitions of all supported resources
func GetProviderResourceSchema() map[string]*tfprotov5.Schema {
waitForType := tftypes.Object{
Expand Down Expand Up @@ -147,3 +157,60 @@ func GetProviderResourceSchema() map[string]*tfprotov5.Schema {
},
}
}

// GetProviderDataSourceSchema contains the definitions of all supported data sources
func GetProviderDataSourceSchema() map[string]*tfprotov5.Schema {
return map[string]*tfprotov5.Schema{
"kubernetes_resource": {
Version: 1,
Block: &tfprotov5.SchemaBlock{
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "api_version",
Type: tftypes.String,
Required: true,
Description: "The resource apiVersion.",
},
{
Name: "kind",
Type: tftypes.String,
Required: true,
Description: "The resource kind.",
},
{
Name: "object",
Type: tftypes.DynamicPseudoType,
Optional: true,
Computed: true,
Description: "The response from the API server.",
},
},
BlockTypes: []*tfprotov5.SchemaNestedBlock{
{
TypeName: "metadata",
Nesting: tfprotov5.SchemaNestedBlockNestingModeList,
MinItems: 1,
MaxItems: 1,
Block: &tfprotov5.SchemaBlock{
Description: "Metadata for the resource",
Attributes: []*tfprotov5.SchemaAttribute{
{
Name: "name",
Type: tftypes.String,
Required: true,
Description: "The resource name.",
},
{
Name: "namespace",
Type: tftypes.String,
Optional: true,
Description: "The resource namespace.",
},
},
},
},
},
},
},
}
}
31 changes: 27 additions & 4 deletions manifest/provider/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,34 @@ func (s *RawProviderServer) ValidateDataSourceConfig(ctx context.Context, req *t
return resp, nil
}

// ReadDataSource function
func (s *RawProviderServer) ReadDataSource(ctx context.Context, req *tfprotov5.ReadDataSourceRequest) (*tfprotov5.ReadDataSourceResponse, error) {
s.logger.Trace("[ReadDataSource][Request]\n%s\n", dump(*req))
// UpgradeResourceState isn't really useful in this provider, but we have to loop the state back through to keep Terraform happy.
func (s *RawProviderServer) UpgradeResourceState(ctx context.Context, req *tfprotov5.UpgradeResourceStateRequest) (*tfprotov5.UpgradeResourceStateResponse, error) {
resp := &tfprotov5.UpgradeResourceStateResponse{}
resp.Diagnostics = []*tfprotov5.Diagnostic{}

return nil, status.Errorf(codes.Unimplemented, "method ReadDataSource not implemented")
sch := GetProviderResourceSchema()
rt := GetObjectTypeFromSchema(sch[req.TypeName])

rv, err := req.RawState.Unmarshal(rt)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to decode old state during upgrade",
Detail: err.Error(),
})
return resp, nil
}
us, err := tfprotov5.NewDynamicValue(rt, rv)
if err != nil {
resp.Diagnostics = append(resp.Diagnostics, &tfprotov5.Diagnostic{
Severity: tfprotov5.DiagnosticSeverityError,
Summary: "Failed to encode new state during upgrade",
Detail: err.Error(),
})
}
resp.UpgradedState = &us

return resp, nil
}

// StopProvider function
Expand Down
Loading

0 comments on commit 4115bde

Please sign in to comment.