Skip to content

Commit

Permalink
add opslevel_service_dependencies datasource (#429)
Browse files Browse the repository at this point in the history
* add opslevel_service_dependencies datasource

* add dependents to service dependencies datasource

* Apply suggestions from code review

Co-authored-by: Taimoor Ahmad <taimoor@opslevel.com>

* code cleanup and azure fix from upstream update

* add example for opslevel_service_dependencies datasource

---------

Co-authored-by: Taimoor Ahmad <taimoor@opslevel.com>
  • Loading branch information
davidbloss and Taimoor Ahmad authored Aug 9, 2024
1 parent 1ec246a commit 87183cb
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .changes/unreleased/Feature-20240808-161107.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Feature
body: add opslevel_service_dependencies datasource
time: 2024-08-08T16:11:07.119526-05:00
15 changes: 15 additions & 0 deletions examples/data-sources/opslevel_service_dependencies/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
data "opslevel_service" "foo" {
alias = "foo"
}

data "opslevel_service" "bar" {
id = "Z2lkOi8vb3BzbGV2ZWwvU2VydmljZS84Njcw"
}

data "opslevel_service_dependencies" "by_alias" {
service = data.opslevel_service.foo.alias
}

data "opslevel_service_dependencies" "by_id" {
service = data.opslevel_service.bar.id
}
175 changes: 175 additions & 0 deletions opslevel/datasource_opslevel_service_dependencies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package opslevel

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/opslevel/opslevel-go/v2024"
)

// Ensure ServiceDataSource implements DataSourceWithConfigure interface
var _ datasource.DataSourceWithConfigure = &ServiceDataSource{}

func NewServiceDependenciesDataSource() datasource.DataSource {
return &ServiceDependenciesDataSource{}
}

// ServiceDataSource manages a Service data source.
type ServiceDependenciesDataSource struct {
CommonDataSourceClient
}

// ServiceDependenciesModel describes the data source data model.
type ServiceDependenciesModel struct {
Dependents []dependentsModel `tfsdk:"dependents"`
Dependencies []dependenciesModel `tfsdk:"dependencies"`
Service types.String `tfsdk:"service"`
}

type dependentsModel struct {
Id types.String `tfsdk:"id"`
Locked types.Bool `tfsdk:"locked"`
Notes types.String `tfsdk:"notes"`
}

type dependenciesModel struct {
Id types.String `tfsdk:"id"`
Locked types.Bool `tfsdk:"locked"`
Notes types.String `tfsdk:"notes"`
}

func NewServiceDependenciesModel(serviceIdentifier string, dependents []dependentsModel, dependencies []dependenciesModel) ServiceDependenciesModel {
return ServiceDependenciesModel{
Dependents: dependents,
Dependencies: dependencies,
Service: types.StringValue(serviceIdentifier),
}
}

func (d *ServiceDependenciesDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_service_dependencies"
}

var depsAttrs = map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: "The ID of the service dependency.",
Computed: true,
},
"locked": schema.BoolAttribute{
Description: "Is the dependency locked by a service config?",
Computed: true,
},
"notes": schema.StringAttribute{
Description: "Notes for service dependency.",
Optional: true,
},
}

func (d *ServiceDependenciesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Service Dependencies data source",

Attributes: map[string]schema.Attribute{
"dependents": schema.ListNestedAttribute{
Description: "List of Service Dependents of a service",
NestedObject: schema.NestedAttributeObject{
Attributes: depsAttrs,
},
Computed: true,
},
"dependencies": schema.ListNestedAttribute{
Description: "List of Service Dependencies of a service",
NestedObject: schema.NestedAttributeObject{
Attributes: depsAttrs,
},
Computed: true,
},
"service": schema.StringAttribute{
Description: "The ID or alias of the service with the dependency.",
Required: true,
},
},
}
}

