Skip to content

Commit 4198f88

Browse files
authored
chore: added READ function and import function for main resources actually without kubernetes resources for solution discussion #35
1 parent 995c9c7 commit 4198f88

23 files changed

+619
-253
lines changed

README.md

Lines changed: 92 additions & 84 deletions
Large diffs are not rendered by default.

docs/index.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ $ export PORTAINER_SKIP_SSL_VERIFY=true
6868
```
6969

7070
## Arguments Reference
71-
| Name | Type | Required | Description |
72-
| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------------- |
73-
| `endpoint` | string | ✅ yes | URL of the Portainer instance. `/api` will be appended automatically if missing. |
74-
| `api_key` | string | ❌ no | API key for authentication. Mutually exclusive with `api_user` and `api_password`. |
75-
| `api_user` | string | ❌ no | Username for authentication (must be used with `api_password`). Mutually exclusive with `api_key`. |
76-
| `api_password` | string | ❌ no | Password for authentication (must be used with `api_user`). Mutually exclusive with `api_key`. |
77-
| `skip_ssl_verify` | boolean | ❌ no | Skip TLS certificate verification (useful for self-signed certs). Default: `false`. |
71+
| Name | Type | Required | Description |
72+
| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------|
73+
| `endpoint` | string | ✅ yes | URL of the Portainer instance. `/api` will be appended automatically if missing. |
74+
| `api_key` | string | ❌ no | API key for authentication. Mutually exclusive with `api_user` and `api_password`. |
75+
| `api_user` | string | ❌ no | Username for authentication (must be used with `api_password`). Mutually exclusive with `api_key`. |
76+
| `api_password` | string | ❌ no | Password for authentication (must be used with `api_user`). Mutually exclusive with `api_key`. |
77+
| `skip_ssl_verify` | boolean | ❌ no | Skip TLS certificate verification (useful for self-signed certs). Default: `false`. |
7878

7979
## 🧩 Supported Resources
8080
| Resource | Status |

docs/resources/settings.md

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,27 @@ trraform apply
3535

3636
## Arguments Reference
3737
### Main Attributes
38-
| Name | Type | Required | Description |
39-
|-------------------------------|----------|----------|------------------------------------------------------------------------------|
40-
| `authentication_method` | number | ✅ yes | Type of authentication (e.g., `1` = internal, `2` = LDAP, `3` = OAuth) |
41-
| `enable_telemetry` | bool | 🚫 no | Enable Portainer telemetry |
42-
| `logo_url` | string | 🚫 no | URL to custom logo |
43-
| `snapshot_interval` | string | 🚫 no | How often to run container snapshots (e.g., `"15m"`) |
44-
| `templates_url` | string | 🚫 no | URL to the template list JSON |
45-
| `user_session_timeout` | string | 🚫 no | Session expiration time (e.g., `"8h"`) |
46-
| `kubeconfig_expiry` | string | 🚫 no | Expiration time for downloaded Kubeconfigs |
47-
| `kubectl_shell_image` | string | 🚫 no | Image to be used for the kubectl shell UI |
48-
| `helm_repository_url` | string | 🚫 no | Default Helm repository URL |
49-
| `enable_edge_compute_features`| bool | 🚫 no | Enable Edge compute management support |
50-
| `enforce_edge_id` | bool | 🚫 no | Enforce the use of Portainer Edge ID |
51-
| `trust_on_first_connect` | bool | 🚫 no | Automatically trust TLS fingerprint on first connection |
52-
| `edge_agent_checkin_interval` | number | 🚫 no | Interval (in seconds) for Edge Agent check-ins |
38+
| Name | Type | Required | Description |
39+
|--------------------------------|----------|----------|------------------------------------------------------------------------------|
40+
| `authentication_method` | number | ✅ yes | Type of authentication (e.g., `1` = internal, `2` = LDAP, `3` = OAuth) |
41+
| `enable_telemetry` | bool | 🚫 no | Enable Portainer telemetry |
42+
| `logo_url` | string | 🚫 no | URL to custom logo |
43+
| `snapshot_interval` | string | 🚫 no | How often to run container snapshots (e.g., `"15m"`) |
44+
| `templates_url` | string | 🚫 no | URL to the template list JSON |
45+
| `user_session_timeout` | string | 🚫 no | Session expiration time (e.g., `"8h"`) |
46+
| `kubeconfig_expiry` | string | 🚫 no | Expiration time for downloaded Kubeconfigs |
47+
| `kubectl_shell_image` | string | 🚫 no | Image to be used for the kubectl shell UI |
48+
| `helm_repository_url` | string | 🚫 no | Default Helm repository URL |
49+
| `enable_edge_compute_features` | bool | 🚫 no | Enable Edge compute management support |
50+
| `enforce_edge_id` | bool | 🚫 no | Enforce the use of Portainer Edge ID |
51+
| `trust_on_first_connect` | bool | 🚫 no | Automatically trust TLS fingerprint on first connection |
52+
| `edge_agent_checkin_interval` | number | 🚫 no | Interval (in seconds) for Edge Agent check-ins |
53+
| `disable_kube_roles_sync` | bool | ❌ no | Disable Kubernetes role sync from RBAC |
54+
| `disable_kube_shell` | bool | ❌ no | Disable the Kubectl Shell feature |
55+
| `disable_kubeconfig_download` | bool | ❌ no | Disable downloading of Kubeconfig files |
56+
| `display_donation_header` | bool | ❌ no | Show the donation header in UI |
57+
| `display_external_contributors`| bool | ❌ no | Show the list of external contributors in the UI |
58+
| `is_docker_desktop_extension` | bool | ❌ no | Whether Portainer is running as Docker Desktop extension |
5359

5460
### `global_deployment_options` Block
5561
| Name | Type | Required | Description |

internal/resource_cloud_credentials.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ func resourceCloudCredentials() *schema.Resource {
2121
Update: resourceCloudCredentialsUpdate,
2222
Delete: resourceCloudCredentialsDelete,
2323
Read: resourceCloudCredentialsRead,
24+
Importer: &schema.ResourceImporter{
25+
State: schema.ImportStatePassthrough,
26+
},
2427
Schema: map[string]*schema.Schema{
2528
"cloud_provider": {
2629
Type: schema.TypeString,

internal/resource_docker_config.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,22 @@ func resourceDockerConfig() *schema.Resource {
1515
Read: resourceDockerConfigRead,
1616
Update: resourceDockerConfigUpdate,
1717
Delete: resourceDockerConfigDelete,
18+
Importer: &schema.ResourceImporter{
19+
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
20+
importID := d.Id()
21+
var endpointID int
22+
var configID string
23+
n, err := fmt.Sscanf(importID, "%d-%s", &endpointID, &configID)
24+
if err != nil || n != 2 {
25+
return nil, fmt.Errorf("invalid import ID format. Expected '<endpoint_id>-<config_id>'")
26+
}
27+
if err := d.Set("endpoint_id", endpointID); err != nil {
28+
return nil, err
29+
}
30+
d.SetId(configID)
31+
return []*schema.ResourceData{d}, nil
32+
},
33+
},
1834
Schema: map[string]*schema.Schema{
1935
"endpoint_id": {
2036
Type: schema.TypeInt,

internal/resource_docker_network.go

Lines changed: 89 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"fmt"
66
"io"
77
"net/http"
8+
"strconv"
9+
"strings"
810

911
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
1012
)
@@ -15,11 +17,27 @@ func resourceDockerNetwork() *schema.Resource {
1517
Read: resourceDockerNetworkRead,
1618
Delete: resourceDockerNetworkDelete,
1719
Update: nil,
20+
Importer: &schema.ResourceImporter{
21+
State: func(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
22+
// Expect ID in format "<endpoint_id>:<network_id>"
23+
parts := strings.SplitN(d.Id(), ":", 2)
24+
if len(parts) != 2 {
25+
return nil, fmt.Errorf("unexpected format of ID (%q), expected <endpoint_id>:<network_id>", d.Id())
26+
}
27+
endpointID, err := strconv.Atoi(parts[0])
28+
if err != nil {
29+
return nil, fmt.Errorf("invalid endpoint ID: %w", err)
30+
}
31+
d.Set("endpoint_id", endpointID)
32+
d.SetId(parts[1])
33+
return []*schema.ResourceData{d}, nil
34+
},
35+
},
1836
Schema: map[string]*schema.Schema{
1937
"endpoint_id": {Type: schema.TypeInt, Required: true, ForceNew: true},
2038
"name": {Type: schema.TypeString, Required: true, ForceNew: true},
2139
"driver": {Type: schema.TypeString, Optional: true, Default: "bridge", ForceNew: true},
22-
"scope": {Type: schema.TypeString, Optional: true, ForceNew: true},
40+
"scope": {Type: schema.TypeString, Optional: true, Default: "local", ForceNew: true},
2341
"internal": {Type: schema.TypeBool, Optional: true, Default: false, ForceNew: true},
2442
"attachable": {Type: schema.TypeBool, Optional: true, Default: false, ForceNew: true},
2543
"ingress": {Type: schema.TypeBool, Optional: true, Default: false, ForceNew: true},
@@ -163,112 +181,79 @@ func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error
163181
}
164182

165183
func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error {
184+
client := meta.(*APIClient)
185+
endpointID := d.Get("endpoint_id").(int)
186+
networkID := d.Id()
187+
path := fmt.Sprintf("/endpoints/%d/docker/networks/%s", endpointID, networkID)
188+
resp, err := client.DoRequest(http.MethodGet, path, nil, nil)
189+
if err != nil {
190+
return fmt.Errorf("failed to read docker network: %w", err)
191+
}
192+
defer resp.Body.Close()
193+
194+
if resp.StatusCode == 404 {
195+
d.SetId("")
196+
return nil
197+
}
198+
if resp.StatusCode != 200 {
199+
body, _ := io.ReadAll(resp.Body)
200+
return fmt.Errorf("failed to read docker network: %s", string(body))
201+
}
202+
203+
var result struct {
204+
Name string `json:"Name"`
205+
Driver string `json:"Driver"`
206+
Scope string `json:"Scope"`
207+
Internal bool `json:"Internal"`
208+
Attachable bool `json:"Attachable"`
209+
Ingress bool `json:"Ingress"`
210+
ConfigOnly bool `json:"ConfigOnly"`
211+
EnableIPv4 bool `json:"EnableIPv4"`
212+
EnableIPv6 bool `json:"EnableIPv6"`
213+
Options map[string]interface{} `json:"Options"`
214+
Labels map[string]string `json:"Labels"`
215+
IPAM struct {
216+
Driver string `json:"Driver"`
217+
Options map[string]string `json:"Options"`
218+
Config []map[string]interface{} `json:"Config"`
219+
} `json:"IPAM"`
220+
}
221+
222+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
223+
return fmt.Errorf("failed to decode docker network response: %w", err)
224+
}
225+
226+
_ = d.Set("name", result.Name)
227+
_ = d.Set("driver", result.Driver)
228+
_ = d.Set("scope", result.Scope)
229+
_ = d.Set("internal", result.Internal)
230+
_ = d.Set("attachable", result.Attachable)
231+
_ = d.Set("ingress", result.Ingress)
232+
_ = d.Set("config_only", result.ConfigOnly)
233+
_ = d.Set("enable_ipv4", result.EnableIPv4)
234+
_ = d.Set("enable_ipv6", result.EnableIPv6)
235+
_ = d.Set("options", result.Options)
236+
237+
labels := make(map[string]interface{}, len(result.Labels))
238+
for k, v := range result.Labels {
239+
labels[k] = v
240+
}
241+
_ = d.Set("labels", labels)
242+
243+
// IPAM config
244+
_ = d.Set("ipam_driver", result.IPAM.Driver)
245+
246+
ipamOpts := make(map[string]interface{}, len(result.IPAM.Options))
247+
for k, v := range result.IPAM.Options {
248+
ipamOpts[k] = v
249+
}
250+
_ = d.Set("ipam_options", ipamOpts)
251+
252+
_ = d.Set("ipam_config", result.IPAM.Config)
253+
166254
return nil
167255
}
168256

169-
// Use if will be exists PUT (update) methods for Docker Netwerks over API
170-
///func resourceDockerNetworkRead(d *schema.ResourceData, meta interface{}) error {
171-
/// client := meta.(*APIClient)
172-
/// endpointID := d.Get("endpoint_id").(int)
173-
/// networkID := d.Id()
174-
///
175-
/// path := fmt.Sprintf("/endpoints/%d/docker/networks/%s", endpointID, networkID)
176-
/// resp, err := client.DoRequest(http.MethodGet, path, nil, nil)
177-
/// if err != nil {
178-
/// return fmt.Errorf("failed to read docker network: %w", err)
179-
/// }
180-
/// defer resp.Body.Close()
181-
///
182-
/// if resp.StatusCode == 404 {
183-
/// d.SetId("")
184-
/// return nil
185-
/// } else if resp.StatusCode != 200 {
186-
/// body, _ := io.ReadAll(resp.Body)
187-
/// return fmt.Errorf("failed to read docker network: %s", string(body))
188-
/// }
189-
///
190-
/// var network struct {
191-
/// ID string `json:"Id"`
192-
/// Name string `json:"Name"`
193-
/// Driver string `json:"Driver"`
194-
/// Scope string `json:"Scope"`
195-
/// Internal bool `json:"Internal"`
196-
/// Attachable bool `json:"Attachable"`
197-
/// Ingress bool `json:"Ingress"`
198-
/// ConfigOnly bool `json:"ConfigOnly"`
199-
/// ConfigFrom map[string]string `json:"ConfigFrom"`
200-
/// EnableIPv4 *bool `json:"EnableIPv4,omitempty"`
201-
/// EnableIPv6 *bool `json:"EnableIPv6,omitempty"`
202-
/// IPAM struct {
203-
/// Driver string `json:"Driver"`
204-
/// Options map[string]string `json:"Options"`
205-
/// Config []map[string]interface{} `json:"Config"`
206-
/// } `json:"IPAM"`
207-
/// Options map[string]string `json:"Options"`
208-
/// Labels map[string]string `json:"Labels"`
209-
/// }
210-
///
211-
/// if err := json.NewDecoder(resp.Body).Decode(&network); err != nil {
212-
/// return err
213-
/// }
214-
///
215-
/// d.Set("name", network.Name)
216-
/// d.Set("driver", network.Driver)
217-
/// d.Set("scope", network.Scope)
218-
/// d.Set("internal", network.Internal)
219-
/// d.Set("attachable", network.Attachable)
220-
/// d.Set("ingress", network.Ingress)
221-
/// d.Set("config_only", network.ConfigOnly)
222-
///
223-
/// if network.EnableIPv4 != nil {
224-
/// d.Set("enable_ipv4", *network.EnableIPv4)
225-
/// }
226-
/// if network.EnableIPv6 != nil {
227-
/// d.Set("enable_ipv6", *network.EnableIPv6)
228-
/// }
229-
///
230-
/// if v, ok := network.ConfigFrom["Network"]; ok {
231-
/// d.Set("config_from", v)
232-
/// }
233-
///
234-
/// d.Set("ipam_driver", network.IPAM.Driver)
235-
///
236-
/// if network.IPAM.Options != nil {
237-
/// d.Set("ipam_options", network.IPAM.Options)
238-
/// } else {
239-
/// d.Set("ipam_options", map[string]string{})
240-
/// }
241-
///
242-
/// var ipamConfigList []map[string]interface{}
243-
/// for _, c := range network.IPAM.Config {
244-
/// entry := map[string]interface{}{}
245-
/// if subnet, ok := c["Subnet"]; ok {
246-
/// entry["subnet"] = subnet
247-
/// }
248-
/// if ipRange, ok := c["IPRange"]; ok {
249-
/// entry["ip_range"] = ipRange
250-
/// }
251-
/// if gateway, ok := c["Gateway"]; ok {
252-
/// entry["gateway"] = gateway
253-
/// }
254-
/// if aux, ok := c["AuxiliaryAddresses"]; ok {
255-
/// entry["auxiliary_addresses"] = aux
256-
/// }
257-
/// ipamConfigList = append(ipamConfigList, entry)
258-
/// }
259-
///
260-
/// if ipamConfigList != nil {
261-
/// d.Set("ipam_config", ipamConfigList)
262-
/// } else {
263-
/// d.Set("ipam_config", []interface{}{})
264-
/// }
265-
///
266-
/// d.Set("options", network.Options)
267-
/// d.Set("labels", network.Labels)
268-
///
269-
/// return nil
270-
///
271-
272257
func resourceDockerNetworkDelete(d *schema.ResourceData, meta interface{}) error {
273258
client := meta.(*APIClient)
274259
endpointID := d.Get("endpoint_id").(int)

internal/resource_docker_node.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ func resourceDockerNode() *schema.Resource {
2424
Read: resourceDockerNodeRead,
2525
Update: resourceDockerNodeUpdate,
2626
Delete: resourceDockerNodeDelete,
27-
2827
Schema: map[string]*schema.Schema{
2928
"endpoint_id": {
3029
Type: schema.TypeInt,

0 commit comments

Comments
 (0)