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