init
This commit is contained in:
commit
51c743fb2b
17 changed files with 2387 additions and 0 deletions
44
.gitignore
vendored
Normal file
44
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
terraform-provider-edge-connect
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Dependency directories
|
||||
vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# Terraform files
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
.terraform/
|
||||
.terraform.lock.hcl
|
||||
crash.log
|
||||
crash.*.log
|
||||
override.tf
|
||||
override.tf.json
|
||||
*_override.tf
|
||||
*_override.tf.json
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
53
Makefile
Normal file
53
Makefile
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
.PHONY: build install test clean fmt vet
|
||||
|
||||
# Default target
|
||||
default: build
|
||||
|
||||
# Build the provider
|
||||
build:
|
||||
go build -o terraform-provider-edge-connect
|
||||
|
||||
# Install the provider locally
|
||||
install: build
|
||||
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/DevFW-CICD/edge-connect/1.0.0/$$(go env GOOS)_$$(go env GOARCH)
|
||||
cp terraform-provider-edge-connect ~/.terraform.d/plugins/registry.terraform.io/DevFW-CICD/edge-connect/1.0.0/$$(go env GOOS)_$$(go env GOARCH)/
|
||||
|
||||
# Run tests
|
||||
test:
|
||||
go test -v ./...
|
||||
|
||||
# Clean build artifacts
|
||||
clean:
|
||||
rm -f terraform-provider-edge-connect
|
||||
rm -rf .terraform/
|
||||
rm -f .terraform.lock.hcl
|
||||
rm -f terraform.tfstate*
|
||||
|
||||
# Format Go code
|
||||
fmt:
|
||||
go fmt ./...
|
||||
|
||||
# Run go vet
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
# Lint the code
|
||||
lint:
|
||||
golangci-lint run
|
||||
|
||||
# Run all checks
|
||||
check: fmt vet test
|
||||
|
||||
# Build for multiple platforms
|
||||
build-all:
|
||||
GOOS=darwin GOARCH=amd64 go build -o bin/terraform-provider-edge-connect_darwin_amd64
|
||||
GOOS=darwin GOARCH=arm64 go build -o bin/terraform-provider-edge-connect_darwin_arm64
|
||||
GOOS=linux GOARCH=amd64 go build -o bin/terraform-provider-edge-connect_linux_amd64
|
||||
GOOS=linux GOARCH=arm64 go build -o bin/terraform-provider-edge-connect_linux_arm64
|
||||
GOOS=windows GOARCH=amd64 go build -o bin/terraform-provider-edge-connect_windows_amd64.exe
|
||||
|
||||
# Generate documentation
|
||||
docs:
|
||||
@echo "Documentation should be generated using terraform-plugin-docs"
|
||||
@echo "Run: go install github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs@latest"
|
||||
@echo "Then: tfplugindocs generate"
|
||||
251
README.md
Normal file
251
README.md
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
# Terraform Provider for Edge Connect
|
||||
|
||||
This Terraform provider allows you to manage Edge Connect applications and application instances.
|
||||
|
||||
## Features
|
||||
|
||||
- Manage application specifications (`edge-connect_app` resource)
|
||||
- Manage application instances (`edge-connect_appinst` resource)
|
||||
- Query existing applications and instances (data sources)
|
||||
- Support for bearer token and basic authentication
|
||||
- Full CRUD operations for all resources
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Terraform](https://www.terraform.io/downloads.html) >= 1.0
|
||||
- [Go](https://golang.org/doc/install) >= 1.21 (for development)
|
||||
- Access to an Edge Connect API endpoint
|
||||
|
||||
## Installation
|
||||
|
||||
### Using Terraform Registry (Recommended)
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
edge-connect = {
|
||||
source = "DevFW-CICD/edge-connect"
|
||||
version = "~> 1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Local Development
|
||||
|
||||
1. Clone the repository:
|
||||
```bash
|
||||
git clone ssh://git@edp.buildth.ing/DevFW-CICD/terraform-provider-edge-connect.git
|
||||
cd terraform-provider-edge-connect
|
||||
```
|
||||
|
||||
2. Build the provider:
|
||||
```bash
|
||||
go build -o terraform-provider-edge-connect
|
||||
```
|
||||
|
||||
3. Install locally:
|
||||
```bash
|
||||
mkdir -p ~/.terraform.d/plugins/registry.terraform.io/DevFW-CICD/edge-connect/1.0.0/darwin_arm64
|
||||
cp terraform-provider-edge-connect ~/.terraform.d/plugins/registry.terraform.io/DevFW-CICD/edge-connect/1.0.0/darwin_arm64/
|
||||
```
|
||||
|
||||
Note: Adjust the path based on your OS and architecture (e.g., `linux_amd64`, `darwin_amd64`, etc.)
|
||||
|
||||
## Usage
|
||||
|
||||
### Provider Configuration
|
||||
|
||||
```hcl
|
||||
provider "edge-connect" {
|
||||
base_url = "https://edp.buildth.ing"
|
||||
token = var.edge_connect_token
|
||||
}
|
||||
```
|
||||
|
||||
Or using basic authentication:
|
||||
|
||||
```hcl
|
||||
provider "edge-connect" {
|
||||
base_url = "https://edp.buildth.ing"
|
||||
username = var.edge_connect_username
|
||||
password = var.edge_connect_password
|
||||
}
|
||||
```
|
||||
|
||||
Configuration can also be provided via environment variables:
|
||||
- `EDGE_CONNECT_BASE_URL`
|
||||
- `EDGE_CONNECT_TOKEN`
|
||||
- `EDGE_CONNECT_USERNAME`
|
||||
- `EDGE_CONNECT_PASSWORD`
|
||||
|
||||
### Creating an Application
|
||||
|
||||
```hcl
|
||||
resource "edge-connect_app" "example" {
|
||||
region = "EU"
|
||||
organization = "myorg"
|
||||
name = "my-app"
|
||||
version = "1.0.0"
|
||||
|
||||
image_type = "Docker"
|
||||
image_path = "nginx:latest"
|
||||
deployment = "kubernetes"
|
||||
default_flavor = "EU.small"
|
||||
access_ports = "tcp:80,tcp:443"
|
||||
}
|
||||
```
|
||||
|
||||
### Creating an Application Instance
|
||||
|
||||
```hcl
|
||||
resource "edge-connect_appinst" "example" {
|
||||
region = "EU"
|
||||
|
||||
app_organization = edge-connect_app.example.organization
|
||||
app_name = edge-connect_app.example.name
|
||||
app_version = edge-connect_app.example.version
|
||||
cloudlet_organization = "cloudlet-org"
|
||||
cloudlet_name = "edge-cloudlet-1"
|
||||
cluster_organization = "cluster-org"
|
||||
|
||||
flavor = "EU.medium"
|
||||
}
|
||||
```
|
||||
|
||||
### Using Data Sources
|
||||
|
||||
```hcl
|
||||
data "edge-connect_app" "existing" {
|
||||
region = "EU"
|
||||
organization = "myorg"
|
||||
name = "existing-app"
|
||||
version = "2.0.0"
|
||||
}
|
||||
|
||||
output "app_image" {
|
||||
value = data.edge-connect_app.existing.image_path
|
||||
}
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
### `edge-connect_app`
|
||||
|
||||
Manages an Edge Connect application specification.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `region` (Required, Forces new resource) - The region where the app is deployed (e.g., 'EU')
|
||||
- `organization` (Required, Forces new resource) - The organization that owns the app
|
||||
- `name` (Required, Forces new resource) - The name of the application
|
||||
- `version` (Required, Forces new resource) - The version of the application
|
||||
- `image_type` (Required) - The type of image (e.g., 'Docker')
|
||||
- `image_path` (Required) - The path to the container image
|
||||
- `deployment` (Required) - The deployment type (e.g., 'kubernetes')
|
||||
- `default_flavor` (Optional) - The default flavor (e.g., 'EU.small', 'EU.medium', 'EU.big', 'EU.large')
|
||||
- `deployment_manifest` (Optional) - The Kubernetes deployment manifest (YAML)
|
||||
- `access_ports` (Optional) - The access ports in format 'protocol:port' (e.g., 'tcp:80,tcp:443')
|
||||
- `annotations` (Optional) - Annotations for the app
|
||||
|
||||
#### Attributes
|
||||
|
||||
- `id` - The unique identifier (format: region/organization/name/version)
|
||||
- `created_at` - The timestamp when the app was created
|
||||
- `updated_at` - The timestamp when the app was last updated
|
||||
|
||||
### `edge-connect_appinst`
|
||||
|
||||
Manages an Edge Connect application instance.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `region` (Required, Forces new resource) - The region where the app instance is deployed
|
||||
- `app_organization` (Required, Forces new resource) - The organization that owns the app
|
||||
- `app_name` (Required, Forces new resource) - The name of the application
|
||||
- `app_version` (Required, Forces new resource) - The version of the application
|
||||
- `cloudlet_organization` (Required, Forces new resource) - The organization that owns the cloudlet
|
||||
- `cloudlet_name` (Required, Forces new resource) - The name of the cloudlet
|
||||
- `cluster_organization` (Required, Forces new resource) - The organization that owns the cluster
|
||||
- `cloudlet` (Optional) - The cloudlet identifier
|
||||
- `flavor` (Optional) - The flavor for the app instance
|
||||
|
||||
#### Attributes
|
||||
|
||||
- `id` - The unique identifier
|
||||
- `real_cluster_name` - The real cluster name
|
||||
- `state` - The state of the app instance
|
||||
- `runtime_info` - Runtime information for the app instance
|
||||
- `uri` - The URI to access the app instance
|
||||
- `liveness` - The liveness status of the app instance
|
||||
- `power_state` - The power state of the app instance
|
||||
- `created_at` - The timestamp when the app instance was created
|
||||
- `updated_at` - The timestamp when the app instance was last updated
|
||||
|
||||
## Data Sources
|
||||
|
||||
### `data.edge-connect_app`
|
||||
|
||||
Fetches information about an existing Edge Connect application.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `region` (Required) - The region where the app is deployed
|
||||
- `organization` (Required) - The organization that owns the app
|
||||
- `name` (Required) - The name of the application
|
||||
- `version` (Required) - The version of the application
|
||||
|
||||
### `data.edge-connect_appinst`
|
||||
|
||||
Fetches information about an existing Edge Connect application instance.
|
||||
|
||||
#### Arguments
|
||||
|
||||
- `region` (Required) - The region where the app instance is deployed
|
||||
- `app_organization` (Required) - The organization that owns the app
|
||||
- `app_name` (Required) - The name of the application
|
||||
- `app_version` (Required) - The version of the application
|
||||
- `cloudlet_organization` (Required) - The organization that owns the cloudlet
|
||||
- `cloudlet_name` (Required) - The name of the cloudlet
|
||||
- `cluster_organization` (Required) - The organization that owns the cluster
|
||||
|
||||
## Examples
|
||||
|
||||
See the [examples](./examples) directory for complete usage examples.
|
||||
|
||||
## Development
|
||||
|
||||
### Building
|
||||
|
||||
```bash
|
||||
go build -o terraform-provider-edge-connect
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
go test ./...
|
||||
```
|
||||
|
||||
### Running Example
|
||||
|
||||
```bash
|
||||
cd examples
|
||||
terraform init
|
||||
terraform plan
|
||||
terraform apply
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please submit pull requests or open issues on the repository.
|
||||
|
||||
## License
|
||||
|
||||
This provider is distributed under the Mozilla Public License 2.0. See LICENSE for more information.
|
||||
|
||||
## Support
|
||||
|
||||
For issues and questions:
|
||||
- Open an issue on the repository
|
||||
- Contact the DevFW-CICD team at https://edp.buildth.ing
|
||||
136
examples/complete/main.tf
Normal file
136
examples/complete/main.tf
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
edge-connect = {
|
||||
source = "DevFW-CICD/edge-connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "edge-connect" {
|
||||
base_url = "https://edp.buildth.ing"
|
||||
token = var.edge_connect_token
|
||||
}
|
||||
|
||||
variable "edge_connect_token" {
|
||||
description = "Edge Connect API token"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# Create a web application
|
||||
resource "edge-connect_app" "web_app" {
|
||||
region = "EU"
|
||||
organization = "acme-corp"
|
||||
name = "web-frontend"
|
||||
version = "1.0.0"
|
||||
|
||||
image_type = "Docker"
|
||||
image_path = "nginx:alpine"
|
||||
deployment = "kubernetes"
|
||||
default_flavor = "EU.small"
|
||||
access_ports = "tcp:80,tcp:443"
|
||||
|
||||
# Optional Kubernetes deployment manifest
|
||||
deployment_manifest = <<-EOT
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: web-frontend
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: web-frontend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: web-frontend
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web-frontend
|
||||
spec:
|
||||
selector:
|
||||
app: web-frontend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
type: LoadBalancer
|
||||
EOT
|
||||
|
||||
annotations = "team=platform,env=production"
|
||||
}
|
||||
|
||||
# Create an API backend application
|
||||
resource "edge-connect_app" "api_backend" {
|
||||
region = "EU"
|
||||
organization = "acme-corp"
|
||||
name = "api-backend"
|
||||
version = "2.3.1"
|
||||
|
||||
image_type = "Docker"
|
||||
image_path = "acme/api-server:2.3.1"
|
||||
deployment = "kubernetes"
|
||||
default_flavor = "EU.medium"
|
||||
access_ports = "tcp:8080"
|
||||
annotations = "team=backend,env=production"
|
||||
}
|
||||
|
||||
# Deploy the web app to edge cloudlet
|
||||
resource "edge-connect_appinst" "web_instance" {
|
||||
region = "EU"
|
||||
|
||||
app_organization = edge-connect_app.web_app.organization
|
||||
app_name = edge-connect_app.web_app.name
|
||||
app_version = edge-connect_app.web_app.version
|
||||
|
||||
cloudlet_organization = "edge-provider"
|
||||
cloudlet_name = "eu-west-1"
|
||||
cluster_organization = "acme-corp"
|
||||
|
||||
flavor = "EU.medium"
|
||||
}
|
||||
|
||||
# Deploy the API backend to edge cloudlet
|
||||
resource "edge-connect_appinst" "api_instance" {
|
||||
region = "EU"
|
||||
|
||||
app_organization = edge-connect_app.api_backend.organization
|
||||
app_name = edge-connect_app.api_backend.name
|
||||
app_version = edge-connect_app.api_backend.version
|
||||
|
||||
cloudlet_organization = "edge-provider"
|
||||
cloudlet_name = "eu-west-1"
|
||||
cluster_organization = "acme-corp"
|
||||
|
||||
flavor = "EU.large"
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "web_app_uri" {
|
||||
description = "URI to access the web application"
|
||||
value = edge-connect_appinst.web_instance.uri
|
||||
}
|
||||
|
||||
output "web_app_state" {
|
||||
description = "Current state of the web application instance"
|
||||
value = edge-connect_appinst.web_instance.state
|
||||
}
|
||||
|
||||
output "api_backend_uri" {
|
||||
description = "URI to access the API backend"
|
||||
value = edge-connect_appinst.api_instance.uri
|
||||
}
|
||||
|
||||
output "api_backend_state" {
|
||||
description = "Current state of the API backend instance"
|
||||
value = edge-connect_appinst.api_instance.state
|
||||
}
|
||||
92
examples/main.tf
Normal file
92
examples/main.tf
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
terraform {
|
||||
required_providers {
|
||||
edge-connect = {
|
||||
source = "DevFW-CICD/edge-connect"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "edge-connect" {
|
||||
base_url = "https://edp.buildth.ing"
|
||||
token = var.edge_connect_token
|
||||
# Alternatively, use username and password:
|
||||
# username = var.edge_connect_username
|
||||
# password = var.edge_connect_password
|
||||
}
|
||||
|
||||
variable "edge_connect_token" {
|
||||
description = "Edge Connect API token"
|
||||
type = string
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
# Create an application specification
|
||||
resource "edge-connect_app" "example" {
|
||||
region = "EU"
|
||||
organization = "myorg"
|
||||
name = "my-app"
|
||||
version = "1.0.0"
|
||||
|
||||
image_type = "Docker"
|
||||
image_path = "nginx:latest"
|
||||
deployment = "kubernetes"
|
||||
default_flavor = "EU.small"
|
||||
access_ports = "tcp:80,tcp:443"
|
||||
|
||||
annotations = "env=production"
|
||||
}
|
||||
|
||||
# Create an application instance
|
||||
resource "edge-connect_appinst" "example" {
|
||||
region = "EU"
|
||||
|
||||
# Reference to the app
|
||||
app_organization = edge-connect_app.example.organization
|
||||
app_name = edge-connect_app.example.name
|
||||
app_version = edge-connect_app.example.version
|
||||
|
||||
# Cloudlet and cluster configuration
|
||||
cloudlet_organization = "cloudlet-org"
|
||||
cloudlet_name = "edge-cloudlet-1"
|
||||
cluster_organization = "cluster-org"
|
||||
|
||||
# Instance configuration
|
||||
flavor = "EU.medium"
|
||||
}
|
||||
|
||||
# Data source to read an existing app
|
||||
data "edge-connect_app" "existing" {
|
||||
region = "EU"
|
||||
organization = "myorg"
|
||||
name = "existing-app"
|
||||
version = "2.0.0"
|
||||
}
|
||||
|
||||
# Data source to read an existing app instance
|
||||
data "edge-connect_appinst" "existing" {
|
||||
region = "EU"
|
||||
|
||||
app_organization = "myorg"
|
||||
app_name = "existing-app"
|
||||
app_version = "2.0.0"
|
||||
cloudlet_organization = "cloudlet-org"
|
||||
cloudlet_name = "edge-cloudlet-1"
|
||||
cluster_organization = "cluster-org"
|
||||
}
|
||||
|
||||
# Outputs
|
||||
output "app_id" {
|
||||
value = edge-connect_app.example.id
|
||||
}
|
||||
|
||||
output "app_instance_uri" {
|
||||
value = edge-connect_appinst.example.uri
|
||||
}
|
||||
|
||||
output "app_instance_state" {
|
||||
value = edge-connect_appinst.example.state
|
||||
}
|
||||
|
||||
output "existing_app_image" {
|
||||
value = data.edge-connect_app.existing.image_path
|
||||
}
|
||||
32
go.mod
Normal file
32
go.mod
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
module github.com/DevFW-CICD/terraform-provider-edge-connect
|
||||
|
||||
go 1.25.3
|
||||
|
||||
require (
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.1
|
||||
github.com/hashicorp/terraform-plugin-log v0.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.15.0 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-plugin v1.7.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.2 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/oklog/run v1.1.0 // indirect
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
golang.org/x/net v0.43.0 // indirect
|
||||
golang.org/x/sys v0.35.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.9 // indirect
|
||||
)
|
||||
99
go.sum
Normal file
99
go.sum
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
|
||||
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.1 h1:1+zwFm3MEqd/0K3YBB2v9u9DtyYHyEuhVOfeIXbteWA=
|
||||
github.com/hashicorp/terraform-plugin-framework v1.16.1/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y=
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
|
||||
github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
|
||||
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
|
||||
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
|
||||
github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
|
||||
github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
|
||||
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
|
||||
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
|
||||
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
|
||||
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
|
||||
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
|
||||
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
|
||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
|
||||
google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
105
internal/client/app.go
Normal file
105
internal/client/app.go
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CreateApp creates a new application
|
||||
func (c *Client) CreateApp(region string, app App) (*App, error) {
|
||||
req := AppRequest{
|
||||
Region: region,
|
||||
App: app,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/CreateApp", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create app: %w", err)
|
||||
}
|
||||
|
||||
var createdApp App
|
||||
if err := json.Unmarshal(respBody, &createdApp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return &createdApp, nil
|
||||
}
|
||||
|
||||
// GetApp retrieves an application by key
|
||||
func (c *Client) GetApp(region string, app App) (*App, error) {
|
||||
req := ShowAppRequest{
|
||||
Region: region,
|
||||
App: &app,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/ShowApp", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get app: %w", err)
|
||||
}
|
||||
|
||||
var apps []App
|
||||
if err := json.Unmarshal(respBody, &apps); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if len(apps) == 0 {
|
||||
return nil, fmt.Errorf("app not found")
|
||||
}
|
||||
|
||||
return &apps[0], nil
|
||||
}
|
||||
|
||||
// ListApps lists all applications
|
||||
func (c *Client) ListApps(region string, filter *App) ([]App, error) {
|
||||
req := ShowAppRequest{
|
||||
Region: region,
|
||||
App: filter,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/ShowApp", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list apps: %w", err)
|
||||
}
|
||||
|
||||
var apps []App
|
||||
if err := json.Unmarshal(respBody, &apps); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
// UpdateApp updates an existing application
|
||||
func (c *Client) UpdateApp(region string, app App) (*App, error) {
|
||||
req := AppRequest{
|
||||
Region: region,
|
||||
App: app,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/UpdateApp", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update app: %w", err)
|
||||
}
|
||||
|
||||
var updatedApp App
|
||||
if err := json.Unmarshal(respBody, &updatedApp); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return &updatedApp, nil
|
||||
}
|
||||
|
||||
// DeleteApp deletes an application
|
||||
func (c *Client) DeleteApp(region string, app App) error {
|
||||
req := AppRequest{
|
||||
Region: region,
|
||||
App: app,
|
||||
}
|
||||
|
||||
_, err := c.doRequest("POST", "/api/v1/auth/ctrl/DeleteApp", req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete app: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
114
internal/client/appinst.go
Normal file
114
internal/client/appinst.go
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// CreateAppInst creates a new application instance
|
||||
func (c *Client) CreateAppInst(region string, appInst AppInst) (*AppInst, error) {
|
||||
req := AppInstRequest{
|
||||
Region: region,
|
||||
AppInst: appInst,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/CreateAppInst", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create app instance: %w", err)
|
||||
}
|
||||
|
||||
// The API returns an array of messages, but we want the final state
|
||||
// For now, we'll just return the input appInst as created
|
||||
// A more sophisticated implementation would parse the messages
|
||||
var messages []map[string]interface{}
|
||||
if err := json.Unmarshal(respBody, &messages); err != nil {
|
||||
// If it's not an array of messages, try to unmarshal as AppInst
|
||||
var createdAppInst AppInst
|
||||
if err := json.Unmarshal(respBody, &createdAppInst); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
return &createdAppInst, nil
|
||||
}
|
||||
|
||||
// Return the input appInst (creation was successful)
|
||||
return &appInst, nil
|
||||
}
|
||||
|
||||
// GetAppInst retrieves an application instance by key
|
||||
func (c *Client) GetAppInst(region string, appInst AppInst) (*AppInst, error) {
|
||||
req := ShowAppInstRequest{
|
||||
Region: region,
|
||||
AppInst: &appInst,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/ShowAppInst", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get app instance: %w", err)
|
||||
}
|
||||
|
||||
var appInsts []AppInst
|
||||
if err := json.Unmarshal(respBody, &appInsts); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
if len(appInsts) == 0 {
|
||||
return nil, fmt.Errorf("app instance not found")
|
||||
}
|
||||
|
||||
return &appInsts[0], nil
|
||||
}
|
||||
|
||||
// ListAppInsts lists all application instances
|
||||
func (c *Client) ListAppInsts(region string, filter *AppInst) ([]AppInst, error) {
|
||||
req := ShowAppInstRequest{
|
||||
Region: region,
|
||||
AppInst: filter,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/ShowAppInst", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list app instances: %w", err)
|
||||
}
|
||||
|
||||
var appInsts []AppInst
|
||||
if err := json.Unmarshal(respBody, &appInsts); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return appInsts, nil
|
||||
}
|
||||
|
||||
// UpdateAppInst updates an existing application instance
|
||||
func (c *Client) UpdateAppInst(region string, appInst AppInst) (*AppInst, error) {
|
||||
req := AppInstRequest{
|
||||
Region: region,
|
||||
AppInst: appInst,
|
||||
}
|
||||
|
||||
respBody, err := c.doRequest("POST", "/api/v1/auth/ctrl/UpdateAppInst", req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update app instance: %w", err)
|
||||
}
|
||||
|
||||
var updatedAppInst AppInst
|
||||
if err := json.Unmarshal(respBody, &updatedAppInst); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||
}
|
||||
|
||||
return &updatedAppInst, nil
|
||||
}
|
||||
|
||||
// DeleteAppInst deletes an application instance
|
||||
func (c *Client) DeleteAppInst(region string, appInst AppInst) error {
|
||||
req := AppInstRequest{
|
||||
Region: region,
|
||||
AppInst: appInst,
|
||||
}
|
||||
|
||||
_, err := c.doRequest("POST", "/api/v1/auth/ctrl/DeleteAppInst", req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete app instance: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
81
internal/client/client.go
Normal file
81
internal/client/client.go
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client is the API client for Edge Connect
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
HTTPClient *http.Client
|
||||
Token string
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// NewClient creates a new Edge Connect API client
|
||||
func NewClient(baseURL, token, username, password string) *Client {
|
||||
return &Client{
|
||||
BaseURL: baseURL,
|
||||
HTTPClient: &http.Client{
|
||||
Timeout: time.Second * 30,
|
||||
},
|
||||
Token: token,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
// doRequest performs an HTTP request with authentication
|
||||
func (c *Client) doRequest(method, path string, body interface{}) ([]byte, error) {
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
jsonBody, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal request body: %w", err)
|
||||
}
|
||||
reqBody = bytes.NewBuffer(jsonBody)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, c.BaseURL+path, reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
// Add authentication
|
||||
if c.Token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+c.Token)
|
||||
} else if c.Username != "" && c.Password != "" {
|
||||
req.SetBasicAuth(c.Username, c.Password)
|
||||
}
|
||||
|
||||
resp, err := c.HTTPClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBody, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return nil, fmt.Errorf("API request failed with status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
return respBody, nil
|
||||
}
|
||||
|
||||
// HealthCheck performs a health check on the API
|
||||
func (c *Client) HealthCheck() error {
|
||||
_, err := c.doRequest("GET", "/api/v1/", nil)
|
||||
return err
|
||||
}
|
||||
82
internal/client/models.go
Normal file
82
internal/client/models.go
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
package client
|
||||
|
||||
// App represents an application specification
|
||||
type App struct {
|
||||
Key struct {
|
||||
Organization string `json:"organization"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
} `json:"key"`
|
||||
Region string `json:"region,omitempty"`
|
||||
ImageType string `json:"image_type,omitempty"`
|
||||
ImagePath string `json:"image_path,omitempty"`
|
||||
DefaultFlavor string `json:"default_flavor,omitempty"`
|
||||
Deployment string `json:"deployment,omitempty"`
|
||||
DeploymentManifest string `json:"deployment_manifest,omitempty"`
|
||||
AccessPorts string `json:"access_ports,omitempty"`
|
||||
Annotations string `json:"annotations,omitempty"`
|
||||
Configs []Config `json:"configs,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
DeletePrepare bool `json:"delete_prepare,omitempty"`
|
||||
}
|
||||
|
||||
// Config represents a configuration item
|
||||
type Config struct {
|
||||
Kind string `json:"kind,omitempty"`
|
||||
Config string `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
// AppInst represents an application instance
|
||||
type AppInst struct {
|
||||
Key struct {
|
||||
AppKey struct {
|
||||
Organization string `json:"organization"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
} `json:"app_key"`
|
||||
ClusterInstKey struct {
|
||||
CloudletKey struct {
|
||||
Organization string `json:"organization"`
|
||||
Name string `json:"name"`
|
||||
} `json:"cloudlet_key"`
|
||||
Organization string `json:"organization"`
|
||||
} `json:"cluster_inst_key"`
|
||||
} `json:"key"`
|
||||
Cloudlet string `json:"cloudlet,omitempty"`
|
||||
Flavor string `json:"flavor,omitempty"`
|
||||
RealClusterName string `json:"real_cluster_name,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
RuntimeInfo string `json:"runtime_info,omitempty"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
UpdatedAt string `json:"updated_at,omitempty"`
|
||||
Uri string `json:"uri,omitempty"`
|
||||
Liveness string `json:"liveness,omitempty"`
|
||||
PowerState string `json:"power_state,omitempty"`
|
||||
Configs []Config `json:"configs,omitempty"`
|
||||
DeletePrepare bool `json:"delete_prepare,omitempty"`
|
||||
}
|
||||
|
||||
// AppRequest represents a request to manage App resources
|
||||
type AppRequest struct {
|
||||
Region string `json:"region,omitempty"`
|
||||
App App `json:"app"`
|
||||
}
|
||||
|
||||
// AppInstRequest represents a request to manage AppInst resources
|
||||
type AppInstRequest struct {
|
||||
Region string `json:"region,omitempty"`
|
||||
AppInst AppInst `json:"appinst"`
|
||||
}
|
||||
|
||||
// ShowAppRequest represents a request to list/show apps
|
||||
type ShowAppRequest struct {
|
||||
Region string `json:"region,omitempty"`
|
||||
App *App `json:"app,omitempty"`
|
||||
}
|
||||
|
||||
// ShowAppInstRequest represents a request to list/show app instances
|
||||
type ShowAppInstRequest struct {
|
||||
Region string `json:"region,omitempty"`
|
||||
AppInst *AppInst `json:"appinst,omitempty"`
|
||||
}
|
||||
177
internal/provider/app_data_source.go
Normal file
177
internal/provider/app_data_source.go
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/client"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &appDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &appDataSource{}
|
||||
)
|
||||
|
||||
// NewAppDataSource is a helper function to simplify the provider implementation.
|
||||
func NewAppDataSource() datasource.DataSource {
|
||||
return &appDataSource{}
|
||||
}
|
||||
|
||||
// appDataSource is the data source implementation.
|
||||
type appDataSource struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// appDataSourceModel maps the data source schema data.
|
||||
type appDataSourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
Organization types.String `tfsdk:"organization"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Version types.String `tfsdk:"version"`
|
||||
ImageType types.String `tfsdk:"image_type"`
|
||||
ImagePath types.String `tfsdk:"image_path"`
|
||||
DefaultFlavor types.String `tfsdk:"default_flavor"`
|
||||
Deployment types.String `tfsdk:"deployment"`
|
||||
DeploymentManifest types.String `tfsdk:"deployment_manifest"`
|
||||
AccessPorts types.String `tfsdk:"access_ports"`
|
||||
Annotations types.String `tfsdk:"annotations"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (d *appDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_app"
|
||||
}
|
||||
|
||||
// Schema defines the schema for the data source.
|
||||
func (d *appDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Fetches an Edge Connect application specification.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "The unique identifier for the app (format: region/organization/name/version).",
|
||||
Computed: true,
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: "The region where the app is deployed (e.g., 'EU').",
|
||||
Required: true,
|
||||
},
|
||||
"organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the app.",
|
||||
Required: true,
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: "The name of the application.",
|
||||
Required: true,
|
||||
},
|
||||
"version": schema.StringAttribute{
|
||||
Description: "The version of the application.",
|
||||
Required: true,
|
||||
},
|
||||
"image_type": schema.StringAttribute{
|
||||
Description: "The type of image (e.g., 'Docker').",
|
||||
Computed: true,
|
||||
},
|
||||
"image_path": schema.StringAttribute{
|
||||
Description: "The path to the container image.",
|
||||
Computed: true,
|
||||
},
|
||||
"default_flavor": schema.StringAttribute{
|
||||
Description: "The default flavor for the app.",
|
||||
Computed: true,
|
||||
},
|
||||
"deployment": schema.StringAttribute{
|
||||
Description: "The deployment type (e.g., 'kubernetes').",
|
||||
Computed: true,
|
||||
},
|
||||
"deployment_manifest": schema.StringAttribute{
|
||||
Description: "The Kubernetes deployment manifest (YAML).",
|
||||
Computed: true,
|
||||
},
|
||||
"access_ports": schema.StringAttribute{
|
||||
Description: "The access ports in format 'protocol:port'.",
|
||||
Computed: true,
|
||||
},
|
||||
"annotations": schema.StringAttribute{
|
||||
Description: "Annotations for the app.",
|
||||
Computed: true,
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app was created.",
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app was last updated.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the data source.
|
||||
func (d *appDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(*client.Client)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Data Source Configure Type",
|
||||
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
d.client = client
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (d *appDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var config appDataSourceModel
|
||||
diags := req.Config.Get(ctx, &config)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get app from API
|
||||
app := client.App{}
|
||||
app.Key.Organization = config.Organization.ValueString()
|
||||
app.Key.Name = config.Name.ValueString()
|
||||
app.Key.Version = config.Version.ValueString()
|
||||
|
||||
readApp, err := d.client.GetApp(config.Region.ValueString(), app)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error reading app",
|
||||
"Could not read app: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response to data source model
|
||||
config.ID = types.StringValue(fmt.Sprintf("%s/%s/%s/%s",
|
||||
config.Region.ValueString(),
|
||||
readApp.Key.Organization,
|
||||
readApp.Key.Name,
|
||||
readApp.Key.Version))
|
||||
config.ImageType = types.StringValue(readApp.ImageType)
|
||||
config.ImagePath = types.StringValue(readApp.ImagePath)
|
||||
config.DefaultFlavor = types.StringValue(readApp.DefaultFlavor)
|
||||
config.Deployment = types.StringValue(readApp.Deployment)
|
||||
config.DeploymentManifest = types.StringValue(readApp.DeploymentManifest)
|
||||
config.AccessPorts = types.StringValue(readApp.AccessPorts)
|
||||
config.Annotations = types.StringValue(readApp.Annotations)
|
||||
config.CreatedAt = types.StringValue(readApp.CreatedAt)
|
||||
config.UpdatedAt = types.StringValue(readApp.UpdatedAt)
|
||||
|
||||
diags = resp.State.Set(ctx, &config)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
311
internal/provider/app_resource.go
Normal file
311
internal/provider/app_resource.go
Normal file
|
|
@ -0,0 +1,311 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/client"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &appResource{}
|
||||
_ resource.ResourceWithConfigure = &appResource{}
|
||||
_ resource.ResourceWithImportState = &appResource{}
|
||||
)
|
||||
|
||||
// NewAppResource is a helper function to simplify the provider implementation.
|
||||
func NewAppResource() resource.Resource {
|
||||
return &appResource{}
|
||||
}
|
||||
|
||||
// appResource is the resource implementation.
|
||||
type appResource struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// appResourceModel maps the resource schema data.
|
||||
type appResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
Organization types.String `tfsdk:"organization"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Version types.String `tfsdk:"version"`
|
||||
ImageType types.String `tfsdk:"image_type"`
|
||||
ImagePath types.String `tfsdk:"image_path"`
|
||||
DefaultFlavor types.String `tfsdk:"default_flavor"`
|
||||
Deployment types.String `tfsdk:"deployment"`
|
||||
DeploymentManifest types.String `tfsdk:"deployment_manifest"`
|
||||
AccessPorts types.String `tfsdk:"access_ports"`
|
||||
Annotations types.String `tfsdk:"annotations"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
func (r *appResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_app"
|
||||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *appResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages an Edge Connect application specification.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "The unique identifier for the app (format: region/organization/name/version).",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: "The region where the app is deployed (e.g., 'EU').",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the app.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"name": schema.StringAttribute{
|
||||
Description: "The name of the application.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"version": schema.StringAttribute{
|
||||
Description: "The version of the application.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"image_type": schema.StringAttribute{
|
||||
Description: "The type of image (e.g., 'Docker').",
|
||||
Required: true,
|
||||
},
|
||||
"image_path": schema.StringAttribute{
|
||||
Description: "The path to the container image.",
|
||||
Required: true,
|
||||
},
|
||||
"default_flavor": schema.StringAttribute{
|
||||
Description: "The default flavor for the app (e.g., 'EU.small', 'EU.medium', 'EU.big', 'EU.large').",
|
||||
Optional: true,
|
||||
},
|
||||
"deployment": schema.StringAttribute{
|
||||
Description: "The deployment type (e.g., 'kubernetes').",
|
||||
Required: true,
|
||||
},
|
||||
"deployment_manifest": schema.StringAttribute{
|
||||
Description: "The Kubernetes deployment manifest (YAML).",
|
||||
Optional: true,
|
||||
},
|
||||
"access_ports": schema.StringAttribute{
|
||||
Description: "The access ports in format 'protocol:port' (e.g., 'tcp:80,tcp:443').",
|
||||
Optional: true,
|
||||
},
|
||||
"annotations": schema.StringAttribute{
|
||||
Description: "Annotations for the app.",
|
||||
Optional: true,
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app was created.",
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app was last updated.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *appResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(*client.Client)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Resource Configure Type",
|
||||
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
r.client = client
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *appResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan appResourceModel
|
||||
diags := req.Plan.Get(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Create new app
|
||||
app := client.App{
|
||||
Region: plan.Region.ValueString(),
|
||||
ImageType: plan.ImageType.ValueString(),
|
||||
ImagePath: plan.ImagePath.ValueString(),
|
||||
DefaultFlavor: plan.DefaultFlavor.ValueString(),
|
||||
Deployment: plan.Deployment.ValueString(),
|
||||
DeploymentManifest: plan.DeploymentManifest.ValueString(),
|
||||
AccessPorts: plan.AccessPorts.ValueString(),
|
||||
Annotations: plan.Annotations.ValueString(),
|
||||
}
|
||||
app.Key.Organization = plan.Organization.ValueString()
|
||||
app.Key.Name = plan.Name.ValueString()
|
||||
app.Key.Version = plan.Version.ValueString()
|
||||
|
||||
createdApp, err := r.client.CreateApp(plan.Region.ValueString(), app)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error creating app",
|
||||
"Could not create app: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema and populate Computed attribute values
|
||||
plan.ID = types.StringValue(fmt.Sprintf("%s/%s/%s/%s",
|
||||
plan.Region.ValueString(),
|
||||
createdApp.Key.Organization,
|
||||
createdApp.Key.Name,
|
||||
createdApp.Key.Version))
|
||||
plan.CreatedAt = types.StringValue(createdApp.CreatedAt)
|
||||
plan.UpdatedAt = types.StringValue(createdApp.UpdatedAt)
|
||||
|
||||
tflog.Trace(ctx, "created app resource")
|
||||
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *appResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state appResourceModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get app from API
|
||||
app := client.App{}
|
||||
app.Key.Organization = state.Organization.ValueString()
|
||||
app.Key.Name = state.Name.ValueString()
|
||||
app.Key.Version = state.Version.ValueString()
|
||||
|
||||
readApp, err := r.client.GetApp(state.Region.ValueString(), app)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error reading app",
|
||||
"Could not read app: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response to state
|
||||
state.ImageType = types.StringValue(readApp.ImageType)
|
||||
state.ImagePath = types.StringValue(readApp.ImagePath)
|
||||
state.DefaultFlavor = types.StringValue(readApp.DefaultFlavor)
|
||||
state.Deployment = types.StringValue(readApp.Deployment)
|
||||
state.DeploymentManifest = types.StringValue(readApp.DeploymentManifest)
|
||||
state.AccessPorts = types.StringValue(readApp.AccessPorts)
|
||||
state.Annotations = types.StringValue(readApp.Annotations)
|
||||
state.CreatedAt = types.StringValue(readApp.CreatedAt)
|
||||
state.UpdatedAt = types.StringValue(readApp.UpdatedAt)
|
||||
|
||||
diags = resp.State.Set(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *appResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan appResourceModel
|
||||
diags := req.Plan.Get(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Update app
|
||||
app := client.App{
|
||||
Region: plan.Region.ValueString(),
|
||||
ImageType: plan.ImageType.ValueString(),
|
||||
ImagePath: plan.ImagePath.ValueString(),
|
||||
DefaultFlavor: plan.DefaultFlavor.ValueString(),
|
||||
Deployment: plan.Deployment.ValueString(),
|
||||
DeploymentManifest: plan.DeploymentManifest.ValueString(),
|
||||
AccessPorts: plan.AccessPorts.ValueString(),
|
||||
Annotations: plan.Annotations.ValueString(),
|
||||
}
|
||||
app.Key.Organization = plan.Organization.ValueString()
|
||||
app.Key.Name = plan.Name.ValueString()
|
||||
app.Key.Version = plan.Version.ValueString()
|
||||
|
||||
updatedApp, err := r.client.UpdateApp(plan.Region.ValueString(), app)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error updating app",
|
||||
"Could not update app: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Update computed attributes
|
||||
plan.UpdatedAt = types.StringValue(updatedApp.UpdatedAt)
|
||||
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *appResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state appResourceModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete app
|
||||
app := client.App{}
|
||||
app.Key.Organization = state.Organization.ValueString()
|
||||
app.Key.Name = state.Name.ValueString()
|
||||
app.Key.Version = state.Version.ValueString()
|
||||
|
||||
err := r.client.DeleteApp(state.Region.ValueString(), app)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error deleting app",
|
||||
"Could not delete app: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ImportState imports the resource state.
|
||||
func (r *appResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
// Import format: region/organization/name/version
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
204
internal/provider/appinst_data_source.go
Normal file
204
internal/provider/appinst_data_source.go
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/client"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ datasource.DataSource = &appInstDataSource{}
|
||||
_ datasource.DataSourceWithConfigure = &appInstDataSource{}
|
||||
)
|
||||
|
||||
// NewAppInstDataSource is a helper function to simplify the provider implementation.
|
||||
func NewAppInstDataSource() datasource.DataSource {
|
||||
return &appInstDataSource{}
|
||||
}
|
||||
|
||||
// appInstDataSource is the data source implementation.
|
||||
type appInstDataSource struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// appInstDataSourceModel maps the data source schema data.
|
||||
type appInstDataSourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
AppOrganization types.String `tfsdk:"app_organization"`
|
||||
AppName types.String `tfsdk:"app_name"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
CloudletOrganization types.String `tfsdk:"cloudlet_organization"`
|
||||
CloudletName types.String `tfsdk:"cloudlet_name"`
|
||||
ClusterOrganization types.String `tfsdk:"cluster_organization"`
|
||||
Cloudlet types.String `tfsdk:"cloudlet"`
|
||||
Flavor types.String `tfsdk:"flavor"`
|
||||
RealClusterName types.String `tfsdk:"real_cluster_name"`
|
||||
State types.String `tfsdk:"state"`
|
||||
RuntimeInfo types.String `tfsdk:"runtime_info"`
|
||||
Uri types.String `tfsdk:"uri"`
|
||||
Liveness types.String `tfsdk:"liveness"`
|
||||
PowerState types.String `tfsdk:"power_state"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// Metadata returns the data source type name.
|
||||
func (d *appInstDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_appinst"
|
||||
}
|
||||
|
||||
// Schema defines the schema for the data source.
|
||||
func (d *appInstDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Fetches an Edge Connect application instance.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "The unique identifier for the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: "The region where the app instance is deployed (e.g., 'EU').",
|
||||
Required: true,
|
||||
},
|
||||
"app_organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the app.",
|
||||
Required: true,
|
||||
},
|
||||
"app_name": schema.StringAttribute{
|
||||
Description: "The name of the application.",
|
||||
Required: true,
|
||||
},
|
||||
"app_version": schema.StringAttribute{
|
||||
Description: "The version of the application.",
|
||||
Required: true,
|
||||
},
|
||||
"cloudlet_organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the cloudlet.",
|
||||
Required: true,
|
||||
},
|
||||
"cloudlet_name": schema.StringAttribute{
|
||||
Description: "The name of the cloudlet.",
|
||||
Required: true,
|
||||
},
|
||||
"cluster_organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the cluster.",
|
||||
Required: true,
|
||||
},
|
||||
"cloudlet": schema.StringAttribute{
|
||||
Description: "The cloudlet identifier.",
|
||||
Computed: true,
|
||||
},
|
||||
"flavor": schema.StringAttribute{
|
||||
Description: "The flavor for the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"real_cluster_name": schema.StringAttribute{
|
||||
Description: "The real cluster name.",
|
||||
Computed: true,
|
||||
},
|
||||
"state": schema.StringAttribute{
|
||||
Description: "The state of the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"runtime_info": schema.StringAttribute{
|
||||
Description: "Runtime information for the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"uri": schema.StringAttribute{
|
||||
Description: "The URI to access the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"liveness": schema.StringAttribute{
|
||||
Description: "The liveness status of the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"power_state": schema.StringAttribute{
|
||||
Description: "The power state of the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app instance was created.",
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app instance was last updated.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the data source.
|
||||
func (d *appInstDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(*client.Client)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Data Source Configure Type",
|
||||
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
d.client = client
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (d *appInstDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var config appInstDataSourceModel
|
||||
diags := req.Config.Get(ctx, &config)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get app instance from API
|
||||
appInst := client.AppInst{}
|
||||
appInst.Key.AppKey.Organization = config.AppOrganization.ValueString()
|
||||
appInst.Key.AppKey.Name = config.AppName.ValueString()
|
||||
appInst.Key.AppKey.Version = config.AppVersion.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Organization = config.CloudletOrganization.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Name = config.CloudletName.ValueString()
|
||||
appInst.Key.ClusterInstKey.Organization = config.ClusterOrganization.ValueString()
|
||||
|
||||
readAppInst, err := d.client.GetAppInst(config.Region.ValueString(), appInst)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error reading app instance",
|
||||
"Could not read app instance: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response to data source model
|
||||
config.ID = types.StringValue(fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s",
|
||||
config.Region.ValueString(),
|
||||
config.AppOrganization.ValueString(),
|
||||
config.AppName.ValueString(),
|
||||
config.AppVersion.ValueString(),
|
||||
config.CloudletOrganization.ValueString(),
|
||||
config.CloudletName.ValueString(),
|
||||
config.ClusterOrganization.ValueString()))
|
||||
config.Cloudlet = types.StringValue(readAppInst.Cloudlet)
|
||||
config.Flavor = types.StringValue(readAppInst.Flavor)
|
||||
config.RealClusterName = types.StringValue(readAppInst.RealClusterName)
|
||||
config.State = types.StringValue(readAppInst.State)
|
||||
config.RuntimeInfo = types.StringValue(readAppInst.RuntimeInfo)
|
||||
config.Uri = types.StringValue(readAppInst.Uri)
|
||||
config.Liveness = types.StringValue(readAppInst.Liveness)
|
||||
config.PowerState = types.StringValue(readAppInst.PowerState)
|
||||
config.CreatedAt = types.StringValue(readAppInst.CreatedAt)
|
||||
config.UpdatedAt = types.StringValue(readAppInst.UpdatedAt)
|
||||
|
||||
diags = resp.State.Set(ctx, &config)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
351
internal/provider/appinst_resource.go
Normal file
351
internal/provider/appinst_resource.go
Normal file
|
|
@ -0,0 +1,351 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/client"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ resource.Resource = &appInstResource{}
|
||||
_ resource.ResourceWithConfigure = &appInstResource{}
|
||||
_ resource.ResourceWithImportState = &appInstResource{}
|
||||
)
|
||||
|
||||
// NewAppInstResource is a helper function to simplify the provider implementation.
|
||||
func NewAppInstResource() resource.Resource {
|
||||
return &appInstResource{}
|
||||
}
|
||||
|
||||
// appInstResource is the resource implementation.
|
||||
type appInstResource struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// appInstResourceModel maps the resource schema data.
|
||||
type appInstResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Region types.String `tfsdk:"region"`
|
||||
AppOrganization types.String `tfsdk:"app_organization"`
|
||||
AppName types.String `tfsdk:"app_name"`
|
||||
AppVersion types.String `tfsdk:"app_version"`
|
||||
CloudletOrganization types.String `tfsdk:"cloudlet_organization"`
|
||||
CloudletName types.String `tfsdk:"cloudlet_name"`
|
||||
ClusterOrganization types.String `tfsdk:"cluster_organization"`
|
||||
Cloudlet types.String `tfsdk:"cloudlet"`
|
||||
Flavor types.String `tfsdk:"flavor"`
|
||||
RealClusterName types.String `tfsdk:"real_cluster_name"`
|
||||
State types.String `tfsdk:"state"`
|
||||
RuntimeInfo types.String `tfsdk:"runtime_info"`
|
||||
Uri types.String `tfsdk:"uri"`
|
||||
Liveness types.String `tfsdk:"liveness"`
|
||||
PowerState types.String `tfsdk:"power_state"`
|
||||
CreatedAt types.String `tfsdk:"created_at"`
|
||||
UpdatedAt types.String `tfsdk:"updated_at"`
|
||||
}
|
||||
|
||||
// Metadata returns the resource type name.
|
||||
func (r *appInstResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_appinst"
|
||||
}
|
||||
|
||||
// Schema defines the schema for the resource.
|
||||
func (r *appInstResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Manages an Edge Connect application instance.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{
|
||||
Description: "The unique identifier for the app instance.",
|
||||
Computed: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.UseStateForUnknown(),
|
||||
},
|
||||
},
|
||||
"region": schema.StringAttribute{
|
||||
Description: "The region where the app instance is deployed (e.g., 'EU').",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"app_organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the app.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"app_name": schema.StringAttribute{
|
||||
Description: "The name of the application.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"app_version": schema.StringAttribute{
|
||||
Description: "The version of the application.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"cloudlet_organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the cloudlet.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"cloudlet_name": schema.StringAttribute{
|
||||
Description: "The name of the cloudlet.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"cluster_organization": schema.StringAttribute{
|
||||
Description: "The organization that owns the cluster.",
|
||||
Required: true,
|
||||
PlanModifiers: []planmodifier.String{
|
||||
stringplanmodifier.RequiresReplace(),
|
||||
},
|
||||
},
|
||||
"cloudlet": schema.StringAttribute{
|
||||
Description: "The cloudlet identifier.",
|
||||
Optional: true,
|
||||
},
|
||||
"flavor": schema.StringAttribute{
|
||||
Description: "The flavor for the app instance (e.g., 'EU.small', 'EU.medium', 'EU.big', 'EU.large').",
|
||||
Optional: true,
|
||||
},
|
||||
"real_cluster_name": schema.StringAttribute{
|
||||
Description: "The real cluster name.",
|
||||
Computed: true,
|
||||
},
|
||||
"state": schema.StringAttribute{
|
||||
Description: "The state of the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"runtime_info": schema.StringAttribute{
|
||||
Description: "Runtime information for the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"uri": schema.StringAttribute{
|
||||
Description: "The URI to access the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"liveness": schema.StringAttribute{
|
||||
Description: "The liveness status of the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"power_state": schema.StringAttribute{
|
||||
Description: "The power state of the app instance.",
|
||||
Computed: true,
|
||||
},
|
||||
"created_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app instance was created.",
|
||||
Computed: true,
|
||||
},
|
||||
"updated_at": schema.StringAttribute{
|
||||
Description: "The timestamp when the app instance was last updated.",
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure adds the provider configured client to the resource.
|
||||
func (r *appInstResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
||||
if req.ProviderData == nil {
|
||||
return
|
||||
}
|
||||
|
||||
client, ok := req.ProviderData.(*client.Client)
|
||||
if !ok {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unexpected Resource Configure Type",
|
||||
fmt.Sprintf("Expected *client.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
r.client = client
|
||||
}
|
||||
|
||||
// Create creates the resource and sets the initial Terraform state.
|
||||
func (r *appInstResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan appInstResourceModel
|
||||
diags := req.Plan.Get(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Create new app instance
|
||||
appInst := client.AppInst{
|
||||
Cloudlet: plan.Cloudlet.ValueString(),
|
||||
Flavor: plan.Flavor.ValueString(),
|
||||
}
|
||||
appInst.Key.AppKey.Organization = plan.AppOrganization.ValueString()
|
||||
appInst.Key.AppKey.Name = plan.AppName.ValueString()
|
||||
appInst.Key.AppKey.Version = plan.AppVersion.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Organization = plan.CloudletOrganization.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Name = plan.CloudletName.ValueString()
|
||||
appInst.Key.ClusterInstKey.Organization = plan.ClusterOrganization.ValueString()
|
||||
|
||||
createdAppInst, err := r.client.CreateAppInst(plan.Region.ValueString(), appInst)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error creating app instance",
|
||||
"Could not create app instance: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response body to schema and populate Computed attribute values
|
||||
plan.ID = types.StringValue(fmt.Sprintf("%s/%s/%s/%s/%s/%s/%s",
|
||||
plan.Region.ValueString(),
|
||||
plan.AppOrganization.ValueString(),
|
||||
plan.AppName.ValueString(),
|
||||
plan.AppVersion.ValueString(),
|
||||
plan.CloudletOrganization.ValueString(),
|
||||
plan.CloudletName.ValueString(),
|
||||
plan.ClusterOrganization.ValueString()))
|
||||
plan.RealClusterName = types.StringValue(createdAppInst.RealClusterName)
|
||||
plan.State = types.StringValue(createdAppInst.State)
|
||||
plan.RuntimeInfo = types.StringValue(createdAppInst.RuntimeInfo)
|
||||
plan.Uri = types.StringValue(createdAppInst.Uri)
|
||||
plan.Liveness = types.StringValue(createdAppInst.Liveness)
|
||||
plan.PowerState = types.StringValue(createdAppInst.PowerState)
|
||||
plan.CreatedAt = types.StringValue(createdAppInst.CreatedAt)
|
||||
plan.UpdatedAt = types.StringValue(createdAppInst.UpdatedAt)
|
||||
|
||||
tflog.Trace(ctx, "created app instance resource")
|
||||
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
// Read refreshes the Terraform state with the latest data.
|
||||
func (r *appInstResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state appInstResourceModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Get app instance from API
|
||||
appInst := client.AppInst{}
|
||||
appInst.Key.AppKey.Organization = state.AppOrganization.ValueString()
|
||||
appInst.Key.AppKey.Name = state.AppName.ValueString()
|
||||
appInst.Key.AppKey.Version = state.AppVersion.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Organization = state.CloudletOrganization.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Name = state.CloudletName.ValueString()
|
||||
appInst.Key.ClusterInstKey.Organization = state.ClusterOrganization.ValueString()
|
||||
|
||||
readAppInst, err := r.client.GetAppInst(state.Region.ValueString(), appInst)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error reading app instance",
|
||||
"Could not read app instance: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Map response to state
|
||||
state.Cloudlet = types.StringValue(readAppInst.Cloudlet)
|
||||
state.Flavor = types.StringValue(readAppInst.Flavor)
|
||||
state.RealClusterName = types.StringValue(readAppInst.RealClusterName)
|
||||
state.State = types.StringValue(readAppInst.State)
|
||||
state.RuntimeInfo = types.StringValue(readAppInst.RuntimeInfo)
|
||||
state.Uri = types.StringValue(readAppInst.Uri)
|
||||
state.Liveness = types.StringValue(readAppInst.Liveness)
|
||||
state.PowerState = types.StringValue(readAppInst.PowerState)
|
||||
state.CreatedAt = types.StringValue(readAppInst.CreatedAt)
|
||||
state.UpdatedAt = types.StringValue(readAppInst.UpdatedAt)
|
||||
|
||||
diags = resp.State.Set(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
// Update updates the resource and sets the updated Terraform state on success.
|
||||
func (r *appInstResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan appInstResourceModel
|
||||
diags := req.Plan.Get(ctx, &plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Update app instance (only limited fields can be updated)
|
||||
appInst := client.AppInst{
|
||||
Cloudlet: plan.Cloudlet.ValueString(),
|
||||
Flavor: plan.Flavor.ValueString(),
|
||||
}
|
||||
appInst.Key.AppKey.Organization = plan.AppOrganization.ValueString()
|
||||
appInst.Key.AppKey.Name = plan.AppName.ValueString()
|
||||
appInst.Key.AppKey.Version = plan.AppVersion.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Organization = plan.CloudletOrganization.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Name = plan.CloudletName.ValueString()
|
||||
appInst.Key.ClusterInstKey.Organization = plan.ClusterOrganization.ValueString()
|
||||
|
||||
updatedAppInst, err := r.client.UpdateAppInst(plan.Region.ValueString(), appInst)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error updating app instance",
|
||||
"Could not update app instance: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Update computed attributes
|
||||
plan.State = types.StringValue(updatedAppInst.State)
|
||||
plan.UpdatedAt = types.StringValue(updatedAppInst.UpdatedAt)
|
||||
|
||||
diags = resp.State.Set(ctx, plan)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
}
|
||||
|
||||
// Delete deletes the resource and removes the Terraform state on success.
|
||||
func (r *appInstResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state appInstResourceModel
|
||||
diags := req.State.Get(ctx, &state)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete app instance
|
||||
appInst := client.AppInst{}
|
||||
appInst.Key.AppKey.Organization = state.AppOrganization.ValueString()
|
||||
appInst.Key.AppKey.Name = state.AppName.ValueString()
|
||||
appInst.Key.AppKey.Version = state.AppVersion.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Organization = state.CloudletOrganization.ValueString()
|
||||
appInst.Key.ClusterInstKey.CloudletKey.Name = state.CloudletName.ValueString()
|
||||
appInst.Key.ClusterInstKey.Organization = state.ClusterOrganization.ValueString()
|
||||
|
||||
err := r.client.DeleteAppInst(state.Region.ValueString(), appInst)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Error deleting app instance",
|
||||
"Could not delete app instance: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ImportState imports the resource state.
|
||||
func (r *appInstResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
// Import format: region/app_org/app_name/app_version/cloudlet_org/cloudlet_name/cluster_org
|
||||
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
||||
}
|
||||
224
internal/provider/provider.go
Normal file
224
internal/provider/provider.go
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
|
||||
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/client"
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/path"
|
||||
"github.com/hashicorp/terraform-plugin-framework/provider"
|
||||
"github.com/hashicorp/terraform-plugin-framework/provider/schema"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"github.com/hashicorp/terraform-plugin-log/tflog"
|
||||
)
|
||||
|
||||
// Ensure the implementation satisfies the expected interfaces.
|
||||
var (
|
||||
_ provider.Provider = &edgeConnectProvider{}
|
||||
)
|
||||
|
||||
// New is a helper function to simplify provider server and testing implementation.
|
||||
func New(version string) func() provider.Provider {
|
||||
return func() provider.Provider {
|
||||
return &edgeConnectProvider{
|
||||
version: version,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// edgeConnectProvider is the provider implementation.
|
||||
type edgeConnectProvider struct {
|
||||
version string
|
||||
}
|
||||
|
||||
// edgeConnectProviderModel maps provider schema data to a Go type.
|
||||
type edgeConnectProviderModel struct {
|
||||
BaseURL types.String `tfsdk:"base_url"`
|
||||
Token types.String `tfsdk:"token"`
|
||||
Username types.String `tfsdk:"username"`
|
||||
Password types.String `tfsdk:"password"`
|
||||
}
|
||||
|
||||
// Metadata returns the provider type name.
|
||||
func (p *edgeConnectProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
|
||||
resp.TypeName = "edge-connect"
|
||||
resp.Version = p.version
|
||||
}
|
||||
|
||||
// Schema defines the provider-level schema for configuration data.
|
||||
func (p *edgeConnectProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "Interact with Edge Connect API for managing applications and application instances.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"base_url": schema.StringAttribute{
|
||||
Description: "The base URL for the Edge Connect API. May also be provided via EDGE_CONNECT_BASE_URL environment variable.",
|
||||
Optional: true,
|
||||
},
|
||||
"token": schema.StringAttribute{
|
||||
Description: "Bearer token for authentication. May also be provided via EDGE_CONNECT_TOKEN environment variable.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
"username": schema.StringAttribute{
|
||||
Description: "Username for basic authentication. May also be provided via EDGE_CONNECT_USERNAME environment variable.",
|
||||
Optional: true,
|
||||
},
|
||||
"password": schema.StringAttribute{
|
||||
Description: "Password for basic authentication. May also be provided via EDGE_CONNECT_PASSWORD environment variable.",
|
||||
Optional: true,
|
||||
Sensitive: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Configure prepares a Edge Connect API client for data sources and resources.
|
||||
func (p *edgeConnectProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
|
||||
tflog.Info(ctx, "Configuring Edge Connect client")
|
||||
|
||||
// Retrieve provider data from configuration
|
||||
var config edgeConnectProviderModel
|
||||
diags := req.Config.Get(ctx, &config)
|
||||
resp.Diagnostics.Append(diags...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// If practitioner provided a configuration value for any of the
|
||||
// attributes, it must be a known value.
|
||||
if config.BaseURL.IsUnknown() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("base_url"),
|
||||
"Unknown Edge Connect API Base URL",
|
||||
"The provider cannot create the Edge Connect API client as there is an unknown configuration value for the base URL. "+
|
||||
"Either target apply the source of the value first, set the value statically in the configuration, or use the EDGE_CONNECT_BASE_URL environment variable.",
|
||||
)
|
||||
}
|
||||
|
||||
if config.Token.IsUnknown() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("token"),
|
||||
"Unknown Edge Connect API Token",
|
||||
"The provider cannot create the Edge Connect API client as there is an unknown configuration value for the token. "+
|
||||
"Either target apply the source of the value first, set the value statically in the configuration, or use the EDGE_CONNECT_TOKEN environment variable.",
|
||||
)
|
||||
}
|
||||
|
||||
if config.Username.IsUnknown() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("username"),
|
||||
"Unknown Edge Connect API Username",
|
||||
"The provider cannot create the Edge Connect API client as there is an unknown configuration value for the username. "+
|
||||
"Either target apply the source of the value first, set the value statically in the configuration, or use the EDGE_CONNECT_USERNAME environment variable.",
|
||||
)
|
||||
}
|
||||
|
||||
if config.Password.IsUnknown() {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("password"),
|
||||
"Unknown Edge Connect API Password",
|
||||
"The provider cannot create the Edge Connect API client as there is an unknown configuration value for the password. "+
|
||||
"Either target apply the source of the value first, set the value statically in the configuration, or use the EDGE_CONNECT_PASSWORD environment variable.",
|
||||
)
|
||||
}
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
// Default values to environment variables, but override
|
||||
// with Terraform configuration value if set.
|
||||
baseURL := os.Getenv("EDGE_CONNECT_BASE_URL")
|
||||
token := os.Getenv("EDGE_CONNECT_TOKEN")
|
||||
username := os.Getenv("EDGE_CONNECT_USERNAME")
|
||||
password := os.Getenv("EDGE_CONNECT_PASSWORD")
|
||||
|
||||
if !config.BaseURL.IsNull() {
|
||||
baseURL = config.BaseURL.ValueString()
|
||||
}
|
||||
|
||||
if !config.Token.IsNull() {
|
||||
token = config.Token.ValueString()
|
||||
}
|
||||
|
||||
if !config.Username.IsNull() {
|
||||
username = config.Username.ValueString()
|
||||
}
|
||||
|
||||
if !config.Password.IsNull() {
|
||||
password = config.Password.ValueString()
|
||||
}
|
||||
|
||||
// If any of the expected configurations are missing, return
|
||||
// errors with provider-specific guidance.
|
||||
if baseURL == "" {
|
||||
resp.Diagnostics.AddAttributeError(
|
||||
path.Root("base_url"),
|
||||
"Missing Edge Connect API Base URL",
|
||||
"The provider requires a base URL for the Edge Connect API. "+
|
||||
"Set the base_url value in the configuration or use the EDGE_CONNECT_BASE_URL environment variable. "+
|
||||
"If either is already set, ensure the value is not empty.",
|
||||
)
|
||||
}
|
||||
|
||||
if token == "" && (username == "" || password == "") {
|
||||
resp.Diagnostics.AddError(
|
||||
"Missing Edge Connect API Authentication",
|
||||
"The provider requires either a bearer token or username/password for authentication. "+
|
||||
"Set the token value in the configuration or use the EDGE_CONNECT_TOKEN environment variable, "+
|
||||
"or set username and password values in the configuration or use the EDGE_CONNECT_USERNAME and EDGE_CONNECT_PASSWORD environment variables.",
|
||||
)
|
||||
}
|
||||
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
ctx = tflog.SetField(ctx, "edge_connect_base_url", baseURL)
|
||||
ctx = tflog.SetField(ctx, "edge_connect_token", token)
|
||||
ctx = tflog.SetField(ctx, "edge_connect_username", username)
|
||||
ctx = tflog.SetField(ctx, "edge_connect_password", password)
|
||||
ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "edge_connect_token")
|
||||
ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "edge_connect_password")
|
||||
|
||||
tflog.Debug(ctx, "Creating Edge Connect client")
|
||||
|
||||
// Create a new Edge Connect client using the configuration values
|
||||
apiClient := client.NewClient(baseURL, token, username, password)
|
||||
|
||||
// Test the connection
|
||||
if err := apiClient.HealthCheck(); err != nil {
|
||||
resp.Diagnostics.AddError(
|
||||
"Unable to Connect to Edge Connect API",
|
||||
"An error occurred while connecting to the Edge Connect API. "+
|
||||
"Please verify that your base URL and authentication credentials are correct.\n\n"+
|
||||
"Edge Connect Client Error: "+err.Error(),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// Make the Edge Connect client available during DataSource and Resource
|
||||
// type Configure methods.
|
||||
resp.DataSourceData = apiClient
|
||||
resp.ResourceData = apiClient
|
||||
|
||||
tflog.Info(ctx, "Configured Edge Connect client", map[string]any{"success": true})
|
||||
}
|
||||
|
||||
// DataSources defines the data sources implemented in the provider.
|
||||
func (p *edgeConnectProvider) DataSources(_ context.Context) []func() datasource.DataSource {
|
||||
return []func() datasource.DataSource{
|
||||
NewAppDataSource,
|
||||
NewAppInstDataSource,
|
||||
}
|
||||
}
|
||||
|
||||
// Resources defines the resources implemented in the provider.
|
||||
func (p *edgeConnectProvider) Resources(_ context.Context) []func() resource.Resource {
|
||||
return []func() resource.Resource{
|
||||
NewAppResource,
|
||||
NewAppInstResource,
|
||||
}
|
||||
}
|
||||
31
main.go
Normal file
31
main.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"log"
|
||||
|
||||
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/provider"
|
||||
"github.com/hashicorp/terraform-plugin-framework/providerserver"
|
||||
)
|
||||
|
||||
// version can be set at build time using -ldflags
|
||||
var version string = "dev"
|
||||
|
||||
func main() {
|
||||
var debug bool
|
||||
|
||||
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
|
||||
flag.Parse()
|
||||
|
||||
opts := providerserver.ServeOpts{
|
||||
Address: "registry.terraform.io/DevFW-CICD/edge-connect",
|
||||
Debug: debug,
|
||||
}
|
||||
|
||||
err := providerserver.Serve(context.Background(), provider.New(version), opts)
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue