Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: docker #79

Merged
merged 1 commit into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/resources/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "clevercloud_docker Resource - terraform-provider-clevercloud"
subcategory: ""
description: |-
Manage Docker https://www.docker.com/ applications.
See Docker product https://www.clever-cloud.com/doc/getting-started/by-language/docker/ specification.
---

# clevercloud_docker (Resource)

Manage [Docker](https://www.docker.com/) applications.

See [Docker product](https://www.clever-cloud.com/doc/getting-started/by-language/docker/) specification.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `biggest_flavor` (String) Biggest intance flavor, if different from smallest, enable autoscaling
- `max_instance_count` (Number) Maximum instance count, if different from min value, enable autoscaling
- `min_instance_count` (Number) Minimum instance count
- `name` (String) Application name
- `smallest_flavor` (String) Smallest instance flavor

### Optional

- `additional_vhosts` (List of String) Add custom hostname in addition to the default one, see [documentation](https://www.clever-cloud.com/doc/administrate/domain-names/)
- `app_folder` (String) Folder in which the application is located (inside the git repository)
- `build_flavor` (String) Use dedicated instance with given flavor for build step
- `container_port` (Number) Set to custom HTTP port if your Docker container runs on custom port
- `container_port_tcp` (Number) Set to custom TCP port if your Docker container runs on custom port.
- `daemon_socket_mount` (Boolean) Set to true to access the host Docker socket from inside your container
- `dependencies` (Set of String) A list of application or addons requires to run this application.
Can be either app_xxx or postgres_yyy ID format
- `deployment` (Block, Optional) (see [below for nested schema](#nestedblock--deployment))
- `description` (String) Application description
- `dockerfile` (String) The name of the Dockerfile to build
- `enable_ipv6` (Boolean) Activate the support of IPv6 with an IPv6 subnet int the docker daemon
- `environment` (Map of String, Sensitive) Environment variables injected into the application
- `hooks` (Block, Optional) (see [below for nested schema](#nestedblock--hooks))
- `redirect_https` (Boolean) Redirect client from plain to TLS port
- `region` (String) Geographical region where the database will be deployed
- `registry_password` (String) The password of your username
- `registry_url` (String) The server of your private registry (optional). Docker’s public registry
- `registry_user` (String) The username to login to a private registry
- `sticky_sessions` (Boolean) Enable sticky sessions, use it when your client sessions are instances scoped

### Read-Only

- `deploy_url` (String) Git URL used to push source code
- `id` (String) Unique identifier generated during application creation
- `vhost` (String) Default vhost to access your app

<a id="nestedblock--deployment"></a>
### Nested Schema for `deployment`

Optional:

- `commit` (String) Deploy application on the given commit/tag
- `repository` (String)


<a id="nestedblock--hooks"></a>
### Nested Schema for `hooks`

Optional:

- `post_build` (String) [CC_POST_BUILD_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#post-build-cc_post_build_hook)
- `pre_build` (String) [CC_PRE_BUILD_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#pre-build-cc_pre_build_hook)
- `pre_run` (String) [CC_PRE_RUN_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#pre-run-cc_pre_run_hook)
- `run_failed` (String) [CC_RUN_FAILED_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#run-succeeded-cc_run_succeeded_hook-or-failed-cc_run_failed_hook)
- `run_succeed` (String) [CC_RUN_SUCCEEDED_HOOK](https://www.clever-cloud.com/doc/develop/build-hooks/#run-succeeded-cc_run_succeeded_hook-or-failed-cc_run_failed_hook)
2 changes: 2 additions & 0 deletions pkg/registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"go.clever-cloud.com/terraform-provider/pkg/resources/addon"
"go.clever-cloud.com/terraform-provider/pkg/resources/cellar"
"go.clever-cloud.com/terraform-provider/pkg/resources/cellar/bucket"
"go.clever-cloud.com/terraform-provider/pkg/resources/docker"
"go.clever-cloud.com/terraform-provider/pkg/resources/java"
"go.clever-cloud.com/terraform-provider/pkg/resources/materiakv"
"go.clever-cloud.com/terraform-provider/pkg/resources/mongodb"
Expand All @@ -32,4 +33,5 @@ var Resources = []func() resource.Resource{
python.NewResourcePython,
scala.NewResourceScala(),
static.NewResourceStatic(),
docker.NewResourceDocker,
}
21 changes: 21 additions & 0 deletions pkg/resources/docker/resource_docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package docker

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/resource"
"go.clever-cloud.dev/client"
)

type ResourceDocker struct {
cc *client.Client
org string
}

func NewResourceDocker() resource.Resource {
return &ResourceDocker{}
}

func (r *ResourceDocker) Metadata(ctx context.Context, req resource.MetadataRequest, res *resource.MetadataResponse) {
res.TypeName = req.ProviderTypeName + "_docker"
}
3 changes: 3 additions & 0 deletions pkg/resources/docker/resource_docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Manage [Docker](https://www.docker.com/) applications.

See [Docker product](https://www.clever-cloud.com/doc/getting-started/by-language/docker/) specification.
198 changes: 198 additions & 0 deletions pkg/resources/docker/resource_docker_crud.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
package docker

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"go.clever-cloud.com/terraform-provider/pkg"
"go.clever-cloud.com/terraform-provider/pkg/application"
"go.clever-cloud.com/terraform-provider/pkg/provider"
"go.clever-cloud.com/terraform-provider/pkg/tmp"
)

// Weird behaviour, but TF can ask for a Resource without having configured a Provider (maybe for Meta and Schema)
// So we need to handle the case there is no ProviderData
func (r *ResourceDocker) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
tflog.Debug(ctx, "ResourceDocker.Configure()")

// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

provider, ok := req.ProviderData.(provider.Provider)
if ok {
r.cc = provider.Client()
r.org = provider.Organization()
}

tflog.Debug(ctx, "AFTER CONFIGURED", map[string]interface{}{"cc": r.cc == nil, "org": r.org})
}

// Create a new resource
func (r *ResourceDocker) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
plan := Docker{}

resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

instance := application.LookupInstance(ctx, r.cc, "docker", "Docker", resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

vhosts := []string{}
resp.Diagnostics.Append(plan.AdditionalVHosts.ElementsAs(ctx, &vhosts, false)...)
if resp.Diagnostics.HasError() {
return
}

environment := plan.toEnv(ctx, resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}

createAppReq := application.CreateReq{
Client: r.cc,
Organization: r.org,
Application: tmp.CreateAppRequest{
Name: plan.Name.ValueString(),
Deploy: "git",
Description: plan.Description.ValueString(),
InstanceType: instance.Type,
InstanceVariant: instance.Variant.ID,
InstanceVersion: instance.Version,
BuildFlavor: plan.BuildFlavor.ValueString(),
MinFlavor: plan.SmallestFlavor.ValueString(),
MaxFlavor: plan.BiggestFlavor.ValueString(),
MinInstances: plan.MinInstanceCount.ValueInt64(),
MaxInstances: plan.MaxInstanceCount.ValueInt64(),
StickySessions: plan.StickySessions.ValueBool(),
ForceHttps: application.FromForceHTTPS(plan.RedirectHTTPS.ValueBool()),
Zone: plan.Region.ValueString(),
CancelOnPush: false,
},
Environment: environment,
VHosts: vhosts,
Deployment: plan.toDeployment(),
}

createAppRes, diags := application.CreateApp(ctx, createAppReq)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, "BUILD FLAVOR RES"+createAppRes.Application.BuildFlavor.Name, map[string]interface{}{})
plan.ID = pkg.FromStr(createAppRes.Application.ID)
plan.DeployURL = pkg.FromStr(createAppRes.Application.DeployURL)
plan.VHost = pkg.FromStr(createAppRes.Application.Vhosts[0].Fqdn)

resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
if resp.Diagnostics.HasError() {
return
}
}

// Read resource information
func (r *ResourceDocker) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state Docker

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

app, diags := application.ReadApp(ctx, r.cc, r.org, state.ID.ValueString())
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
if app.AppIsDeleted {
resp.State.RemoveResource(ctx)
return
}

state.Name = pkg.FromStr(app.App.Name)
state.Description = pkg.FromStr(app.App.Description)
state.MinInstanceCount = pkg.FromI(int64(app.App.Instance.MinInstances))
state.MaxInstanceCount = pkg.FromI(int64(app.App.Instance.MaxInstances))
state.SmallestFlavor = pkg.FromStr(app.App.Instance.MinFlavor.Name)
state.BiggestFlavor = pkg.FromStr(app.App.Instance.MaxFlavor.Name)
state.Region = pkg.FromStr(app.App.Zone)
state.DeployURL = pkg.FromStr(app.App.DeployURL)

if app.App.SeparateBuild {
state.BuildFlavor = pkg.FromStr(app.App.BuildFlavor.Name)
} else {
state.BuildFlavor = types.StringNull()
}

vhosts := pkg.Map(app.App.Vhosts, func(vhost tmp.Vhost) string {
return vhost.Fqdn
})
hasDefaultVHost := pkg.HasSome(vhosts, func(vhost string) bool {
return pkg.VhostCleverAppsRegExp.MatchString(vhost)
})
if hasDefaultVHost {
cleverapps := *pkg.First(vhosts, func(vhost string) bool {
return pkg.VhostCleverAppsRegExp.MatchString(vhost)
})
state.VHost = pkg.FromStr(cleverapps)
} else {
state.VHost = types.StringNull()
}

vhostsWithoutDefault := pkg.Filter(vhosts, func(vhost string) bool {
ok := pkg.VhostCleverAppsRegExp.MatchString(vhost)
return !ok
})
if len(vhostsWithoutDefault) > 0 {
state.AdditionalVHosts = pkg.FromListString(vhostsWithoutDefault)
} else {
state.AdditionalVHosts = types.ListNull(types.StringType)
}

resp.Diagnostics.Append(resp.State.Set(ctx, state)...)
}

// Update resource
func (r *ResourceDocker) Update(ctx context.Context, req resource.UpdateRequest, res *resource.UpdateResponse) {
// TODO
}

// Delete resource
func (r *ResourceDocker) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state Docker

resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
tflog.Debug(ctx, "DOCKER DELETE", map[string]interface{}{"state": state})

res := tmp.DeleteApp(ctx, r.cc, r.org, state.ID.ValueString())
if res.IsNotFoundError() {
resp.State.RemoveResource(ctx)
return
}
if res.HasError() {
resp.Diagnostics.AddError("failed to delete app", res.Error().Error())
return
}

resp.State.RemoveResource(ctx)
}

// Import resource
func (r *ResourceDocker) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Save the import identifier in the id attribute
// and call Read() to fill fields
attr := path.Root("id")
resource.ImportStatePassthroughID(ctx, attr, req, resp)
}
Loading
Loading