@@ -827,20 +827,155 @@ func compileCmdAndArgs(executorInput *pipelinespec.ExecutorInput, cmd string, ar
827827 executorInputJSONString := string (executorInputJSON )
828828
829829 compiledCmd := strings .ReplaceAll (cmd , executorInputJSONKey , executorInputJSONString )
830- compiledArgs := make ([]string , 0 , len (args ))
831830 for placeholder , replacement := range placeholders {
832- cmd = strings .ReplaceAll (cmd , placeholder , replacement )
831+ compiledCmd = strings .ReplaceAll (compiledCmd , placeholder , replacement )
833832 }
833+
834+ compiledArgs := make ([]string , 0 , len (args ))
835+ providedInputs := getProvidedInputs (executorInput )
834836 for _ , arg := range args {
835- compiledArgTemplate := strings .ReplaceAll (arg , executorInputJSONKey , executorInputJSONString )
836- for placeholder , replacement := range placeholders {
837- compiledArgTemplate = strings .ReplaceAll (compiledArgTemplate , placeholder , replacement )
837+ expandedArgs , err := resolveStructPlaceholders (arg , providedInputs )
838+ if err != nil {
839+ return "" , nil , err
840+ }
841+ for _ , expanded := range expandedArgs {
842+ compiledArgTemplate := strings .ReplaceAll (expanded , executorInputJSONKey , executorInputJSONString )
843+ for placeholder , replacement := range placeholders {
844+ compiledArgTemplate = strings .ReplaceAll (compiledArgTemplate , placeholder , replacement )
845+ }
846+ compiledArgs = append (compiledArgs , compiledArgTemplate )
838847 }
839- compiledArgs = append (compiledArgs , compiledArgTemplate )
840848 }
841849 return compiledCmd , compiledArgs , nil
842850}
843851
852+ // getProvidedInputs returns the input names that have values supplied (parameters or artifacts).
853+ func getProvidedInputs (executorInput * pipelinespec.ExecutorInput ) map [string ]struct {} {
854+ provided := make (map [string ]struct {})
855+ for name , v := range executorInput .GetInputs ().GetParameterValues () {
856+ if v != nil {
857+ provided [name ] = struct {}{}
858+ }
859+ }
860+ for name , artifactList := range executorInput .GetInputs ().GetArtifacts () {
861+ if artifactList != nil && len (artifactList .Artifacts ) > 0 {
862+ provided [name ] = struct {}{}
863+ }
864+ }
865+ return provided
866+ }
867+
868+ // resolveStructPlaceholders expands IfPresent/Concat placeholder strings into concrete args.
869+ // If no struct placeholder is detected, the original arg is returned.
870+ func resolveStructPlaceholders (arg string , providedInputs map [string ]struct {}) ([]string , error ) {
871+ if strings .HasPrefix (arg , `{"Concat": ` ) || strings .HasPrefix (arg , `{"IfPresent": ` ) {
872+ var obj interface {}
873+ if err := json .Unmarshal ([]byte (arg ), & obj ); err != nil {
874+ return nil , fmt .Errorf ("failed to unmarshal struct placeholder %q: %w" , arg , err )
875+ }
876+ resolved , err := recursivelyResolveStruct (obj , providedInputs )
877+ if err != nil {
878+ return nil , err
879+ }
880+ switch v := resolved .(type ) {
881+ case nil :
882+ return []string {}, nil
883+ case string :
884+ return []string {v }, nil
885+ case []string :
886+ return v , nil
887+ default :
888+ return nil , fmt .Errorf ("unexpected resolved struct placeholder type %T for %q" , v , arg )
889+ }
890+ }
891+
892+ return []string {arg }, nil
893+ }
894+
895+ // recursivelyResolveStruct handles nested IfPresent/Concat structures.
896+ func recursivelyResolveStruct (obj interface {}, providedInputs map [string ]struct {}) (interface {}, error ) {
897+ switch typed := obj .(type ) {
898+ case string :
899+ return typed , nil
900+ case []interface {}:
901+ var parts []string
902+ for _ , item := range typed {
903+ resolved , err := recursivelyResolveStruct (item , providedInputs )
904+ if err != nil {
905+ return nil , err
906+ }
907+ switch v := resolved .(type ) {
908+ case nil :
909+ continue
910+ case string :
911+ parts = append (parts , v )
912+ case []string :
913+ parts = append (parts , v ... )
914+ default :
915+ return nil , fmt .Errorf ("unexpected list item type %T in struct placeholder" , v )
916+ }
917+ }
918+ return parts , nil
919+ case map [string ]interface {}:
920+ if len (typed ) != 1 {
921+ return nil , fmt .Errorf ("invalid struct placeholder: %v" , typed )
922+ }
923+ for key , value := range typed {
924+ switch key {
925+ case "Concat" :
926+ items , ok := value .([]interface {})
927+ if ! ok {
928+ return nil , fmt .Errorf ("Concat value must be a list, got %T" , value )
929+ }
930+ var parts []string
931+ for _ , item := range items {
932+ resolved , err := recursivelyResolveStruct (item , providedInputs )
933+ if err != nil {
934+ return nil , err
935+ }
936+ switch v := resolved .(type ) {
937+ case nil :
938+ continue
939+ case string :
940+ parts = append (parts , v )
941+ case []string :
942+ parts = append (parts , v ... )
943+ default :
944+ return nil , fmt .Errorf ("unexpected Concat item type %T" , v )
945+ }
946+ }
947+ return strings .Join (parts , "" ), nil
948+ case "IfPresent" :
949+ inner , ok := value .(map [string ]interface {})
950+ if ! ok {
951+ return nil , fmt .Errorf ("IfPresent value must be an object, got %T" , value )
952+ }
953+ inputName , ok := inner ["InputName" ].(string )
954+ if ! ok {
955+ return nil , fmt .Errorf ("IfPresent.InputName must be a string, got %T" , inner ["InputName" ])
956+ }
957+ _ , exists := providedInputs [inputName ]
958+ var branch interface {}
959+ if exists {
960+ branch = inner ["Then" ]
961+ } else {
962+ branch = inner ["Else" ]
963+ }
964+ if branch == nil {
965+ return nil , nil
966+ }
967+ return recursivelyResolveStruct (branch , providedInputs )
968+ default :
969+ return nil , fmt .Errorf ("unsupported struct placeholder key %q" , key )
970+ }
971+ }
972+ default :
973+ return nil , fmt .Errorf ("unexpected struct placeholder type %T" , typed )
974+ }
975+
976+ return nil , nil
977+ }
978+
844979// Add executor input placeholders to provided map.
845980func getPlaceholders (executorInput * pipelinespec.ExecutorInput ) (placeholders map [string ]string , err error ) {
846981 defer func () {
0 commit comments