func (d *ServiceDependenciesDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var (
err error
service *opslevel.Service
serviceIdentifier string
)

// Read Terraform configuration data into the model
resp.Diagnostics.Append(req.Config.GetAttribute(ctx, path.Root("service"), &serviceIdentifier)...)
if resp.Diagnostics.HasError() {
return
}

// Retrieve Service
if opslevel.IsID(serviceIdentifier) {
service, err = d.client.GetService(opslevel.ID(serviceIdentifier))
} else {
service, err = d.client.GetServiceWithAlias(serviceIdentifier)
}
if err != nil || service == nil {
resp.Diagnostics.AddError("opslevel client error", fmt.Sprintf("Unable to read service, got error: %s", err))
return
}

dependenciesModel, err := getDependenciesModelOfService(d.client, service)
if err != nil {
resp.Diagnostics.AddError("opslevel client error", fmt.Sprintf("Unable to get dependencies for service, got error: %s", err))
return
}

dependentsModel, err := getDependentsModelOfService(d.client, service)
if err != nil {
resp.Diagnostics.AddError("opslevel client error", fmt.Sprintf("Unable to get dependents for service, got error: %s", err))
return
}

stateModel := NewServiceDependenciesModel(serviceIdentifier, dependentsModel, dependenciesModel)

// Save data into Terraform state
tflog.Trace(ctx, "read an OpsLevel Service Dependencies data source")
resp.Diagnostics.Append(resp.State.Set(ctx, &stateModel)...)
}

func getDependenciesModelOfService(client *opslevel.Client, service *opslevel.Service) ([]dependenciesModel, error) {
dependencies := []dependenciesModel{}
svcDependencies, err := service.GetDependencies(client, nil)
if err != nil || svcDependencies == nil {
return dependencies, err
}

for _, svcDependency := range svcDependencies.Edges {
dependencies = append(dependencies, dependenciesModel{
Locked: types.BoolValue(svcDependency.Locked),
Id: ComputedStringValue(string(svcDependency.Id)),
Notes: ComputedStringValue(svcDependency.Notes),
})
}
return dependencies, nil
}

func getDependentsModelOfService(client *opslevel.Client, service *opslevel.Service) ([]dependentsModel, error) {
dependents := []dependentsModel{}
svcDependents, err := service.GetDependents(client, nil)
if err != nil || svcDependents == nil {
return dependents, err
}

for _, svcDependent := range svcDependents.Edges {
dependents = append(dependents, dependentsModel{
Locked: types.BoolValue(svcDependent.Locked),
Id: ComputedStringValue(string(svcDependent.Id)),
Notes: ComputedStringValue(svcDependent.Notes),
})
}
return dependents, nil
}
1 change: 1 addition & 0 deletions opslevel/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func (p *OpslevelProvider) DataSources(context.Context) []func() datasource.Data
NewScorecardDataSource,
NewScorecardDataSourcesAll,
NewServiceDataSource,
NewServiceDependenciesDataSource,
NewServiceDataSourcesAll,
NewSystemDataSource,
NewSystemDataSourcesAll,
Expand Down
4 changes: 2 additions & 2 deletions opslevel/resource_opslevel_integration_azure_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ type IntegrationAzureResourcesResourceModel struct {

func NewIntegrationAzureResourcesResourceModel(ctx context.Context, azureResourcesIntegration opslevel.Integration, givenModel IntegrationAzureResourcesResourceModel) IntegrationAzureResourcesResourceModel {
resourceModel := IntegrationAzureResourcesResourceModel{
Aliases: OptionalStringListValue(azureResourcesIntegration.Aliases),
Aliases: OptionalStringListValue(azureResourcesIntegration.AzureResourcesIntegrationFragment.Aliases),
ClientId: givenModel.ClientId,
ClientSecret: givenModel.ClientSecret,
CreatedAt: ComputedStringValue(azureResourcesIntegration.CreatedAt.Local().Format(time.RFC850)),
Expand All @@ -68,7 +68,7 @@ func NewIntegrationAzureResourcesResourceModel(ctx context.Context, azureResourc
if givenModel.TagsOverrideOwnership.IsNull() {
resourceModel.TagsOverrideOwnership = types.BoolNull()
} else {
resourceModel.TagsOverrideOwnership = types.BoolValue(azureResourcesIntegration.TagsOverrideOwnership)
resourceModel.TagsOverrideOwnership = types.BoolValue(azureResourcesIntegration.AzureResourcesIntegrationFragment.TagsOverrideOwnership)
}

return resourceModel
Expand Down

0 comments on commit 87183cb

Please sign in to comment.