|
1 | 1 | package extent
|
2 | 2 |
|
3 | 3 | import (
|
| 4 | + "fmt" |
4 | 5 | "regexp"
|
5 | 6 | "strings"
|
6 | 7 | "time"
|
7 | 8 |
|
8 | 9 | "github.com/gitploy-io/cronexpr"
|
| 10 | + "github.com/gitploy-io/gitploy/pkg/e" |
9 | 11 | eutil "github.com/gitploy-io/gitploy/pkg/e"
|
10 | 12 | )
|
11 | 13 |
|
12 |
| -type ( |
13 |
| - Env struct { |
14 |
| - Name string `json:"name" yaml:"name"` |
| 14 | +type Env struct { |
| 15 | + Name string `json:"name" yaml:"name"` |
15 | 16 |
|
16 |
| - // GitHub parameters of deployment. |
17 |
| - Task *string `json:"task" yaml:"task"` |
18 |
| - Description *string `json:"description" yaml:"description"` |
19 |
| - AutoMerge *bool `json:"auto_merge" yaml:"auto_merge"` |
20 |
| - RequiredContexts *[]string `json:"required_contexts,omitempty" yaml:"required_contexts"` |
21 |
| - Payload interface{} `json:"payload" yaml:"payload"` |
22 |
| - ProductionEnvironment *bool `json:"production_environment" yaml:"production_environment"` |
| 17 | + // GitHub parameters of deployment. |
| 18 | + Task *string `json:"task" yaml:"task"` |
| 19 | + Description *string `json:"description" yaml:"description"` |
| 20 | + AutoMerge *bool `json:"auto_merge" yaml:"auto_merge"` |
| 21 | + RequiredContexts *[]string `json:"required_contexts,omitempty" yaml:"required_contexts"` |
| 22 | + Payload interface{} `json:"payload" yaml:"payload"` |
| 23 | + DynamicPayload *DynamicPayload `json:"dynamic_payload" yaml:"dynamic_payload"` |
| 24 | + ProductionEnvironment *bool `json:"production_environment" yaml:"production_environment"` |
23 | 25 |
|
24 |
| - // DeployableRef validates the ref is deployable or not. |
25 |
| - DeployableRef *string `json:"deployable_ref" yaml:"deployable_ref"` |
| 26 | + // DeployableRef validates the ref is deployable or not. |
| 27 | + DeployableRef *string `json:"deployable_ref" yaml:"deployable_ref"` |
26 | 28 |
|
27 |
| - // AutoDeployOn deploys automatically when the pattern is matched. |
28 |
| - AutoDeployOn *string `json:"auto_deploy_on" yaml:"auto_deploy_on"` |
| 29 | + // AutoDeployOn deploys automatically when the pattern is matched. |
| 30 | + AutoDeployOn *string `json:"auto_deploy_on" yaml:"auto_deploy_on"` |
29 | 31 |
|
30 |
| - // Serialization verify if there is a running deployment. |
31 |
| - Serialization *bool `json:"serialization" yaml:"serialization"` |
| 32 | + // Serialization verify if there is a running deployment. |
| 33 | + Serialization *bool `json:"serialization" yaml:"serialization"` |
32 | 34 |
|
33 |
| - // Review is the configuration of Review, |
34 |
| - // It is disabled when it is empty. |
35 |
| - Review *Review `json:"review,omitempty" yaml:"review"` |
| 35 | + // Review is the configuration of Review, |
| 36 | + // It is disabled when it is empty. |
| 37 | + Review *Review `json:"review,omitempty" yaml:"review"` |
36 | 38 |
|
37 |
| - // FrozenWindows is the list of windows to freeze deployments. |
38 |
| - FrozenWindows []FrozenWindow `json:"frozen_windows" yaml:"frozen_windows"` |
39 |
| - } |
| 39 | + // FrozenWindows is the list of windows to freeze deployments. |
| 40 | + FrozenWindows []FrozenWindow `json:"frozen_windows" yaml:"frozen_windows"` |
| 41 | +} |
40 | 42 |
|
41 |
| - Review struct { |
42 |
| - Enabled bool `json:"enabled" yaml:"enabled"` |
43 |
| - Reviewers []string `json:"reviewers" yaml:"reviewers"` |
44 |
| - } |
| 43 | +type Review struct { |
| 44 | + Enabled bool `json:"enabled" yaml:"enabled"` |
| 45 | + Reviewers []string `json:"reviewers" yaml:"reviewers"` |
| 46 | +} |
45 | 47 |
|
46 |
| - FrozenWindow struct { |
47 |
| - Start string `json:"start" yaml:"start"` |
48 |
| - Duration string `json:"duration" yaml:"duration"` |
49 |
| - Location string `json:"location" yaml:"location"` |
50 |
| - } |
51 |
| -) |
| 48 | +type FrozenWindow struct { |
| 49 | + Start string `json:"start" yaml:"start"` |
| 50 | + Duration string `json:"duration" yaml:"duration"` |
| 51 | + Location string `json:"location" yaml:"location"` |
| 52 | +} |
52 | 53 |
|
53 | 54 | // IsProductionEnvironment verifies whether the environment is production or not.
|
54 | 55 | func (e *Env) IsProductionEnvironment() bool {
|
@@ -124,3 +125,100 @@ func (e *Env) IsFreezed(t time.Time) (bool, error) {
|
124 | 125 |
|
125 | 126 | return false, nil
|
126 | 127 | }
|
| 128 | + |
| 129 | +func (e *Env) EvaluateDynamicPayload(values map[string]interface{}) (map[string]interface{}, error) { |
| 130 | + return e.DynamicPayload.Evaluate(values) |
| 131 | +} |
| 132 | + |
| 133 | +// DynamicPayload can be set to dynamically fill in the payload. |
| 134 | +type DynamicPayload struct { |
| 135 | + Enabled bool `json:"enabled" yaml:"enabled"` |
| 136 | + Inputs map[string]Input `json:"inputs" yaml:"inputs"` |
| 137 | +} |
| 138 | + |
| 139 | +// Evaluate validates the payload. After that, |
| 140 | +// an object containing only the value of the defined field is returned. |
| 141 | +func (dp *DynamicPayload) Evaluate(values map[string]interface{}) (output map[string]interface{}, err error) { |
| 142 | + for key, input := range dp.Inputs { |
| 143 | + // If it is a required field, check if the value exists. |
| 144 | + value, ok := values[key] |
| 145 | + if !ok { |
| 146 | + if optional := !(input.Required != nil && *input.Required); optional { |
| 147 | + continue |
| 148 | + } |
| 149 | + |
| 150 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, fmt.Sprintf("The '%s' field is required.", key), nil) |
| 151 | + } |
| 152 | + |
| 153 | + eval, err := dp.validate(input, value) |
| 154 | + if err != nil { |
| 155 | + return nil, err |
| 156 | + } |
| 157 | + |
| 158 | + output[key] = eval |
| 159 | + } |
| 160 | + |
| 161 | + return output, nil |
| 162 | +} |
| 163 | + |
| 164 | +func (dp *DynamicPayload) validate(input Input, value interface{}) (interface{}, error) { |
| 165 | + switch input.Type { |
| 166 | + case InputTypeSelect: |
| 167 | + // Checks if the selected value matches the option, |
| 168 | + // and returns the value if it is. |
| 169 | + sv, ok := value.(string) |
| 170 | + if !ok { |
| 171 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, fmt.Sprintf("The '%v' is not string type.", value), nil) |
| 172 | + } |
| 173 | + |
| 174 | + for _, option := range *input.Options { |
| 175 | + if sv == option { |
| 176 | + return sv, nil |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, "The '%s' is not matched with the options.", nil) |
| 181 | + case InputTypeNumber: |
| 182 | + nv, ok := value.(float64) |
| 183 | + if !ok { |
| 184 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, fmt.Sprintf("The '%v' is not string type.", value), nil) |
| 185 | + } |
| 186 | + |
| 187 | + return nv, nil |
| 188 | + case InputTypeString: |
| 189 | + sv, ok := value.(string) |
| 190 | + if !ok { |
| 191 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, fmt.Sprintf("The '%v' is not string type.", value), nil) |
| 192 | + } |
| 193 | + |
| 194 | + return sv, nil |
| 195 | + case InputTypeBoolean: |
| 196 | + bv, ok := value.(bool) |
| 197 | + if !ok { |
| 198 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, fmt.Sprintf("The '%v' is not string type.", value), nil) |
| 199 | + } |
| 200 | + |
| 201 | + return bv, nil |
| 202 | + default: |
| 203 | + return nil, e.NewErrorWithMessage(e.ErrorCodeDeploymentInvalid, "The type must be 'select', 'number', 'string', or 'boolean'.", nil) |
| 204 | + } |
| 205 | +} |
| 206 | + |
| 207 | +// Input defines specifications for input values. |
| 208 | +type Input struct { |
| 209 | + Type InputType `json:"type" yaml:"type"` |
| 210 | + Required *bool `json:"required" yaml:"required"` |
| 211 | + Default *interface{} `json:"default" yaml:"default"` |
| 212 | + Description *string `json:"description" yaml:"description"` |
| 213 | + Options *[]string `json:"options" yaml:"options"` |
| 214 | +} |
| 215 | + |
| 216 | +// InputType is the type for input. |
| 217 | +type InputType string |
| 218 | + |
| 219 | +const ( |
| 220 | + InputTypeSelect InputType = "select" |
| 221 | + InputTypeNumber InputType = "number" |
| 222 | + InputTypeString InputType = "string" |
| 223 | + InputTypeBoolean InputType = "boolean" |
| 224 | +) |
0 commit comments