mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
* #11602 fix : Object options menu truncated when selected in ApplicationListView. Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * CMP parameter changes Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * fix: pointers to param values Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> better? Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> fix silliness Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> terrible hacks Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> maybe better codegen Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> fix tests Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * same prefix issue fixed Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * fix for delete param name Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * lint changes Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * lint fix Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * lint fix Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * finalChanges Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * Delete application-resource-list.tsx Not needed for this PR. Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * added file which was deleted as it was not the change needed for this feature. Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * refactored MapValuField and added fix for some edge cases Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * Update application-resource-list.tsx Reverting the change as this is not related to this PR. Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> * Reverting the change in application-resource-list Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * Showing application parameter values irrespective of parameter present or not in plugin.yaml Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * fix for lint errors Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> * fix false source mismatch Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * fix equals Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * fix swagger doc Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> * Tooltip description change. Signed-off-by: schakrad <chakradari.sindhu@gmail.com> * fixed lint Signed-off-by: schakrad <chakradari.sindhu@gmail.com> * CMP fix for empty array. Signed-off-by: schakrad <chakradari.sindhu@gmail.com> --------- Signed-off-by: schakradari <saisindhu_chakradari@intuit.com> Signed-off-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com> Signed-off-by: schakrad <58915923+schakrad@users.noreply.github.com> Signed-off-by: schakrad <chakradari.sindhu@gmail.com> Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
This commit is contained in:
@@ -1479,7 +1479,7 @@ func currentSourceEqualsSyncedSource(app *appv1.Application) bool {
|
||||
if app.Spec.HasMultipleSources() {
|
||||
return app.Spec.Sources.Equals(app.Status.Sync.ComparedTo.Sources)
|
||||
}
|
||||
return app.Spec.Source.Equals(app.Status.Sync.ComparedTo.Source)
|
||||
return app.Spec.Source.Equals(&app.Status.Sync.ComparedTo.Source)
|
||||
}
|
||||
|
||||
// needRefreshAppStatus answers if application status needs to be refreshed.
|
||||
|
||||
@@ -112,7 +112,13 @@ EOF
|
||||
rm -f "${SWAGGER_OUT}"
|
||||
|
||||
find "${SWAGGER_ROOT}" -name '*.swagger.json' -exec swagger mixin --ignore-conflicts "${PRIMARY_SWAGGER}" '{}' \+ > "${COMBINED_SWAGGER}"
|
||||
jq -r 'del(.definitions[].properties[]? | select(."$ref"!=null and .description!=null).description) | del(.definitions[].properties[]? | select(."$ref"!=null and .title!=null).title)' "${COMBINED_SWAGGER}" > "${SWAGGER_OUT}"
|
||||
jq -r 'del(.definitions[].properties[]? | select(."$ref"!=null and .description!=null).description) | del(.definitions[].properties[]? | select(."$ref"!=null and .title!=null).title) |
|
||||
# The "array" and "map" fields have custom unmarshaling. Modify the swagger to reflect this.
|
||||
.definitions.v1alpha1ApplicationSourcePluginParameter.properties.array = {"description":"Array is the value of an array type parameter.","type":"array","items":{"type":"string"}} |
|
||||
del(.definitions.v1alpha1OptionalArray) |
|
||||
.definitions.v1alpha1ApplicationSourcePluginParameter.properties.map = {"description":"Map is the value of a map type parameter.","type":"object","additionalProperties":{"type":"string"}} |
|
||||
del(.definitions.v1alpha1OptionalMap)
|
||||
' "${COMBINED_SWAGGER}" > "${SWAGGER_OUT}"
|
||||
|
||||
/bin/rm "${PRIMARY_SWAGGER}" "${COMBINED_SWAGGER}"
|
||||
}
|
||||
@@ -128,3 +134,4 @@ clean_swagger server
|
||||
clean_swagger reposerver
|
||||
clean_swagger controller
|
||||
clean_swagger cmpserver
|
||||
|
||||
|
||||
@@ -21,7 +21,6 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/ap
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceJsonnet,ExtVars
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceJsonnet,Libs
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourceJsonnet,TLAs
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourcePluginParameter,Array
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSpec,IgnoreDifferences
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSpec,Info
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationStatus,Conditions
|
||||
@@ -48,6 +47,7 @@ API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/ap
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,MergeGenerator,MergeKeys
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,NestedMergeGenerator,MergeKeys
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,Operation,Info
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,OptionalArray,Array
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,OrphanedResourcesMonitorSettings,Ignore
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,OverrideIgnoreDiff,JQPathExpressions
|
||||
API rule violation: list_type_missing,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,OverrideIgnoreDiff,JSONPointers
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -490,10 +490,10 @@ message ApplicationSourcePluginParameter {
|
||||
optional string string = 5;
|
||||
|
||||
// Map is the value of a map type parameter.
|
||||
map<string, string> map = 3;
|
||||
optional OptionalMap map = 3;
|
||||
|
||||
// Array is the value of an array type parameter.
|
||||
repeated string array = 4;
|
||||
optional OptionalArray array = 4;
|
||||
}
|
||||
|
||||
// ApplicationSpec represents desired application state. Contains link to repository with application definition and additional parameters link definition revision.
|
||||
@@ -1117,6 +1117,18 @@ message OperationState {
|
||||
optional int64 retryCount = 8;
|
||||
}
|
||||
|
||||
message OptionalArray {
|
||||
// Array is the value of an array type parameter.
|
||||
// +optional
|
||||
repeated string array = 1;
|
||||
}
|
||||
|
||||
message OptionalMap {
|
||||
// Map is the value of a map type parameter.
|
||||
// +optional
|
||||
map<string, string> map = 1;
|
||||
}
|
||||
|
||||
// OrphanedResourceKey is a reference to a resource to be ignored from
|
||||
message OrphanedResourceKey {
|
||||
optional string group = 1;
|
||||
|
||||
@@ -95,6 +95,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.Operation": schema_pkg_apis_application_v1alpha1_Operation(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OperationInitiator": schema_pkg_apis_application_v1alpha1_OperationInitiator(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OperationState": schema_pkg_apis_application_v1alpha1_OperationState(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OptionalArray": schema_pkg_apis_application_v1alpha1_OptionalArray(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OptionalMap": schema_pkg_apis_application_v1alpha1_OptionalMap(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OrphanedResourceKey": schema_pkg_apis_application_v1alpha1_OrphanedResourceKey(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OrphanedResourcesMonitorSettings": schema_pkg_apis_application_v1alpha1_OrphanedResourcesMonitorSettings(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.OverrideIgnoreDiff": schema_pkg_apis_application_v1alpha1_OverrideIgnoreDiff(ref),
|
||||
@@ -1840,37 +1842,6 @@ func schema_pkg_apis_application_v1alpha1_ApplicationSourcePluginParameter(ref c
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"map": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Map is the value of a map type parameter.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"array": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Array is the value of an array type parameter.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -3986,6 +3957,61 @@ func schema_pkg_apis_application_v1alpha1_OperationState(ref common.ReferenceCal
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_OptionalArray(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"array": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Array is the value of an array type parameter.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_OptionalMap(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"map": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Map is the value of a map type parameter.",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_OrphanedResourceKey(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
||||
@@ -188,7 +188,7 @@ func (s ApplicationSources) Equals(other ApplicationSources) bool {
|
||||
return false
|
||||
}
|
||||
for i := range s {
|
||||
if !s[i].Equals(other[i]) {
|
||||
if !s[i].Equals(&other[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -529,19 +529,155 @@ func (d *ApplicationSourceDirectory) IsZero() bool {
|
||||
return d == nil || !d.Recurse && d.Jsonnet.IsZero()
|
||||
}
|
||||
|
||||
type OptionalMap struct {
|
||||
// Map is the value of a map type parameter.
|
||||
// +optional
|
||||
Map map[string]string `json:"map" protobuf:"bytes,1,rep,name=map"`
|
||||
// We need the explicit +optional so that kube-builder generates the CRD without marking this as required.
|
||||
}
|
||||
|
||||
// Equals returns true if the two OptionalMap objects are equal. We can't use reflect.DeepEqual because it will return
|
||||
// false if one of the maps is nil and the other is an empty map. This is because the JSON unmarshaller will set the
|
||||
// map to nil if it is empty, but the protobuf unmarshaller will set it to an empty map.
|
||||
func (o *OptionalMap) Equals(other *OptionalMap) bool {
|
||||
if o == nil && other == nil {
|
||||
return true
|
||||
}
|
||||
if o == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
if len(o.Map) != len(other.Map) {
|
||||
return false
|
||||
}
|
||||
if o.Map == nil && other.Map == nil {
|
||||
return true
|
||||
}
|
||||
// The next two blocks are critical. Depending on whether the struct was populated from JSON or protobufs, the map
|
||||
// field will be either nil or an empty map. They mean the same thing: the map is empty.
|
||||
if o.Map == nil && len(other.Map) == 0 {
|
||||
return true
|
||||
}
|
||||
if other.Map == nil && len(o.Map) == 0 {
|
||||
return true
|
||||
}
|
||||
return reflect.DeepEqual(o.Map, other.Map)
|
||||
}
|
||||
|
||||
type OptionalArray struct {
|
||||
// Array is the value of an array type parameter.
|
||||
// +optional
|
||||
Array []string `json:"array" protobuf:"bytes,1,rep,name=array"`
|
||||
// We need the explicit +optional so that kube-builder generates the CRD without marking this as required.
|
||||
}
|
||||
|
||||
// Equals returns true if the two OptionalArray objects are equal. We can't use reflect.DeepEqual because it will return
|
||||
// false if one of the arrays is nil and the other is an empty array. This is because the JSON unmarshaller will set the
|
||||
// array to nil if it is empty, but the protobuf unmarshaller will set it to an empty array.
|
||||
func (o *OptionalArray) Equals(other *OptionalArray) bool {
|
||||
if o == nil && other == nil {
|
||||
return true
|
||||
}
|
||||
if o == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
if len(o.Array) != len(other.Array) {
|
||||
return false
|
||||
}
|
||||
if o.Array == nil && other.Array == nil {
|
||||
return true
|
||||
}
|
||||
// The next two blocks are critical. Depending on whether the struct was populated from JSON or protobufs, the array
|
||||
// field will be either nil or an empty array. They mean the same thing: the array is empty.
|
||||
if o.Array == nil && len(other.Array) == 0 {
|
||||
return true
|
||||
}
|
||||
if other.Array == nil && len(o.Array) == 0 {
|
||||
return true
|
||||
}
|
||||
return reflect.DeepEqual(o.Array, other.Array)
|
||||
}
|
||||
|
||||
type ApplicationSourcePluginParameter struct {
|
||||
// We use pointers to structs because go-to-protobuf represents pointers to arrays/maps as repeated fields.
|
||||
// These repeated fields have no way to represent "present but empty." So we would have no way to distinguish
|
||||
// {name: parameters, array: []} from {name: parameter}
|
||||
// By wrapping the array/map in a struct, we can use a pointer to the struct to represent "present but empty."
|
||||
|
||||
// Name is the name identifying a parameter.
|
||||
Name string `json:"name,omitempty" protobuf:"bytes,1,opt,name=name"`
|
||||
// String_ is the value of a string type parameter.
|
||||
String_ *string `json:"string,omitempty" protobuf:"bytes,5,opt,name=string"`
|
||||
// Map is the value of a map type parameter.
|
||||
Map map[string]string `json:"map,omitempty" protobuf:"bytes,3,rep,name=map"`
|
||||
*OptionalMap `json:",omitempty" protobuf:"bytes,3,rep,name=map"`
|
||||
// Array is the value of an array type parameter.
|
||||
Array []string `json:"array,omitempty" protobuf:"bytes,4,rep,name=array"`
|
||||
*OptionalArray `json:",omitempty" protobuf:"bytes,4,rep,name=array"`
|
||||
}
|
||||
|
||||
func (p ApplicationSourcePluginParameter) Equals(other ApplicationSourcePluginParameter) bool {
|
||||
if p.Name != other.Name {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(p.String_, other.String_) {
|
||||
return false
|
||||
}
|
||||
return p.OptionalMap.Equals(other.OptionalMap) && p.OptionalArray.Equals(other.OptionalArray)
|
||||
}
|
||||
|
||||
// MarshalJSON is a custom JSON marshaller for ApplicationSourcePluginParameter. We need this custom marshaler because,
|
||||
// when ApplicationSourcePluginParameter is unmarshaled, either from JSON or protobufs, the fields inside OptionalMap and
|
||||
// OptionalArray are not set. The default JSON marshaler marshals these as "null." But really what we want to represent
|
||||
// is an empty map or array.
|
||||
//
|
||||
// There are efforts to change things upstream, but nothing has been merged yet. See https://github.com/golang/go/issues/37711
|
||||
func (p ApplicationSourcePluginParameter) MarshalJSON() ([]byte, error) {
|
||||
out := map[string]interface{}{}
|
||||
out["name"] = p.Name
|
||||
if p.String_ != nil {
|
||||
out["string"] = p.String_
|
||||
}
|
||||
if p.OptionalMap != nil {
|
||||
if p.OptionalMap.Map == nil {
|
||||
// Nil is not the same as a nil map. Nil means the field was not set, while a nil map means the field was set to an empty map.
|
||||
// Either way, we want to marshal it as "{}".
|
||||
out["map"] = map[string]string{}
|
||||
} else {
|
||||
out["map"] = p.OptionalMap.Map
|
||||
}
|
||||
}
|
||||
if p.OptionalArray != nil {
|
||||
if p.OptionalArray.Array == nil {
|
||||
// Nil is not the same as a nil array. Nil means the field was not set, while a nil array means the field was set to an empty array.
|
||||
// Either way, we want to marshal it as "[]".
|
||||
out["array"] = []string{}
|
||||
} else {
|
||||
out["array"] = p.OptionalArray.Array
|
||||
}
|
||||
}
|
||||
bytes, err := json.Marshal(out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
type ApplicationSourcePluginParameters []ApplicationSourcePluginParameter
|
||||
|
||||
func (p ApplicationSourcePluginParameters) Equals(other ApplicationSourcePluginParameters) bool {
|
||||
if len(p) != len(other) {
|
||||
return false
|
||||
}
|
||||
for i := range p {
|
||||
if !p[i].Equals(other[i]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p ApplicationSourcePluginParameters) IsZero() bool {
|
||||
return len(p) == 0
|
||||
}
|
||||
|
||||
// Environ builds a list of environment variables to represent parameters sent to a plugin from the Application
|
||||
// manifest. Parameters are represented as one large stringified JSON array (under `ARGOCD_APP_PARAMETERS`). They're
|
||||
// also represented as individual environment variables, each variable's key being an escaped version of the parameter's
|
||||
@@ -560,13 +696,13 @@ func (p ApplicationSourcePluginParameters) Environ() ([]string, error) {
|
||||
if param.String_ != nil {
|
||||
env = append(env, fmt.Sprintf("%s=%s", envBaseName, *param.String_))
|
||||
}
|
||||
if param.Map != nil {
|
||||
for key, value := range param.Map {
|
||||
if param.OptionalMap != nil {
|
||||
for key, value := range param.OptionalMap.Map {
|
||||
env = append(env, fmt.Sprintf("%s_%s=%s", envBaseName, escaped(key), value))
|
||||
}
|
||||
}
|
||||
if param.Array != nil {
|
||||
for i, value := range param.Array {
|
||||
if param.OptionalArray != nil {
|
||||
for i, value := range param.OptionalArray.Array {
|
||||
env = append(env, fmt.Sprintf("%s_%d=%s", envBaseName, i, value))
|
||||
}
|
||||
}
|
||||
@@ -588,9 +724,28 @@ type ApplicationSourcePlugin struct {
|
||||
Parameters ApplicationSourcePluginParameters `json:"parameters,omitempty" protobuf:"bytes,3,opt,name=parameters"`
|
||||
}
|
||||
|
||||
func (c *ApplicationSourcePlugin) Equals(other *ApplicationSourcePlugin) bool {
|
||||
if c == nil && other == nil {
|
||||
return true
|
||||
}
|
||||
if c == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
if !c.Parameters.Equals(other.Parameters) {
|
||||
return false
|
||||
}
|
||||
// DeepEqual works fine for fields besides Parameters. Since we already know that Parameters are equal, we can
|
||||
// set them to nil and then do a DeepEqual.
|
||||
leftCopy := c.DeepCopy()
|
||||
rightCopy := other.DeepCopy()
|
||||
leftCopy.Parameters = nil
|
||||
rightCopy.Parameters = nil
|
||||
return reflect.DeepEqual(leftCopy, rightCopy)
|
||||
}
|
||||
|
||||
// IsZero returns true if the ApplicationSourcePlugin is considered empty
|
||||
func (c *ApplicationSourcePlugin) IsZero() bool {
|
||||
return c == nil || c.Name == "" && c.Env.IsZero()
|
||||
return c == nil || c.Name == "" && c.Env.IsZero() && c.Parameters.IsZero()
|
||||
}
|
||||
|
||||
// AddEnvEntry merges an EnvEntry into a list of entries. If an entry with the same name already exists,
|
||||
@@ -2417,8 +2572,23 @@ func (condition *ApplicationCondition) IsError() bool {
|
||||
}
|
||||
|
||||
// Equals compares two instances of ApplicationSource and return true if instances are equal.
|
||||
func (source *ApplicationSource) Equals(other ApplicationSource) bool {
|
||||
return reflect.DeepEqual(*source, other)
|
||||
func (source *ApplicationSource) Equals(other *ApplicationSource) bool {
|
||||
if source == nil && other == nil {
|
||||
return true
|
||||
}
|
||||
if source == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
if !source.Plugin.Equals(other.Plugin) {
|
||||
return false
|
||||
}
|
||||
// reflect.DeepEqual works fine for the other fields. Since the plugin fields are equal, set them to null so they're
|
||||
// not considered in the DeepEqual comparison.
|
||||
sourceCopy := source.DeepCopy()
|
||||
otherCopy := other.DeepCopy()
|
||||
sourceCopy.Plugin = nil
|
||||
otherCopy.Plugin = nil
|
||||
return reflect.DeepEqual(sourceCopy, otherCopy)
|
||||
}
|
||||
|
||||
// ExplicitType returns the type (e.g. Helm, Kustomize, etc) of the application. If either none or multiple types are defined, returns an error.
|
||||
|
||||
@@ -3,7 +3,7 @@ package v1alpha1
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
fmt "fmt"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"reflect"
|
||||
@@ -11,10 +11,11 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
argocdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/utils/pointer"
|
||||
|
||||
argocdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -863,9 +864,9 @@ func TestAppSourceEquality(t *testing.T) {
|
||||
},
|
||||
}
|
||||
right := left.DeepCopy()
|
||||
assert.True(t, left.Equals(*right))
|
||||
assert.True(t, left.Equals(right))
|
||||
right.Directory.Recurse = false
|
||||
assert.False(t, left.Equals(*right))
|
||||
assert.False(t, left.Equals(right))
|
||||
}
|
||||
|
||||
func TestAppDestinationEquality(t *testing.T) {
|
||||
@@ -3261,8 +3262,8 @@ func TestApplicationSourcePluginParameters_Environ_string(t *testing.T) {
|
||||
func TestApplicationSourcePluginParameters_Environ_array(t *testing.T) {
|
||||
params := ApplicationSourcePluginParameters{
|
||||
{
|
||||
Name: "dependencies",
|
||||
Array: []string{"redis", "minio"},
|
||||
Name: "dependencies",
|
||||
OptionalArray: &OptionalArray{Array: []string{"redis", "minio"}},
|
||||
},
|
||||
}
|
||||
environ, err := params.Environ()
|
||||
@@ -3279,9 +3280,11 @@ func TestApplicationSourcePluginParameters_Environ_map(t *testing.T) {
|
||||
params := ApplicationSourcePluginParameters{
|
||||
{
|
||||
Name: "helm-parameters",
|
||||
Map: map[string]string{
|
||||
"image.repo": "quay.io/argoproj/argo-cd",
|
||||
"image.tag": "v2.4.0",
|
||||
OptionalMap: &OptionalMap{
|
||||
Map: map[string]string{
|
||||
"image.repo": "quay.io/argoproj/argo-cd",
|
||||
"image.tag": "v2.4.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -3302,10 +3305,14 @@ func TestApplicationSourcePluginParameters_Environ_all(t *testing.T) {
|
||||
{
|
||||
Name: "some-name",
|
||||
String_: pointer.String("1.2.3"),
|
||||
Array: []string{"redis", "minio"},
|
||||
Map: map[string]string{
|
||||
"image.repo": "quay.io/argoproj/argo-cd",
|
||||
"image.tag": "v2.4.0",
|
||||
OptionalArray: &OptionalArray{
|
||||
Array: []string{"redis", "minio"},
|
||||
},
|
||||
OptionalMap: &OptionalMap{
|
||||
Map: map[string]string{
|
||||
"image.repo": "quay.io/argoproj/argo-cd",
|
||||
"image.tag": "v2.4.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -3401,3 +3408,89 @@ func TestGetSources(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalArrayEquality(t *testing.T) {
|
||||
// Demonstrate that the JSON unmarshalling of an empty array parameter is an OptionalArray with the array field set
|
||||
// to an empty array.
|
||||
presentButEmpty := `{"array":[]}`
|
||||
param := ApplicationSourcePluginParameter{}
|
||||
err := json.Unmarshal([]byte(presentButEmpty), ¶m)
|
||||
require.NoError(t, err)
|
||||
jsonPresentButEmpty := param.OptionalArray
|
||||
require.Equal(t, &OptionalArray{Array: []string{}}, jsonPresentButEmpty)
|
||||
|
||||
// We won't simulate the protobuf unmarshalling of an empty array parameter. By experimentation, this is how it's
|
||||
// unmarshalled.
|
||||
protobufPresentButEmpty := &OptionalArray{Array: nil}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a *OptionalArray
|
||||
b *OptionalArray
|
||||
expected bool
|
||||
}{
|
||||
{"nil and nil", nil, nil, true},
|
||||
{"nil and empty", nil, jsonPresentButEmpty, false},
|
||||
{"nil and empty-containing-nil", nil, protobufPresentButEmpty, false},
|
||||
{"empty-containing-empty and nil", jsonPresentButEmpty, nil, false},
|
||||
{"empty-containing-nil and nil", protobufPresentButEmpty, nil, false},
|
||||
{"empty-containing-empty and empty-containing-empty", jsonPresentButEmpty, jsonPresentButEmpty, true},
|
||||
{"empty-containing-empty and empty-containing-nil", jsonPresentButEmpty, protobufPresentButEmpty, true},
|
||||
{"empty-containing-nil and empty-containing-empty", protobufPresentButEmpty, jsonPresentButEmpty, true},
|
||||
{"empty-containing-nil and empty-containing-nil", protobufPresentButEmpty, protobufPresentButEmpty, true},
|
||||
{"empty-containing-empty and non-empty", jsonPresentButEmpty, &OptionalArray{Array: []string{"a"}}, false},
|
||||
{"non-empty and empty-containing-nil", &OptionalArray{Array: []string{"a"}}, jsonPresentButEmpty, false},
|
||||
{"non-empty and non-empty", &OptionalArray{Array: []string{"a"}}, &OptionalArray{Array: []string{"a"}}, true},
|
||||
{"non-empty and non-empty different", &OptionalArray{Array: []string{"a"}}, &OptionalArray{Array: []string{"b"}}, false},
|
||||
}
|
||||
for _, testCase := range tests {
|
||||
testCopy := testCase
|
||||
t.Run(testCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, testCopy.expected, testCopy.a.Equals(testCopy.b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOptionalMapEquality(t *testing.T) {
|
||||
// Demonstrate that the JSON unmarshalling of an empty map parameter is an OptionalMap with the map field set
|
||||
// to an empty map.
|
||||
presentButEmpty := `{"map":{}}`
|
||||
param := ApplicationSourcePluginParameter{}
|
||||
err := json.Unmarshal([]byte(presentButEmpty), ¶m)
|
||||
require.NoError(t, err)
|
||||
jsonPresentButEmpty := param.OptionalMap
|
||||
require.Equal(t, &OptionalMap{Map: map[string]string{}}, jsonPresentButEmpty)
|
||||
|
||||
// We won't simulate the protobuf unmarshalling of an empty map parameter. By experimentation, this is how it's
|
||||
// unmarshalled.
|
||||
protobufPresentButEmpty := &OptionalMap{Map: nil}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
a *OptionalMap
|
||||
b *OptionalMap
|
||||
expected bool
|
||||
}{
|
||||
{"nil and nil", nil, nil, true},
|
||||
{"nil and empty-containing-empty", nil, jsonPresentButEmpty, false},
|
||||
{"nil and empty-containing-nil", nil, protobufPresentButEmpty, false},
|
||||
{"empty-containing-empty and nil", jsonPresentButEmpty, nil, false},
|
||||
{"empty-containing-nil and nil", protobufPresentButEmpty, nil, false},
|
||||
{"empty-containing-empty and empty-containing-empty", jsonPresentButEmpty, jsonPresentButEmpty, true},
|
||||
{"empty-containing-empty and empty-containing-nil", jsonPresentButEmpty, protobufPresentButEmpty, true},
|
||||
{"empty-containing-empty and non-empty", jsonPresentButEmpty, &OptionalMap{Map: map[string]string{"a": "b"}}, false},
|
||||
{"empty-containing-nil and empty-containing-empty", protobufPresentButEmpty, jsonPresentButEmpty, true},
|
||||
{"empty-containing-nil and empty-containing-nil", protobufPresentButEmpty, protobufPresentButEmpty, true},
|
||||
{"non-empty and empty-containing-empty", &OptionalMap{Map: map[string]string{"a": "b"}}, jsonPresentButEmpty, false},
|
||||
{"non-empty and non-empty", &OptionalMap{Map: map[string]string{"a": "b"}}, &OptionalMap{Map: map[string]string{"a": "b"}}, true},
|
||||
{"non-empty and non-empty different", &OptionalMap{Map: map[string]string{"a": "b"}}, &OptionalMap{Map: map[string]string{"a": "c"}}, false},
|
||||
}
|
||||
for _, testCase := range tests {
|
||||
testCopy := testCase
|
||||
t.Run(testCopy.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
assert.Equal(t, testCopy.expected, testCopy.a.Equals(testCopy.b))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1051,17 +1051,15 @@ func (in *ApplicationSourcePluginParameter) DeepCopyInto(out *ApplicationSourceP
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Map != nil {
|
||||
in, out := &in.Map, &out.Map
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
if in.OptionalMap != nil {
|
||||
in, out := &in.OptionalMap, &out.OptionalMap
|
||||
*out = new(OptionalMap)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
if in.Array != nil {
|
||||
in, out := &in.Array, &out.Array
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
if in.OptionalArray != nil {
|
||||
in, out := &in.OptionalArray, &out.OptionalArray
|
||||
*out = new(OptionalArray)
|
||||
(*in).DeepCopyInto(*out)
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -2307,6 +2305,50 @@ func (in *OperationState) DeepCopy() *OperationState {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OptionalArray) DeepCopyInto(out *OptionalArray) {
|
||||
*out = *in
|
||||
if in.Array != nil {
|
||||
in, out := &in.Array, &out.Array
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionalArray.
|
||||
func (in *OptionalArray) DeepCopy() *OptionalArray {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OptionalArray)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OptionalMap) DeepCopyInto(out *OptionalMap) {
|
||||
*out = *in
|
||||
if in.Map != nil {
|
||||
in, out := &in.Map, &out.Map
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OptionalMap.
|
||||
func (in *OptionalMap) DeepCopy() *OptionalMap {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(OptionalMap)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *OrphanedResourceKey) DeepCopyInto(out *OrphanedResourceKey) {
|
||||
*out = *in
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"context"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/kube"
|
||||
"github.com/argoproj/gitops-engine/pkg/utils/text"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -559,7 +558,8 @@ func (s *Server) isRepoPermittedInProject(ctx context.Context, repo string, proj
|
||||
// isSourceInHistory checks if the supplied application source is either our current application
|
||||
// source, or was something which we synced to previously.
|
||||
func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSource) bool {
|
||||
if source.Equals(app.Spec.GetSource()) {
|
||||
appSource := app.Spec.GetSource()
|
||||
if source.Equals(&appSource) {
|
||||
return true
|
||||
}
|
||||
// Iterate history. When comparing items in our history, use the actual synced revision to
|
||||
@@ -568,7 +568,7 @@ func isSourceInHistory(app *v1alpha1.Application, source v1alpha1.ApplicationSou
|
||||
// history[].revision will contain the explicit SHA
|
||||
for _, h := range app.Status.History {
|
||||
h.Source.TargetRevision = h.Revision
|
||||
if source.Equals(h.Source) {
|
||||
if source.Equals(&h.Source) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ metadata:
|
||||
name: cmp-plugin
|
||||
spec:
|
||||
version: v1.0
|
||||
init:
|
||||
command: [env]
|
||||
generate:
|
||||
command: [sh, -c, 'kustomize build']
|
||||
discover:
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import {AutocompleteField, DataLoader, FormField, FormSelect, getNestedField} from 'argo-ui';
|
||||
import * as React from 'react';
|
||||
import {FieldApi, FormApi, FormField as ReactFormField, Text, TextArea} from 'react-form';
|
||||
|
||||
import {ArrayInputField, CheckboxField, EditablePanel, EditablePanelItem, Expandable, TagsInputField} from '../../../shared/components';
|
||||
import {cloneDeep} from 'lodash-es';
|
||||
import {
|
||||
ArrayInputField,
|
||||
ArrayValueField,
|
||||
CheckboxField,
|
||||
EditablePanel,
|
||||
EditablePanelItem,
|
||||
Expandable,
|
||||
MapValueField,
|
||||
NameValueEditor,
|
||||
StringValueField,
|
||||
NameValue,
|
||||
TagsInputField,
|
||||
ValueEditor
|
||||
} from '../../../shared/components';
|
||||
import * as models from '../../../shared/models';
|
||||
import {ApplicationSourceDirectory, Plugin} from '../../../shared/models';
|
||||
import {services} from '../../../shared/services';
|
||||
@@ -117,6 +130,7 @@ export const ApplicationParameters = (props: {
|
||||
const [removedOverrides, setRemovedOverrides] = React.useState(new Array<boolean>());
|
||||
|
||||
let attributes: EditablePanelItem[] = [];
|
||||
const [appParamsDeletedState, setAppParamsDeletedState] = React.useState([]);
|
||||
|
||||
if (props.details.type === 'Kustomize' && props.details.kustomize) {
|
||||
attributes.push({
|
||||
@@ -262,7 +276,7 @@ export const ApplicationParameters = (props: {
|
||||
} else if (props.details.type === 'Plugin') {
|
||||
attributes.push({
|
||||
title: 'NAME',
|
||||
view: source.plugin && source.plugin.name,
|
||||
view: <div style={{marginTop: 15, marginBottom: 5}}>{ValueEditor(app.spec.source.plugin && app.spec.source.plugin.name, null)}</div>,
|
||||
edit: (formApi: FormApi) => (
|
||||
<DataLoader load={() => services.authService.plugins()}>
|
||||
{(plugins: Plugin[]) => (
|
||||
@@ -273,39 +287,160 @@ export const ApplicationParameters = (props: {
|
||||
});
|
||||
attributes.push({
|
||||
title: 'ENV',
|
||||
view: source.plugin && (source.plugin.env || []).map(i => `${i.name}='${i.value}'`).join(' '),
|
||||
view: (
|
||||
<div style={{marginTop: 15}}>
|
||||
{app.spec.source.plugin &&
|
||||
(app.spec.source.plugin.env || []).map(val => (
|
||||
<span key={val.name} style={{display: 'block', marginBottom: 5}}>
|
||||
{NameValueEditor(val, null)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
edit: (formApi: FormApi) => <FormField field='spec.source.plugin.env' formApi={formApi} component={ArrayInputField} />
|
||||
});
|
||||
if (props.details.plugin.parametersAnnouncement) {
|
||||
const parametersSet = new Set<string>();
|
||||
if (props.details?.plugin?.parametersAnnouncement) {
|
||||
for (const announcement of props.details.plugin.parametersAnnouncement) {
|
||||
const liveParam = app.spec.source.plugin.parameters?.find(param => param.name === announcement.name);
|
||||
if (announcement.collectionType === undefined || announcement.collectionType === '' || announcement.collectionType === 'string') {
|
||||
attributes.push({
|
||||
title: announcement.title ?? announcement.name,
|
||||
view: liveParam?.string || announcement.string,
|
||||
edit: () => liveParam?.string || announcement.string
|
||||
});
|
||||
} else if (announcement.collectionType === 'array') {
|
||||
attributes.push({
|
||||
title: announcement.title ?? announcement.name,
|
||||
view: (liveParam?.array || announcement.array || []).join(' '),
|
||||
edit: () => (liveParam?.array || announcement.array || []).join(' ')
|
||||
});
|
||||
} else if (announcement.collectionType === 'map') {
|
||||
const entries = concatMaps(announcement.map, liveParam?.map).entries();
|
||||
attributes.push({
|
||||
title: announcement.title ?? announcement.name,
|
||||
view: Array.from(entries)
|
||||
.map(([key, value]) => `${key}='${value}'`)
|
||||
.join(' '),
|
||||
edit: () =>
|
||||
Array.from(entries)
|
||||
.map(([key, value]) => `${key}='${value}'`)
|
||||
.join(' ')
|
||||
});
|
||||
}
|
||||
parametersSet.add(announcement.name);
|
||||
}
|
||||
}
|
||||
if (app.spec.source.plugin?.parameters) {
|
||||
for (const appParameter of app.spec.source.plugin.parameters) {
|
||||
parametersSet.add(appParameter.name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of appParamsDeletedState) {
|
||||
parametersSet.delete(key);
|
||||
}
|
||||
parametersSet.forEach(name => {
|
||||
const announcement = props.details.plugin.parametersAnnouncement?.find(param => param.name === name);
|
||||
const liveParam = app.spec.source.plugin?.parameters?.find(param => param.name === name);
|
||||
const pluginIcon =
|
||||
announcement && liveParam ? 'This parameter has been provided by plugin, but is overridden in application manifest.' : 'This parameter is provided by the plugin.';
|
||||
const isPluginPar = announcement ? true : false;
|
||||
if ((announcement?.collectionType === undefined && liveParam?.map) || announcement?.collectionType === 'map') {
|
||||
let liveParamMap;
|
||||
if (liveParam) {
|
||||
liveParamMap = liveParam.map ?? new Map<string, string>();
|
||||
}
|
||||
const map = concatMaps(liveParamMap ?? announcement?.map, new Map<string, string>());
|
||||
const entries = map.entries();
|
||||
const items = new Array<NameValue>();
|
||||
Array.from(entries).forEach(([key, value]) => items.push({name: key, value: `${value}`}));
|
||||
attributes.push({
|
||||
title: announcement?.title ?? announcement?.name ?? name,
|
||||
customTitle: (
|
||||
<span>
|
||||
{isPluginPar && <i className='fa solid fa-puzzle-piece' title={pluginIcon} style={{marginRight: 5}} />}
|
||||
{announcement?.title ?? announcement?.name ?? name}
|
||||
</span>
|
||||
),
|
||||
view: (
|
||||
<div style={{marginTop: 15, marginBottom: 5}}>
|
||||
{items.length === 0 && <span style={{color: 'dimgray'}}>-- NO ITEMS --</span>}
|
||||
{items.map(val => (
|
||||
<span key={val.name} style={{display: 'block', marginBottom: 5}}>
|
||||
{NameValueEditor(val)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
edit: (formApi: FormApi) => (
|
||||
<FormField
|
||||
field='spec.source.plugin.parameters'
|
||||
componentProps={{
|
||||
name: announcement?.title ?? announcement?.name ?? name,
|
||||
defaultVal: announcement?.map,
|
||||
isPluginPar,
|
||||
setAppParamsDeletedState
|
||||
}}
|
||||
formApi={formApi}
|
||||
component={MapValueField}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if ((announcement?.collectionType === undefined && liveParam?.array) || announcement?.collectionType === 'array') {
|
||||
let liveParamArray;
|
||||
if (liveParam) {
|
||||
liveParamArray = liveParam?.array ?? [];
|
||||
}
|
||||
attributes.push({
|
||||
title: announcement?.title ?? announcement?.name ?? name,
|
||||
customTitle: (
|
||||
<span>
|
||||
{isPluginPar && <i className='fa-solid fa-puzzle-piece' title={pluginIcon} style={{marginRight: 5}} />}
|
||||
{announcement?.title ?? announcement?.name ?? name}
|
||||
</span>
|
||||
),
|
||||
view: (
|
||||
<div style={{marginTop: 15, marginBottom: 5}}>
|
||||
{(liveParamArray ?? announcement?.array ?? []).length === 0 && <span style={{color: 'dimgray'}}>-- NO ITEMS --</span>}
|
||||
{(liveParamArray ?? announcement?.array ?? []).map((val, index) => (
|
||||
<span key={index} style={{display: 'block', marginBottom: 5}}>
|
||||
{ValueEditor(val, null)}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
edit: (formApi: FormApi) => (
|
||||
<FormField
|
||||
field='spec.source.plugin.parameters'
|
||||
componentProps={{
|
||||
name: announcement?.title ?? announcement?.name ?? name,
|
||||
defaultVal: announcement?.array,
|
||||
isPluginPar,
|
||||
setAppParamsDeletedState
|
||||
}}
|
||||
formApi={formApi}
|
||||
component={ArrayValueField}
|
||||
/>
|
||||
)
|
||||
});
|
||||
} else if (
|
||||
(announcement?.collectionType === undefined && liveParam?.string) ||
|
||||
announcement?.collectionType === '' ||
|
||||
announcement?.collectionType === 'string' ||
|
||||
announcement?.collectionType === undefined
|
||||
) {
|
||||
let liveParamString;
|
||||
if (liveParam) {
|
||||
liveParamString = liveParam?.string ?? '';
|
||||
}
|
||||
attributes.push({
|
||||
title: announcement?.title ?? announcement?.name ?? name,
|
||||
customTitle: (
|
||||
<span>
|
||||
{isPluginPar && <i className='fa-solid fa-puzzle-piece' title={pluginIcon} style={{marginRight: 5}} />}
|
||||
{announcement?.title ?? announcement?.name ?? name}
|
||||
</span>
|
||||
),
|
||||
view: (
|
||||
<div
|
||||
style={{
|
||||
marginTop: 15,
|
||||
marginBottom: 5
|
||||
}}>
|
||||
{ValueEditor(liveParamString ?? announcement?.string, null)}
|
||||
</div>
|
||||
),
|
||||
edit: (formApi: FormApi) => (
|
||||
<FormField
|
||||
field='spec.source.plugin.parameters'
|
||||
componentProps={{
|
||||
name: announcement?.title ?? announcement?.name ?? name,
|
||||
defaultVal: announcement?.string,
|
||||
isPluginPar,
|
||||
setAppParamsDeletedState
|
||||
}}
|
||||
formApi={formApi}
|
||||
component={StringValueField}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if (props.details.type === 'Directory') {
|
||||
const directory = source.directory || ({} as ApplicationSourceDirectory);
|
||||
attributes.push({
|
||||
@@ -351,6 +486,7 @@ export const ApplicationParameters = (props: {
|
||||
props.save &&
|
||||
(async (input: models.Application) => {
|
||||
const src = getAppDefaultSource(input);
|
||||
|
||||
function isDefined(item: any) {
|
||||
return item !== null && item !== undefined;
|
||||
}
|
||||
@@ -364,11 +500,30 @@ export const ApplicationParameters = (props: {
|
||||
if (src.kustomize && src.kustomize.images) {
|
||||
src.kustomize.images = src.kustomize.images.filter(isDefinedWithVersion);
|
||||
}
|
||||
|
||||
let params = input.spec?.source?.plugin?.parameters;
|
||||
if (params) {
|
||||
for (const param of params) {
|
||||
if (param.map && param.array) {
|
||||
// @ts-ignore
|
||||
param.map = param.array.reduce((acc, {name, value}) => {
|
||||
// @ts-ignore
|
||||
acc[name] = value;
|
||||
return acc;
|
||||
}, {});
|
||||
delete param.array;
|
||||
}
|
||||
}
|
||||
|
||||
params = params.filter(param => !appParamsDeletedState.includes(param.name));
|
||||
input.spec.source.plugin.parameters = params;
|
||||
}
|
||||
|
||||
await props.save(input, {});
|
||||
setRemovedOverrides(new Array<boolean>());
|
||||
})
|
||||
}
|
||||
values={app}
|
||||
values={((props.details.plugin || app?.spec?.source?.plugin) && cloneDeep(app)) || app}
|
||||
validate={updatedApp => {
|
||||
const errors = {} as any;
|
||||
|
||||
@@ -379,6 +534,12 @@ export const ApplicationParameters = (props: {
|
||||
|
||||
return errors;
|
||||
}}
|
||||
onModeSwitch={
|
||||
props.details.plugin &&
|
||||
(() => {
|
||||
setAppParamsDeletedState([]);
|
||||
})
|
||||
}
|
||||
title={props.details.type.toLocaleUpperCase()}
|
||||
items={attributes}
|
||||
noReadonlyMode={props.noReadonlyMode}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactForm from 'react-form';
|
||||
import {FormValue} from 'react-form';
|
||||
|
||||
/*
|
||||
This provide a way to may a form field to an array of items. It allows you to
|
||||
@@ -26,32 +27,53 @@ export interface NameValue {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export const NameValueEditor = (item: NameValue, onChange: (item: NameValue) => any) => (
|
||||
<React.Fragment>
|
||||
export const NameValueEditor = (item: NameValue, onChange?: (item: NameValue) => any) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input
|
||||
// disable chrome autocomplete
|
||||
autoComplete='fake'
|
||||
className='argo-field'
|
||||
style={{width: '40%', borderColor: !onChange ? '#eff3f5' : undefined}}
|
||||
placeholder='Name'
|
||||
value={item.name}
|
||||
onChange={e => onChange({...item, name: e.target.value})}
|
||||
// onBlur={e=>onChange({...item, name: e.target.value})}
|
||||
title='Name'
|
||||
readOnly={!onChange}
|
||||
/>
|
||||
=
|
||||
<input
|
||||
// disable chrome autocomplete
|
||||
autoComplete='fake'
|
||||
className='argo-field'
|
||||
style={{width: '40%', borderColor: !onChange ? '#eff3f5' : undefined}}
|
||||
placeholder='Value'
|
||||
value={item.value || ''}
|
||||
onChange={e => onChange({...item, value: e.target.value})}
|
||||
title='Value'
|
||||
readOnly={!onChange}
|
||||
/>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export const ValueEditor = (item: string, onChange: (item: string) => any) => {
|
||||
return (
|
||||
<input
|
||||
// disable chrome autocomplete
|
||||
autoComplete='fake'
|
||||
className='argo-field'
|
||||
style={{width: '40%'}}
|
||||
placeholder='Name'
|
||||
value={item.name || ''}
|
||||
onChange={e => onChange({...item, name: e.target.value})}
|
||||
title='Name'
|
||||
/>
|
||||
=
|
||||
<input
|
||||
// disable chrome autocomplete
|
||||
autoComplete='fake'
|
||||
className='argo-field'
|
||||
style={{width: '40%'}}
|
||||
style={{width: '40%', borderColor: !onChange ? '#eff3f5' : undefined}}
|
||||
placeholder='Value'
|
||||
value={item.value || ''}
|
||||
onChange={e => onChange({...item, value: e.target.value})}
|
||||
value={item || ''}
|
||||
onChange={e => onChange(e.target.value)}
|
||||
title='Value'
|
||||
readOnly={!onChange}
|
||||
/>
|
||||
|
||||
</React.Fragment>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
interface Props<T> {
|
||||
items: T[];
|
||||
@@ -97,6 +119,50 @@ export function ArrayInput<T>(props: Props<T>) {
|
||||
);
|
||||
}
|
||||
|
||||
export const ResetOrDeleteButton = (props: {
|
||||
isPluginPar: boolean;
|
||||
getValue: () => FormValue;
|
||||
name: string;
|
||||
index: number;
|
||||
setValue: (value: FormValue) => void;
|
||||
setAppParamsDeletedState: any;
|
||||
}) => {
|
||||
const handleDeleteChange = () => {
|
||||
if (props.index >= 0) {
|
||||
props.setAppParamsDeletedState((val: string[]) => val.concat(props.name));
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetChange = () => {
|
||||
if (props.index >= 0) {
|
||||
const items = [...props.getValue()];
|
||||
items.splice(props.index, 1);
|
||||
props.setValue(items);
|
||||
}
|
||||
};
|
||||
|
||||
const disabled = props.index === -1;
|
||||
|
||||
const content = props.isPluginPar ? 'Reset' : 'Delete';
|
||||
let tooltip = '';
|
||||
if (content === 'Reset' && !disabled) {
|
||||
tooltip = 'Resets the parameter to the value provided by the plugin. This removes the parameter override from the application manifest';
|
||||
} else if (content === 'Delete' && !disabled) {
|
||||
tooltip = 'Deletes this parameter values from the application manifest.';
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className='argo-button argo-button--base'
|
||||
disabled={disabled}
|
||||
title={tooltip}
|
||||
style={{fontSize: '12px', display: 'flex', marginLeft: 'auto', marginTop: '8px'}}
|
||||
onClick={props.isPluginPar ? handleResetChange : handleDeleteChange}>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArrayInputField = ReactForm.FormField((props: {fieldApi: ReactForm.FieldApi}) => {
|
||||
const {
|
||||
fieldApi: {getValue, setValue}
|
||||
@@ -104,6 +170,95 @@ export const ArrayInputField = ReactForm.FormField((props: {fieldApi: ReactForm.
|
||||
return <ArrayInput editor={NameValueEditor} items={getValue() || []} onChange={setValue} />;
|
||||
});
|
||||
|
||||
export const ArrayValueField = ReactForm.FormField(
|
||||
(props: {fieldApi: ReactForm.FieldApi; name: string; defaultVal: string[]; isPluginPar: boolean; setAppParamsDeletedState: any}) => {
|
||||
const {
|
||||
fieldApi: {getValue, setValue}
|
||||
} = props;
|
||||
|
||||
let liveParamArray;
|
||||
const liveParam = getValue()?.find((val: {name: string; array: object}) => val.name === props.name);
|
||||
if (liveParam) {
|
||||
liveParamArray = liveParam?.array ?? [];
|
||||
}
|
||||
const index = getValue()?.findIndex((val: {name: string; array: object}) => val.name === props.name) ?? -1;
|
||||
const values = liveParamArray ?? props.defaultVal ?? [];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ResetOrDeleteButton
|
||||
isPluginPar={props.isPluginPar}
|
||||
getValue={getValue}
|
||||
name={props.name}
|
||||
index={index}
|
||||
setValue={setValue}
|
||||
setAppParamsDeletedState={props.setAppParamsDeletedState}
|
||||
/>
|
||||
<ArrayInput
|
||||
editor={ValueEditor}
|
||||
items={values || []}
|
||||
onChange={change => {
|
||||
const update = change.map((val: string | object) => (typeof val !== 'string' ? '' : val));
|
||||
if (index >= 0) {
|
||||
getValue()[index].array = update;
|
||||
setValue([...getValue()]);
|
||||
} else {
|
||||
setValue([...(getValue() || []), {name: props.name, array: update}]);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const StringValueField = ReactForm.FormField(
|
||||
(props: {fieldApi: ReactForm.FieldApi; name: string; defaultVal: string; isPluginPar: boolean; setAppParamsDeletedState: any}) => {
|
||||
const {
|
||||
fieldApi: {getValue, setValue}
|
||||
} = props;
|
||||
let liveParamString;
|
||||
const liveParam = getValue()?.find((val: {name: string; string: string}) => val.name === props.name);
|
||||
if (liveParam) {
|
||||
liveParamString = liveParam?.string ? liveParam?.string : '';
|
||||
}
|
||||
const values = liveParamString ?? props.defaultVal ?? '';
|
||||
const index = getValue()?.findIndex((val: {name: string; string: string}) => val.name === props.name) ?? -1;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ResetOrDeleteButton
|
||||
isPluginPar={props.isPluginPar}
|
||||
getValue={getValue}
|
||||
name={props.name}
|
||||
index={index}
|
||||
setValue={setValue}
|
||||
setAppParamsDeletedState={props.setAppParamsDeletedState}
|
||||
/>
|
||||
<div>
|
||||
<input
|
||||
// disable chrome autocomplete
|
||||
autoComplete='fake'
|
||||
className='argo-field'
|
||||
style={{width: '40%', display: 'inline-block', marginTop: 25}}
|
||||
placeholder='Value'
|
||||
value={values || ''}
|
||||
onChange={e => {
|
||||
if (index >= 0) {
|
||||
getValue()[index].string = e.target.value;
|
||||
setValue([...getValue()]);
|
||||
} else {
|
||||
setValue([...(getValue() || []), {name: props.name, string: e.target.value}]);
|
||||
}
|
||||
}}
|
||||
title='Value'
|
||||
/>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export const MapInputField = ReactForm.FormField((props: {fieldApi: ReactForm.FieldApi}) => {
|
||||
const {
|
||||
fieldApi: {getValue, setValue}
|
||||
@@ -123,3 +278,55 @@ export const MapInputField = ReactForm.FormField((props: {fieldApi: ReactForm.Fi
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
export const MapValueField = ReactForm.FormField(
|
||||
(props: {fieldApi: ReactForm.FieldApi; name: string; defaultVal: Map<string, string>; isPluginPar: boolean; setAppParamsDeletedState: any}) => {
|
||||
const {
|
||||
fieldApi: {getValue, setValue}
|
||||
} = props;
|
||||
const items = new Array<NameValue>();
|
||||
const liveParam = getValue()?.find((val: {name: string; map: object}) => val.name === props.name);
|
||||
const index = getValue()?.findIndex((val: {name: string; map: object}) => val.name === props.name) ?? -1;
|
||||
if (liveParam) {
|
||||
liveParam.map = liveParam.map ? liveParam.map : new Map<string, string>();
|
||||
}
|
||||
if (liveParam?.array) {
|
||||
items.push(...liveParam.array);
|
||||
} else {
|
||||
const map = liveParam?.map ?? props.defaultVal ?? new Map<string, string>();
|
||||
Object.keys(map).forEach(item => items.push({name: item || '', value: map[item] || ''}));
|
||||
if (liveParam?.map) {
|
||||
getValue()[index].array = items;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ResetOrDeleteButton
|
||||
isPluginPar={props.isPluginPar}
|
||||
getValue={getValue}
|
||||
name={props.name}
|
||||
index={index}
|
||||
setValue={setValue}
|
||||
setAppParamsDeletedState={props.setAppParamsDeletedState}
|
||||
/>
|
||||
|
||||
<ArrayInput
|
||||
editor={NameValueEditor}
|
||||
items={items || []}
|
||||
onChange={change => {
|
||||
if (index === -1) {
|
||||
getValue().push({
|
||||
name: props.name,
|
||||
array: change
|
||||
});
|
||||
} else {
|
||||
getValue()[index].array = change;
|
||||
}
|
||||
setValue([...getValue()]);
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -3,12 +3,12 @@ import * as classNames from 'classnames';
|
||||
import * as React from 'react';
|
||||
import {Form, FormApi} from 'react-form';
|
||||
import {helpTip} from '../../../applications/components/utils';
|
||||
|
||||
import {Consumer} from '../../context';
|
||||
import {Spinner} from '../spinner';
|
||||
|
||||
export interface EditablePanelItem {
|
||||
title: string;
|
||||
customTitle?: string | React.ReactNode;
|
||||
key?: string;
|
||||
before?: React.ReactNode;
|
||||
view: string | React.ReactNode;
|
||||
@@ -104,7 +104,7 @@ export class EditablePanel<T = {}> extends React.Component<EditablePanelProps<T>
|
||||
<React.Fragment key={item.key || item.title}>
|
||||
{item.before}
|
||||
<div className='row white-box__details-row'>
|
||||
<div className='columns small-3'>{item.title}</div>
|
||||
<div className='columns small-3'>{item.customTitle || item.title}</div>
|
||||
<div className='columns small-9'>{item.view}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
@@ -142,7 +142,7 @@ export class EditablePanel<T = {}> extends React.Component<EditablePanelProps<T>
|
||||
<React.Fragment key={item.key || item.title}>
|
||||
{item.before}
|
||||
<div className='row white-box__details-row'>
|
||||
<div className='columns small-3'>{(item.titleEdit && item.titleEdit(api)) || item.title}</div>
|
||||
<div className='columns small-3'>{(item.titleEdit && item.titleEdit(api)) || item.customTitle || item.title}</div>
|
||||
<div className='columns small-9'>{(item.edit && item.edit(api)) || item.view}</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -143,8 +143,13 @@ func matchRepositoryCMP(ctx context.Context, repoPath string, client pluginclien
|
||||
}
|
||||
|
||||
func cmpSupports(ctx context.Context, pluginSockFilePath, repoPath, fileName string, env []string, tarExcludedGlobs []string, namedPlugin bool) (io.Closer, pluginclient.ConfigManagementPluginServiceClient, bool) {
|
||||
address := filepath.Join(pluginSockFilePath, fileName)
|
||||
if !files.Inbound(address, pluginSockFilePath) {
|
||||
absPluginSockFilePath, err := filepath.Abs(pluginSockFilePath)
|
||||
if err != nil {
|
||||
log.Errorf("error getting absolute path for plugin socket dir %v, %v", pluginSockFilePath, err)
|
||||
return nil, nil, false
|
||||
}
|
||||
address := filepath.Join(absPluginSockFilePath, fileName)
|
||||
if !files.Inbound(address, absPluginSockFilePath) {
|
||||
log.Errorf("invalid socket file path, %v is outside plugin socket dir %v", fileName, pluginSockFilePath)
|
||||
return nil, nil, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user