diff --git a/.changes/unreleased/Feature-20260717-100000.yaml b/.changes/unreleased/Feature-20260717-100000.yaml new file mode 100644 index 00000000..39c0a565 --- /dev/null +++ b/.changes/unreleased/Feature-20260717-100000.yaml @@ -0,0 +1,6 @@ +kind: Feature +body: Add CreateIntegrationKubernetes and UpdateIntegrationKubernetes client methods + along with KubernetesIntegrationInput and a KubernetesIntegration fragment exposing + ExtractDefinition and TransformDefinition, so a Kubernetes integration's ETL config + can be managed via the API. +time: 2026-07-17T10:00:00.000000Z diff --git a/integration.go b/integration.go index 704763f6..4a9c8988 100644 --- a/integration.go +++ b/integration.go @@ -37,6 +37,11 @@ type GoogleCloudIntegrationFragment struct { TagsOverrideOwnership bool `graphql:"tagsOverrideOwnership"` } +type KubernetesIntegrationFragment struct { + ExtractDefinition string `graphql:"extractDefinition"` + TransformDefinition string `graphql:"transformDefinition"` +} + type NewRelicIntegrationFragment struct { BaseUrl string `graphql:"baseUrl"` AccountKey string `graphql:"accountKey"` @@ -51,7 +56,17 @@ type AWSIntegrationInput struct { RegionOverride *[]string `json:"regionOverride,omitempty"` } +type KubernetesIntegrationInput struct { + ExtractDefinition *YAML `json:"extractDefinition,omitempty"` + Name *Nullable[string] `json:"name,omitempty"` + TransformDefinition *YAML `json:"transformDefinition,omitempty"` +} + func (awsIntegrationInput AWSIntegrationInput) GetGraphQLType() string { return "AwsIntegrationInput" } +func (kubernetesIntegrationInput KubernetesIntegrationInput) GetGraphQLType() string { + return "KubernetesIntegrationInput" +} + func (newRelicIntegrationInput NewRelicIntegrationInput) GetGraphQLType() string { return "NewRelicIntegrationInput" } @@ -234,6 +249,29 @@ func (client *Client) UpdateIntegrationGCP(identifier string, input GoogleCloudI return &m.Payload.Integration, HandleErrors(err, m.Payload.Errors) } +func (client *Client) CreateIntegrationKubernetes(input KubernetesIntegrationInput) (*Integration, error) { + var m struct { + Payload IntegrationCreatePayload `graphql:"kubernetesIntegrationCreate(input: $input)"` + } + v := PayloadVariables{ + "input": input, + } + err := client.Mutate(&m, v, WithName("KubernetesIntegrationCreate")) + return &m.Payload.Integration, HandleErrors(err, m.Payload.Errors) +} + +func (client *Client) UpdateIntegrationKubernetes(identifier string, input KubernetesIntegrationInput) (*Integration, error) { + var m struct { + Payload IntegrationUpdatePayload `graphql:"kubernetesIntegrationUpdate(integration: $integration input: $input)"` + } + v := PayloadVariables{ + "integration": *NewIdentifier(identifier), + "input": input, + } + err := client.Mutate(&m, v, WithName("KubernetesIntegrationUpdate")) + return &m.Payload.Integration, HandleErrors(err, m.Payload.Errors) +} + func (client *Client) IntegrationReactivate(identifier string) (*Integration, error) { var m struct { Payload IntegrationReactivatePayload `graphql:"integrationReactivate(integration: $integration)"` diff --git a/integration_test.go b/integration_test.go index 02fa1f77..a5d7df75 100644 --- a/integration_test.go +++ b/integration_test.go @@ -159,6 +159,42 @@ func TestCreateGoogleCloudIntegration(t *testing.T) { }, result.Projects) } +func TestCreateKubernetesIntegration(t *testing.T) { + // Arrange + testRequest := autopilot.NewTestRequest( + `mutation KubernetesIntegrationCreate($input:KubernetesIntegrationInput!){kubernetesIntegrationCreate(input: $input){integration{{ template "integration_request" }},errors{message,path}}}`, + `{"input": { "name": "Kubernetes", "extractDefinition": "extractors:\n- external_kind: Deployment\n", "transformDefinition": "transforms:\n- infrastructure_resource: Deployment\n" }}`, + `{"data": { + "kubernetesIntegrationCreate": { + "integration": { + {{ template "id1" }}, + "name": "Kubernetes", + "type": "kubernetes", + "createdAt": "2026-07-17T16:25:29.574450Z", + "installedAt": "2026-07-17T16:25:28.541124Z", + "extractDefinition": "extractors:\n- external_kind: Deployment\n", + "transformDefinition": "transforms:\n- infrastructure_resource: Deployment\n" + }, + "errors": [] + }}}`, + ) + client := BestTestClient(t, "integration/create_kubernetes", testRequest) + extractDefinition := opslevel.YAML("extractors:\n- external_kind: Deployment\n") + transformDefinition := opslevel.YAML("transforms:\n- infrastructure_resource: Deployment\n") + // Act + result, err := client.CreateIntegrationKubernetes(opslevel.KubernetesIntegrationInput{ + Name: opslevel.RefOf("Kubernetes"), + ExtractDefinition: &extractDefinition, + TransformDefinition: &transformDefinition, + }) + // Assert + autopilot.Equals(t, nil, err) + autopilot.Equals(t, id1, result.Id) + autopilot.Equals(t, "Kubernetes", result.Name) + autopilot.Equals(t, "extractors:\n- external_kind: Deployment\n", result.ExtractDefinition) + autopilot.Equals(t, "transforms:\n- infrastructure_resource: Deployment\n", result.TransformDefinition) +} + func TestCreateNewRelicIntegration(t *testing.T) { // Arrange testRequest := autopilot.NewTestRequest( @@ -368,6 +404,42 @@ func TestUpdateGoogleCloudIntegration(t *testing.T) { }, result.Projects) } +func TestUpdateKubernetesIntegration(t *testing.T) { + // Arrange + testRequest := autopilot.NewTestRequest( + `mutation KubernetesIntegrationUpdate($input:KubernetesIntegrationInput!$integration:IdentifierInput!){kubernetesIntegrationUpdate(integration: $integration input: $input){integration{{ template "integration_request" }},errors{message,path}}}`, + `{"integration": { {{ template "id1" }} }, "input": { "name": "Kubernetes Updated", "extractDefinition": "extractors:\n- external_kind: StatefulSet\n", "transformDefinition": "transforms:\n- infrastructure_resource: StatefulSet\n" }}`, + `{"data": { + "kubernetesIntegrationUpdate": { + "integration": { + {{ template "id1" }}, + "name": "Kubernetes Updated", + "type": "kubernetes", + "createdAt": "2026-07-17T16:25:29.574450Z", + "installedAt": "2026-07-17T16:25:28.541124Z", + "extractDefinition": "extractors:\n- external_kind: StatefulSet\n", + "transformDefinition": "transforms:\n- infrastructure_resource: StatefulSet\n" + }, + "errors": [] + }}}`, + ) + client := BestTestClient(t, "integration/update_kubernetes", testRequest) + extractDefinition := opslevel.YAML("extractors:\n- external_kind: StatefulSet\n") + transformDefinition := opslevel.YAML("transforms:\n- infrastructure_resource: StatefulSet\n") + // Act + result, err := client.UpdateIntegrationKubernetes(string(id1), opslevel.KubernetesIntegrationInput{ + Name: opslevel.RefOf("Kubernetes Updated"), + ExtractDefinition: &extractDefinition, + TransformDefinition: &transformDefinition, + }) + // Assert + autopilot.Equals(t, nil, err) + autopilot.Equals(t, id1, result.Id) + autopilot.Equals(t, "Kubernetes Updated", result.Name) + autopilot.Equals(t, "extractors:\n- external_kind: StatefulSet\n", result.ExtractDefinition) + autopilot.Equals(t, "transforms:\n- infrastructure_resource: StatefulSet\n", result.TransformDefinition) +} + func TestUpdateNewRelicIntegration(t *testing.T) { // Arrange testRequest := autopilot.NewTestRequest( diff --git a/interfaces.go b/interfaces.go index 020050c1..24f8bb1b 100644 --- a/interfaces.go +++ b/interfaces.go @@ -68,6 +68,7 @@ type Integration struct { AWSIntegrationFragment `graphql:"... on AwsIntegration"` AzureResourcesIntegrationFragment `graphql:"... on AzureResourcesIntegration"` GoogleCloudIntegrationFragment `graphql:"... on GoogleCloudIntegration"` + KubernetesIntegrationFragment `graphql:"... on KubernetesIntegration"` NewRelicIntegrationFragment `graphql:"... on NewRelicIntegration"` } diff --git a/testdata/templates/integrations.tpl b/testdata/templates/integrations.tpl index 3aadb8a6..e7d65a97 100644 --- a/testdata/templates/integrations.tpl +++ b/testdata/templates/integrations.tpl @@ -40,5 +40,5 @@ {{ end }} {{- define "integration_request" -}} -{id,name,type,displayName,webhookUrl,createdAt,installedAt,... on AwsIntegration{iamRole,externalId,awsTagsOverrideOwnership,ownershipTagKeys,regionOverride},... on AzureResourcesIntegration{aliases,ownershipTagKeys,subscriptionId,tagsOverrideOwnership,tenantId},... on GoogleCloudIntegration{aliases,clientEmail,ownershipTagKeys,projects{id,name,url},tagsOverrideOwnership},... on NewRelicIntegration{baseUrl,accountKey}} +{id,name,type,displayName,webhookUrl,createdAt,installedAt,... on AwsIntegration{iamRole,externalId,awsTagsOverrideOwnership,ownershipTagKeys,regionOverride},... on AzureResourcesIntegration{aliases,ownershipTagKeys,subscriptionId,tagsOverrideOwnership,tenantId},... on GoogleCloudIntegration{aliases,clientEmail,ownershipTagKeys,projects{id,name,url},tagsOverrideOwnership},... on KubernetesIntegration{extractDefinition,transformDefinition},... on NewRelicIntegration{baseUrl,accountKey}} {{- end }} diff --git a/yaml.go b/yaml.go new file mode 100644 index 00000000..42b2ddae --- /dev/null +++ b/yaml.go @@ -0,0 +1,6 @@ +package opslevel + +// YAML represents a YAML document serialized as a string for use with the OpsLevel API. +type YAML string + +func (y YAML) GetGraphQLType() string { return "YAML" }