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

Make function non mandatory in ctfd_challenge #133

Merged
merged 1 commit into from
Dec 13, 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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/ctfer-io/terraform-provider-ctfd
go 1.23.2

require (
github.com/ctfer-io/go-ctfd v0.10.0
github.com/ctfer-io/go-ctfd v0.10.1
github.com/hashicorp/terraform-plugin-docs v0.20.1
github.com/hashicorp/terraform-plugin-framework v1.13.0
github.com/hashicorp/terraform-plugin-go v0.25.0
Expand Down
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZ
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/ctfer-io/go-ctfd v0.10.0 h1:+lBLqde9XNdLB+TzXvJg9I9qkLRA8rYmf9bpjQ2CAUg=
github.com/ctfer-io/go-ctfd v0.10.0/go.mod h1:jNeeXui6iW1jsAfa4D5v6MaK0UF26CtjTRtCqS/PWH4=
github.com/ctfer-io/go-ctfd v0.10.1 h1:Bxr6/qo/h1c0FYFNZv/ej839Hzo5x1VMs0v+Io8ovs8=
github.com/ctfer-io/go-ctfd v0.10.1/go.mod h1:ebgSW8LdP/qtRCpglK4djBp+g6kU5YM98XBZKowiCeY=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
Expand Down Expand Up @@ -189,8 +189,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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 v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
Expand Down
65 changes: 44 additions & 21 deletions provider/challenge_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ import (
"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/defaults"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listdefault"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault"
Expand Down Expand Up @@ -102,34 +100,23 @@ func (r *challengeResource) Schema(ctx context.Context, req resource.SchemaReque
MarkdownDescription: "Decay function to define how the challenge value evolve through solves, either linear or logarithmic.",
Optional: true,
Computed: true,
Default: defaults.String(stringdefault.StaticString("linear")),
Validators: []validator.String{
validators.NewStringEnumValidator([]basetypes.StringValue{
FunctionLinear,
FunctionLogarithmic,
}),
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
"value": schema.Int64Attribute{
MarkdownDescription: "The value (points) of the challenge once solved. Internally, the provider will handle what target is legitimate depending on the `.type` value, i.e. either `value` for \"standard\" or `initial` for \"dynamic\".",
Required: true,
},
// XXX decay and minimum are only required if .type == "dynamic"
"decay": schema.Int64Attribute{
MarkdownDescription: "The decay defines from each number of solves does the decay function triggers until reaching minimum. This function is defined by CTFd and could be configured through `.function`.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"minimum": schema.Int64Attribute{
MarkdownDescription: "The minimum points for a dynamic-score challenge to reach with the decay function. Once there, no solve could have more value.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.UseStateForUnknown(),
},
},
"state": schema.StringAttribute{
MarkdownDescription: "State of the challenge, either hidden or visible.",
Expand Down Expand Up @@ -228,6 +215,26 @@ func (r *challengeResource) Create(ctx context.Context, req resource.CreateReque
return
}

// Configuration checks
if data.Type.Equal(types.StringValue("dynamic")) {
if data.Decay.IsNull() {
resp.Diagnostics.AddError("Configuration error", "decay must be set for dynamic challenges")
}
if data.Minimum.IsNull() {
resp.Diagnostics.AddError("Configuration error", "minimum must be set for dynamic challenges")
}
if data.Function.IsNull() || data.Function.IsUnknown() {
data.Function = FunctionLogarithmic
}
} else {
if data.Function.IsNull() || data.Function.IsUnknown() {
data.Function = types.StringNull()
}
}
if resp.Diagnostics.HasError() {
return
}

// Create Challenge
reqs := (*api.Requirements)(nil)
if data.Requirements != nil {
Expand All @@ -247,11 +254,11 @@ func (r *challengeResource) Create(ctx context.Context, req resource.CreateReque
Description: data.Description.ValueString(),
ConnectionInfo: data.ConnectionInfo.ValueStringPointer(),
MaxAttempts: utils.ToInt(data.MaxAttempts),
Function: data.Function.ValueString(),
Function: data.Function.ValueStringPointer(),
Value: int(data.Value.ValueInt64()),
Initial: utils.ToInt(data.Value),
Decay: utils.ToInt(data.Decay),
Minimum: utils.ToInt(data.Minimum),
Initial: utils.ToIntOnDynamic(data.Value, data.Type),
Decay: utils.ToIntOnDynamic(data.Decay, data.Type),
Minimum: utils.ToIntOnDynamic(data.Minimum, data.Type),
State: data.State.ValueString(),
Type: data.Type.ValueString(),
NextID: utils.ToInt(data.Next),
Expand Down Expand Up @@ -343,6 +350,22 @@ func (r *challengeResource) Update(ctx context.Context, req resource.UpdateReque
var dataState challengeResourceModel
req.State.Get(ctx, &dataState)

// Configuration checks
if data.Type.Equal(types.StringValue("dynamic")) {
if data.Decay.IsNull() {
resp.Diagnostics.AddError("Configuration error", "decay must be set for dynamic challenges")
}
if data.Minimum.IsNull() {
resp.Diagnostics.AddError("Configuration error", "minimum must be set for dynamic challenges")
}
if data.Function.IsNull() {
data.Function = FunctionLogarithmic
}
}
if resp.Diagnostics.HasError() {
return
}

// Patch direct attributes
reqs := (*api.Requirements)(nil)
if data.Requirements != nil {
Expand All @@ -362,7 +385,7 @@ func (r *challengeResource) Update(ctx context.Context, req resource.UpdateReque
Description: data.Description.ValueString(),
ConnectionInfo: data.ConnectionInfo.ValueStringPointer(),
MaxAttempts: utils.ToInt(data.MaxAttempts),
Function: data.Function.ValueString(),
Function: data.Function.ValueStringPointer(),
Value: utils.ToInt(data.Value),
Initial: utils.ToInt(data.Value),
Decay: utils.ToInt(data.Decay),
Expand Down Expand Up @@ -499,7 +522,7 @@ func (chall *challengeResourceModel) Read(ctx context.Context, client *api.Clien
chall.Description = types.StringValue(res.Description)
chall.ConnectionInfo = utils.ToTFString(res.ConnectionInfo)
chall.MaxAttempts = utils.ToTFInt64(res.MaxAttempts)
chall.Function = types.StringValue(res.Function)
chall.Function = utils.ToTFString(res.Function)
chall.Decay = utils.ToTFInt64(res.Decay)
chall.Minimum = utils.ToTFInt64(res.Minimum)
chall.State = types.StringValue(res.State)
Expand Down
2 changes: 2 additions & 0 deletions provider/challenge_resource_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ resource "ctfd_challenge" "icmp" {
- NicolasFgrx
EOT
value = 500
type = "standard"

requirements = {
behavior = "anonymized"
Expand All @@ -96,6 +97,7 @@ resource "ctfd_challenge" "icmp" {
`,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("ctfd_challenge.icmp", "requirements.prerequisites.#", "1"),
resource.TestCheckNoResourceAttr("ctfd_challenge.icmp", "function"),
),
},
// Delete testing automatically occurs in TestCase
Expand Down
9 changes: 9 additions & 0 deletions provider/utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ func ToInt(itf types.Int64) *int {
return &i
}

// ToIntOnDynamic returns the value of itf as an integer pointer iif
// the challType is dynamic.
func ToIntOnDynamic(itf types.Int64, challType types.String) *int {
if challType == types.StringValue("dynamic") {
return ToInt(itf)
}
return nil
}

func Ptr[T any](t T) *T {
return &t
}
Expand Down
Loading