diff --git a/pkg/apiclient/applicationset/applicationset.pb.go b/pkg/apiclient/applicationset/applicationset.pb.go index d684166be477d..0a57e499c959d 100644 --- a/pkg/apiclient/applicationset/applicationset.pb.go +++ b/pkg/apiclient/applicationset/applicationset.pb.go @@ -581,58 +581,59 @@ func init() { } var fileDescriptor_eacb9df0ce5738fa = []byte{ - // 801 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x96, 0x4b, 0x6b, 0x14, 0x4b, - 0x14, 0xc7, 0xa9, 0x4c, 0x32, 0x77, 0x52, 0x09, 0xf7, 0x42, 0xc1, 0x4d, 0xc6, 0xd6, 0x8c, 0x43, - 0x43, 0x92, 0x31, 0x71, 0xaa, 0x9d, 0xc4, 0x85, 0xc4, 0x95, 0x2f, 0x42, 0x20, 0x88, 0xf6, 0x48, - 0x02, 0xba, 0x90, 0x4e, 0xcf, 0x61, 0xd2, 0x66, 0xa6, 0xbb, 0xad, 0xaa, 0x69, 0x08, 0xc1, 0x8d, - 0xe0, 0xc6, 0x8d, 0x0b, 0xd1, 0x0f, 0xa0, 0x1b, 0xf7, 0xba, 0x72, 0x93, 0x85, 0x1b, 0x97, 0x82, - 0x5f, 0x40, 0x82, 0x1f, 0x44, 0xaa, 0xba, 0xe7, 0xd1, 0x95, 0x79, 0x04, 0x1c, 0xdd, 0x75, 0x3d, - 0xfa, 0xd4, 0xaf, 0xce, 0xf9, 0xd7, 0xbf, 0x0a, 0xaf, 0x70, 0x60, 0x11, 0x30, 0xcb, 0x09, 0xc3, - 0x86, 0xe7, 0x3a, 0xc2, 0x0b, 0x7c, 0x0e, 0x42, 0x6b, 0xd2, 0x90, 0x05, 0x22, 0x20, 0xff, 0xa6, - 0x7b, 0x8d, 0x0b, 0xf5, 0x20, 0xa8, 0x37, 0xc0, 0x72, 0x42, 0xcf, 0x72, 0x7c, 0x3f, 0x10, 0xf1, - 0x48, 0x3c, 0xdb, 0x30, 0x0f, 0xae, 0x71, 0xea, 0x05, 0x6a, 0xd4, 0x0d, 0x18, 0x58, 0x51, 0xc5, - 0xaa, 0x83, 0x0f, 0xcc, 0x11, 0x50, 0x4b, 0xe6, 0x6c, 0xd7, 0x3d, 0xb1, 0xdf, 0xda, 0xa3, 0x6e, - 0xd0, 0xb4, 0x1c, 0x56, 0x0f, 0x42, 0x16, 0x3c, 0x51, 0x1f, 0x65, 0xb7, 0x66, 0x45, 0xeb, 0x56, - 0x78, 0x50, 0x97, 0xff, 0xf3, 0x5e, 0x1e, 0x2b, 0xaa, 0x38, 0x8d, 0x70, 0xdf, 0x39, 0x15, 0xcd, - 0xdc, 0xc1, 0x73, 0x37, 0xba, 0xf3, 0xaa, 0x20, 0x36, 0x41, 0xdc, 0x6f, 0x01, 0x3b, 0x24, 0x04, - 0x4f, 0xfa, 0x4e, 0x13, 0xf2, 0xa8, 0x88, 0x4a, 0xd3, 0xb6, 0xfa, 0x26, 0x25, 0xfc, 0x9f, 0x13, - 0x86, 0x1c, 0xc4, 0x5d, 0xa7, 0x09, 0x3c, 0x74, 0x5c, 0xc8, 0x4f, 0xa8, 0x61, 0xbd, 0xdb, 0x3c, - 0xc2, 0xf3, 0xe9, 0xb8, 0xdb, 0x1e, 0x4f, 0x02, 0x1b, 0x38, 0x27, 0x99, 0xc1, 0x15, 0x3c, 0x8f, - 0x8a, 0x99, 0xd2, 0xb4, 0xdd, 0x69, 0xcb, 0x31, 0x0e, 0x0d, 0x70, 0x45, 0xc0, 0x92, 0xc8, 0x9d, - 0x76, 0xbf, 0xc5, 0x33, 0xfd, 0x17, 0xff, 0x8c, 0x70, 0x3e, 0xbd, 0xfa, 0xae, 0x23, 0xdc, 0xfd, - 0xc1, 0xfb, 0xea, 0x45, 0x9a, 0x18, 0x82, 0x94, 0xe9, 0x8b, 0x54, 0xed, 0x45, 0x9a, 0xec, 0x20, - 0xf5, 0x76, 0xcb, 0x99, 0x0c, 0x78, 0xd0, 0x62, 0x2e, 0xec, 0x00, 0xe3, 0x5e, 0xe0, 0xe7, 0xa7, - 0xe2, 0x99, 0x5a, 0xb7, 0xf9, 0x01, 0xe9, 0x25, 0xb1, 0x81, 0x87, 0x52, 0x3d, 0x24, 0x8f, 0xff, - 0x49, 0xb0, 0x12, 0xfa, 0x76, 0x93, 0x08, 0xac, 0x09, 0x4d, 0x65, 0x6f, 0x66, 0x6d, 0x9b, 0x76, - 0xd5, 0x42, 0xdb, 0x6a, 0x51, 0x1f, 0x8f, 0xdd, 0x1a, 0x8d, 0xd6, 0x69, 0x78, 0x50, 0xa7, 0x52, - 0x2d, 0xb4, 0xe7, 0x77, 0xda, 0x56, 0x0b, 0xd5, 0x38, 0xb4, 0x35, 0xcc, 0x2f, 0x08, 0x9f, 0x4f, - 0x4f, 0xb9, 0xc5, 0xc0, 0x11, 0x60, 0xc3, 0xd3, 0x16, 0xf0, 0x7e, 0x54, 0xe8, 0xcf, 0x53, 0x91, - 0x39, 0x9c, 0x6d, 0x85, 0x1c, 0x58, 0x9c, 0x83, 0x9c, 0x9d, 0xb4, 0x64, 0x7f, 0x8d, 0x1d, 0xda, - 0x2d, 0x5f, 0x95, 0x31, 0x67, 0x27, 0x2d, 0xf3, 0x91, 0xbe, 0x89, 0xdb, 0xd0, 0x80, 0xee, 0x26, - 0x7e, 0xef, 0x1c, 0xec, 0xea, 0xe7, 0xe0, 0x01, 0x03, 0x18, 0xc7, 0x01, 0x7b, 0x83, 0xf0, 0x82, - 0x7e, 0x72, 0xe3, 0xa3, 0xdd, 0x3f, 0xfb, 0xd5, 0xbf, 0x90, 0xfd, 0x2a, 0x08, 0xf3, 0x15, 0xc2, - 0x85, 0x41, 0x5c, 0x89, 0x8c, 0x9b, 0x78, 0xb6, 0xb7, 0x64, 0xca, 0x04, 0x66, 0xd6, 0xb6, 0xc6, - 0x86, 0x65, 0xa7, 0xc2, 0xaf, 0x1d, 0x63, 0xfc, 0x7f, 0x9a, 0xa8, 0x0a, 0x2c, 0xf2, 0x5c, 0x20, - 0xef, 0x11, 0xce, 0x6c, 0x82, 0x20, 0x4b, 0x54, 0xf3, 0xee, 0xfe, 0x96, 0x68, 0x8c, 0x35, 0x73, - 0xe6, 0xd2, 0xf3, 0xef, 0x3f, 0x5f, 0x4f, 0x14, 0x49, 0x41, 0xd9, 0x7d, 0x54, 0xd1, 0x2e, 0x10, - 0x6e, 0x1d, 0x49, 0x49, 0x3c, 0x23, 0x6f, 0x11, 0xce, 0xb5, 0x73, 0x48, 0xca, 0xa3, 0x50, 0x53, - 0x1a, 0x30, 0xe8, 0x59, 0xa7, 0xc7, 0xa5, 0x31, 0x57, 0x15, 0xd3, 0xa2, 0x59, 0x1c, 0xc4, 0xd4, - 0xbe, 0x3f, 0x36, 0xd0, 0x0a, 0x79, 0x87, 0xf0, 0xa4, 0xb4, 0x75, 0xb2, 0x3c, 0x7c, 0x95, 0x8e, - 0xf5, 0x1b, 0xf7, 0xc6, 0x99, 0x40, 0x19, 0xd6, 0xbc, 0xa8, 0x80, 0xcf, 0x91, 0xf9, 0x01, 0xc0, - 0xe4, 0x13, 0xc2, 0xd9, 0xd8, 0x95, 0xc8, 0xea, 0x70, 0xcc, 0x94, 0x77, 0x8d, 0xb9, 0xd6, 0x96, - 0xc2, 0xbc, 0x64, 0x0e, 0xc2, 0xdc, 0xd0, 0x4d, 0xec, 0x05, 0xc2, 0xd9, 0xd8, 0x87, 0x46, 0x61, - 0xa7, 0xdc, 0xca, 0x18, 0x21, 0xe5, 0x4e, 0xa1, 0x13, 0xf1, 0xad, 0x8c, 0x12, 0xdf, 0x31, 0xc2, - 0xb3, 0x76, 0x72, 0x43, 0x49, 0xeb, 0x1a, 0x55, 0xeb, 0x8e, 0xbd, 0x8d, 0xb7, 0xd6, 0x32, 0xac, - 0x79, 0x55, 0x31, 0x53, 0x72, 0x79, 0x38, 0xb3, 0xd5, 0xbe, 0x51, 0xcb, 0x42, 0x02, 0xbf, 0x44, - 0x98, 0x48, 0xa9, 0xb4, 0x77, 0x71, 0x27, 0x02, 0x5f, 0xf0, 0x33, 0x9f, 0xf9, 0x05, 0x1a, 0xbf, - 0xc9, 0x24, 0x2a, 0x95, 0x6f, 0x32, 0x1a, 0x55, 0xa8, 0x8a, 0xa1, 0xf4, 0x57, 0x56, 0x4c, 0xcb, - 0x64, 0x71, 0x04, 0x13, 0xc4, 0xab, 0x7e, 0x44, 0x78, 0x4a, 0xbd, 0x45, 0x48, 0x69, 0xf8, 0xfa, - 0xdd, 0x07, 0x8b, 0xb1, 0x33, 0xce, 0x44, 0xaa, 0xb8, 0x0a, 0xff, 0xb4, 0xff, 0x70, 0xc1, 0xc0, - 0x69, 0xea, 0x3b, 0xb8, 0x82, 0x6e, 0x6e, 0x7d, 0x3d, 0x29, 0xa0, 0x6f, 0x27, 0x05, 0xf4, 0xe3, - 0xa4, 0x80, 0x1e, 0x5e, 0x3f, 0xdb, 0x03, 0xd4, 0x6d, 0x78, 0xe0, 0xeb, 0xaf, 0xe2, 0xbd, 0xac, - 0x7a, 0x76, 0xae, 0xff, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x03, 0xd8, 0x6f, 0x1e, 0x44, 0x0b, 0x00, - 0x00, + // 825 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x96, 0x4d, 0x6b, 0x13, 0x41, + 0x18, 0xc7, 0x99, 0xa6, 0x4d, 0xd3, 0x69, 0x51, 0x18, 0xb0, 0x8d, 0xab, 0x8d, 0x61, 0xa1, 0x6d, + 0x6c, 0xcd, 0xac, 0x69, 0x3d, 0x48, 0x3d, 0xf9, 0x46, 0x29, 0x14, 0xd1, 0x8d, 0xb4, 0xa0, 0x07, + 0xd9, 0x6e, 0x1e, 0x93, 0xb5, 0xc9, 0xee, 0x3a, 0x33, 0x59, 0x28, 0xc5, 0x8b, 0xe0, 0xc5, 0x8b, + 0x07, 0xd1, 0x0f, 0xa0, 0x17, 0xcf, 0xea, 0xc9, 0x8b, 0x07, 0x2f, 0x1e, 0x05, 0xbf, 0x80, 0x14, + 0x3f, 0x82, 0x1f, 0x40, 0x66, 0x76, 0xf3, 0xb2, 0xdb, 0xbc, 0x14, 0x8c, 0x7a, 0xdb, 0x79, 0xd9, + 0x67, 0x7e, 0xf3, 0x3c, 0xff, 0xf9, 0xcf, 0xe0, 0x65, 0x0e, 0x2c, 0x00, 0x66, 0x58, 0xbe, 0x5f, + 0x77, 0x6c, 0x4b, 0x38, 0x9e, 0xcb, 0x41, 0x24, 0x9a, 0xd4, 0x67, 0x9e, 0xf0, 0xc8, 0x89, 0x78, + 0xaf, 0x76, 0xb6, 0xea, 0x79, 0xd5, 0x3a, 0x18, 0x96, 0xef, 0x18, 0x96, 0xeb, 0x7a, 0x22, 0x1c, + 0x09, 0x67, 0x6b, 0xfa, 0xde, 0x65, 0x4e, 0x1d, 0x4f, 0x8d, 0xda, 0x1e, 0x03, 0x23, 0x28, 0x19, + 0x55, 0x70, 0x81, 0x59, 0x02, 0x2a, 0xd1, 0x9c, 0xad, 0xaa, 0x23, 0x6a, 0xcd, 0x5d, 0x6a, 0x7b, + 0x0d, 0xc3, 0x62, 0x55, 0xcf, 0x67, 0xde, 0x23, 0xf5, 0x51, 0xb4, 0x2b, 0x46, 0xb0, 0x66, 0xf8, + 0x7b, 0x55, 0xf9, 0x3f, 0xef, 0xe6, 0x31, 0x82, 0x92, 0x55, 0xf7, 0x6b, 0xd6, 0x91, 0x68, 0xfa, + 0x36, 0x9e, 0xbd, 0xda, 0x99, 0x57, 0x06, 0xb1, 0x01, 0xe2, 0x4e, 0x13, 0xd8, 0x3e, 0x21, 0x78, + 0xdc, 0xb5, 0x1a, 0x90, 0x45, 0x79, 0x54, 0x98, 0x32, 0xd5, 0x37, 0x29, 0xe0, 0x93, 0x96, 0xef, + 0x73, 0x10, 0xb7, 0xac, 0x06, 0x70, 0xdf, 0xb2, 0x21, 0x3b, 0xa6, 0x86, 0x93, 0xdd, 0xfa, 0x01, + 0x9e, 0x8b, 0xc7, 0xdd, 0x72, 0x78, 0x14, 0x58, 0xc3, 0x19, 0xc9, 0x0c, 0xb6, 0xe0, 0x59, 0x94, + 0x4f, 0x15, 0xa6, 0xcc, 0x76, 0x5b, 0x8e, 0x71, 0xa8, 0x83, 0x2d, 0x3c, 0x16, 0x45, 0x6e, 0xb7, + 0x7b, 0x2d, 0x9e, 0xea, 0xbd, 0xf8, 0x27, 0x84, 0xb3, 0xf1, 0xd5, 0x77, 0x2c, 0x61, 0xd7, 0xfa, + 0xef, 0xab, 0x1b, 0x69, 0x6c, 0x00, 0x52, 0xaa, 0x27, 0x52, 0xb9, 0x1b, 0x69, 0xbc, 0x8d, 0xd4, + 0xdd, 0x2d, 0x67, 0x32, 0xe0, 0x5e, 0x93, 0xd9, 0xb0, 0x0d, 0x8c, 0x3b, 0x9e, 0x9b, 0x9d, 0x08, + 0x67, 0x26, 0xba, 0xf5, 0x77, 0x28, 0x59, 0x12, 0x13, 0xb8, 0x2f, 0xd5, 0x43, 0xb2, 0x78, 0x32, + 0xc2, 0x8a, 0xe8, 0x5b, 0x4d, 0x22, 0x70, 0x42, 0x68, 0x2a, 0x7b, 0xd3, 0xab, 0x5b, 0xb4, 0xa3, + 0x16, 0xda, 0x52, 0x8b, 0xfa, 0x78, 0x60, 0x57, 0x68, 0xb0, 0x46, 0xfd, 0xbd, 0x2a, 0x95, 0x6a, + 0xa1, 0x5d, 0xbf, 0xd3, 0x96, 0x5a, 0x68, 0x82, 0x23, 0xb1, 0x86, 0xfe, 0x05, 0xe1, 0x33, 0xf1, + 0x29, 0xd7, 0x19, 0x58, 0x02, 0x4c, 0x78, 0xdc, 0x04, 0xde, 0x8b, 0x0a, 0xfd, 0x7d, 0x2a, 0x32, + 0x8b, 0xd3, 0x4d, 0x9f, 0x03, 0x0b, 0x73, 0x90, 0x31, 0xa3, 0x96, 0xec, 0xaf, 0xb0, 0x7d, 0xb3, + 0xe9, 0xaa, 0x32, 0x66, 0xcc, 0xa8, 0xa5, 0xdf, 0x4f, 0x6e, 0xe2, 0x06, 0xd4, 0xa1, 0xb3, 0x89, + 0x3f, 0x3b, 0x07, 0x3b, 0xc9, 0x73, 0x70, 0x97, 0x01, 0x8c, 0xe2, 0x80, 0xbd, 0x42, 0x78, 0x3e, + 0x79, 0x72, 0xc3, 0xa3, 0xdd, 0x3b, 0xfb, 0xe5, 0x7f, 0x90, 0xfd, 0x32, 0x08, 0xfd, 0x05, 0xc2, + 0xb9, 0x7e, 0x5c, 0x91, 0x8c, 0x1b, 0x78, 0xa6, 0xbb, 0x64, 0xca, 0x04, 0xa6, 0x57, 0x37, 0x47, + 0x86, 0x65, 0xc6, 0xc2, 0xaf, 0xfe, 0x9a, 0xc6, 0xa7, 0xe2, 0x44, 0x65, 0x60, 0x81, 0x63, 0x03, + 0x79, 0x8b, 0x70, 0x6a, 0x03, 0x04, 0x59, 0xa4, 0x09, 0xef, 0xee, 0x6d, 0x89, 0xda, 0x48, 0x33, + 0xa7, 0x2f, 0x3e, 0xfd, 0xfe, 0xf3, 0xe5, 0x58, 0x9e, 0xe4, 0x94, 0xdd, 0x07, 0xa5, 0xc4, 0x05, + 0xc2, 0x8d, 0x03, 0x29, 0x89, 0x27, 0xe4, 0x35, 0xc2, 0x99, 0x56, 0x0e, 0x49, 0x71, 0x18, 0x6a, + 0x4c, 0x03, 0x1a, 0x3d, 0xee, 0xf4, 0xb0, 0x34, 0xfa, 0x8a, 0x62, 0x5a, 0xd0, 0xf3, 0xfd, 0x98, + 0x5a, 0xf7, 0xc7, 0x3a, 0x5a, 0x26, 0x6f, 0x10, 0x1e, 0x97, 0xb6, 0x4e, 0x96, 0x06, 0xaf, 0xd2, + 0xb6, 0x7e, 0xed, 0xf6, 0x28, 0x13, 0x28, 0xc3, 0xea, 0xe7, 0x14, 0xf0, 0x69, 0x32, 0xd7, 0x07, + 0x98, 0x7c, 0x44, 0x38, 0x1d, 0xba, 0x12, 0x59, 0x19, 0x8c, 0x19, 0xf3, 0xae, 0x11, 0xd7, 0xda, + 0x50, 0x98, 0xe7, 0xf5, 0x7e, 0x98, 0xeb, 0x49, 0x13, 0x7b, 0x86, 0x70, 0x3a, 0xf4, 0xa1, 0x61, + 0xd8, 0x31, 0xb7, 0xd2, 0x86, 0x48, 0xb9, 0x5d, 0xe8, 0x48, 0x7c, 0xcb, 0xc3, 0xc4, 0xf7, 0x19, + 0xe1, 0x19, 0x33, 0xba, 0xa1, 0xa4, 0x75, 0x0d, 0xab, 0x75, 0xdb, 0xde, 0x46, 0x5b, 0x6b, 0x19, + 0x56, 0xbf, 0xa4, 0x98, 0x29, 0xb9, 0x30, 0x98, 0xd9, 0x68, 0xdd, 0xa8, 0x45, 0x21, 0x81, 0x9f, + 0x23, 0x4c, 0xa4, 0x54, 0x5a, 0xbb, 0xb8, 0x19, 0x80, 0x2b, 0xf8, 0xb1, 0xcf, 0xfc, 0x3c, 0x0d, + 0xdf, 0x64, 0x12, 0x95, 0xca, 0x37, 0x19, 0x0d, 0x4a, 0x54, 0xc5, 0x50, 0xfa, 0x2b, 0x2a, 0xa6, + 0x25, 0xb2, 0x30, 0x84, 0x09, 0xc2, 0x55, 0xdf, 0x23, 0x3c, 0x69, 0xc2, 0x43, 0x06, 0xbc, 0xf6, + 0x9f, 0x5c, 0xa7, 0xa4, 0x80, 0x57, 0xf4, 0xc5, 0xa1, 0x49, 0x54, 0x94, 0xf2, 0x9c, 0x7f, 0x40, + 0x78, 0x42, 0x3d, 0xa0, 0x48, 0x61, 0x30, 0x72, 0xe7, 0x95, 0xa5, 0x6d, 0x8f, 0x12, 0x5a, 0xc5, + 0x55, 0x39, 0x3f, 0x6a, 0x9a, 0x5c, 0x30, 0xb0, 0x1a, 0xc9, 0x5d, 0x5c, 0x44, 0xd7, 0x36, 0xbf, + 0x1e, 0xe6, 0xd0, 0xb7, 0xc3, 0x1c, 0xfa, 0x71, 0x98, 0x43, 0xf7, 0xae, 0x1c, 0xef, 0xd5, 0x6c, + 0xd7, 0x1d, 0x70, 0x93, 0x4f, 0xf9, 0xdd, 0xb4, 0x7a, 0x2b, 0xaf, 0xfd, 0x0e, 0x00, 0x00, 0xff, + 0xff, 0x1f, 0xad, 0x71, 0x74, 0xf9, 0x0b, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -661,6 +662,8 @@ type ApplicationSetServiceClient interface { ResourceTree(ctx context.Context, in *ApplicationSetTreeQuery, opts ...grpc.CallOption) (*v1alpha1.ApplicationSetTree, error) // ListResourceEvents returns a list of event resources ListResourceEvents(ctx context.Context, in *ApplicationSetGetQuery, opts ...grpc.CallOption) (*v1.EventList, error) + // Refresh requests immediate reconciliation of an ApplicationSet + Refresh(ctx context.Context, in *ApplicationSetGetQuery, opts ...grpc.CallOption) (*v1alpha1.ApplicationSet, error) Watch(ctx context.Context, in *ApplicationSetWatchQuery, opts ...grpc.CallOption) (ApplicationSetService_WatchClient, error) } @@ -735,6 +738,15 @@ func (c *applicationSetServiceClient) ListResourceEvents(ctx context.Context, in return out, nil } +func (c *applicationSetServiceClient) Refresh(ctx context.Context, in *ApplicationSetGetQuery, opts ...grpc.CallOption) (*v1alpha1.ApplicationSet, error) { + out := new(v1alpha1.ApplicationSet) + err := c.cc.Invoke(ctx, "/applicationset.ApplicationSetService/Refresh", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *applicationSetServiceClient) Watch(ctx context.Context, in *ApplicationSetWatchQuery, opts ...grpc.CallOption) (ApplicationSetService_WatchClient, error) { stream, err := c.cc.NewStream(ctx, &_ApplicationSetService_serviceDesc.Streams[0], "/applicationset.ApplicationSetService/Watch", opts...) if err != nil { @@ -783,6 +795,8 @@ type ApplicationSetServiceServer interface { ResourceTree(context.Context, *ApplicationSetTreeQuery) (*v1alpha1.ApplicationSetTree, error) // ListResourceEvents returns a list of event resources ListResourceEvents(context.Context, *ApplicationSetGetQuery) (*v1.EventList, error) + // Refresh requests immediate reconciliation of an ApplicationSet + Refresh(context.Context, *ApplicationSetGetQuery) (*v1alpha1.ApplicationSet, error) Watch(*ApplicationSetWatchQuery, ApplicationSetService_WatchServer) error } @@ -811,6 +825,9 @@ func (*UnimplementedApplicationSetServiceServer) ResourceTree(ctx context.Contex func (*UnimplementedApplicationSetServiceServer) ListResourceEvents(ctx context.Context, req *ApplicationSetGetQuery) (*v1.EventList, error) { return nil, status.Errorf(codes.Unimplemented, "method ListResourceEvents not implemented") } +func (*UnimplementedApplicationSetServiceServer) Refresh(ctx context.Context, req *ApplicationSetGetQuery) (*v1alpha1.ApplicationSet, error) { + return nil, status.Errorf(codes.Unimplemented, "method Refresh not implemented") +} func (*UnimplementedApplicationSetServiceServer) Watch(req *ApplicationSetWatchQuery, srv ApplicationSetService_WatchServer) error { return status.Errorf(codes.Unimplemented, "method Watch not implemented") } @@ -945,6 +962,24 @@ func _ApplicationSetService_ListResourceEvents_Handler(srv interface{}, ctx cont return interceptor(ctx, in, info, handler) } +func _ApplicationSetService_Refresh_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationSetGetQuery) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationSetServiceServer).Refresh(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/applicationset.ApplicationSetService/Refresh", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationSetServiceServer).Refresh(ctx, req.(*ApplicationSetGetQuery)) + } + return interceptor(ctx, in, info, handler) +} + func _ApplicationSetService_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { m := new(ApplicationSetWatchQuery) if err := stream.RecvMsg(m); err != nil { @@ -998,6 +1033,10 @@ var _ApplicationSetService_serviceDesc = grpc.ServiceDesc{ MethodName: "ListResourceEvents", Handler: _ApplicationSetService_ListResourceEvents_Handler, }, + { + MethodName: "Refresh", + Handler: _ApplicationSetService_Refresh_Handler, + }, }, Streams: []grpc.StreamDesc{ { diff --git a/pkg/apiclient/applicationset/applicationset.pb.gw.go b/pkg/apiclient/applicationset/applicationset.pb.gw.go index 82c6420a5dd7a..f0aadf7a396de 100644 --- a/pkg/apiclient/applicationset/applicationset.pb.gw.go +++ b/pkg/apiclient/applicationset/applicationset.pb.gw.go @@ -443,6 +443,76 @@ func local_request_ApplicationSetService_ListResourceEvents_0(ctx context.Contex } +func request_ApplicationSetService_Refresh_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationSetServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationSetGetQuery + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + + protoReq.Name, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + + msg, err := client.Refresh(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_ApplicationSetService_Refresh_0(ctx context.Context, marshaler runtime.Marshaler, server ApplicationSetServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationSetGetQuery + var metadata runtime.ServerMetadata + + newReader, berr := utilities.IOReaderFactory(req.Body) + if berr != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", berr) + } + if err := marshaler.NewDecoder(newReader()).Decode(&protoReq); err != nil && err != io.EOF { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["name"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name") + } + + protoReq.Name, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err) + } + + msg, err := server.Refresh(ctx, &protoReq) + return msg, metadata, err + +} + var ( filter_ApplicationSetService_Watch_0 = &utilities.DoubleArray{Encoding: map[string]int{}, Base: []int(nil), Check: []int(nil)} ) @@ -638,6 +708,29 @@ func RegisterApplicationSetServiceHandlerServer(ctx context.Context, mux *runtim }) + mux.Handle("POST", pattern_ApplicationSetService_Refresh_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + var stream runtime.ServerTransportStream + ctx = grpc.NewContextWithServerTransportStream(ctx, &stream) + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_ApplicationSetService_Refresh_0(rctx, inboundMarshaler, server, req, pathParams) + md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer()) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationSetService_Refresh_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_ApplicationSetService_Watch_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { err := status.Error(codes.Unimplemented, "streaming calls are not yet supported in the in-process transport") _, outboundMarshaler := runtime.MarshalerForRequest(mux, req) @@ -826,6 +919,26 @@ func RegisterApplicationSetServiceHandlerClient(ctx context.Context, mux *runtim }) + mux.Handle("POST", pattern_ApplicationSetService_Refresh_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_ApplicationSetService_Refresh_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationSetService_Refresh_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("GET", pattern_ApplicationSetService_Watch_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(req.Context()) defer cancel() @@ -864,6 +977,8 @@ var ( pattern_ApplicationSetService_ListResourceEvents_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applicationsets", "name", "events"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_ApplicationSetService_Refresh_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applicationsets", "name", "refresh"}, "", runtime.AssumeColonVerbOpt(true))) + pattern_ApplicationSetService_Watch_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "stream", "applicationsets"}, "", runtime.AssumeColonVerbOpt(true))) ) @@ -882,5 +997,7 @@ var ( forward_ApplicationSetService_ListResourceEvents_0 = runtime.ForwardResponseMessage + forward_ApplicationSetService_Refresh_0 = runtime.ForwardResponseMessage + forward_ApplicationSetService_Watch_0 = runtime.ForwardResponseStream ) diff --git a/server/applicationset/applicationset.go b/server/applicationset/applicationset.go index 018f056c30997..398c127a544dc 100644 --- a/server/applicationset/applicationset.go +++ b/server/applicationset/applicationset.go @@ -24,6 +24,7 @@ import ( "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" appsettemplate "github.com/argoproj/argo-cd/v3/applicationset/controllers/template" @@ -660,3 +661,48 @@ func (s *Server) ListResourceEvents(ctx context.Context, q *applicationset.Appli } return list.DeepCopy(), nil } + +// Refresh patches the ApplicationSet with the refresh annotation to trigger immediate reconciliation. +func (s *Server) Refresh(ctx context.Context, q *applicationset.ApplicationSetGetQuery) (*v1alpha1.ApplicationSet, error) { + namespace := s.appsetNamespaceOrDefault(q.AppsetNamespace) + + appset, err := s.getAppSetEnforceRBAC(ctx, rbac.ActionUpdate, namespace, q.Name) + if err != nil { + return nil, err + } + + s.projectLock.RLock(appset.Spec.Template.Spec.Project) + defer s.projectLock.RUnlock(appset.Spec.Template.Spec.Project) + + updated, err := s.refreshAppSet(ctx, appset) + if err != nil { + return nil, err + } + s.logAppSetEvent(ctx, updated, argo.EventReasonResourceUpdated, "requested refresh of ApplicationSet") + return updated, nil +} + +func (s *Server) refreshAppSet(ctx context.Context, appset *v1alpha1.ApplicationSet) (*v1alpha1.ApplicationSet, error) { + var updated *v1alpha1.ApplicationSet + err := retry.RetryOnConflict(retry.DefaultBackoff, func() error { + current, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(appset.Namespace).Get(ctx, appset.Name, metav1.GetOptions{}) + if err != nil { + return fmt.Errorf("error getting ApplicationSet: %w", err) + } + if current.Annotations == nil { + current.Annotations = map[string]string{} + } + current.Annotations[argocommon.AnnotationApplicationSetRefresh] = "true" + res, err := s.appclientset.ArgoprojV1alpha1().ApplicationSets(appset.Namespace).Update(ctx, current, metav1.UpdateOptions{}) + if err != nil { + return err + } + updated = res + return nil + }) + if err != nil { + return nil, fmt.Errorf("error updating ApplicationSet: %w", err) + } + s.waitSync(updated) + return updated, nil +} diff --git a/server/applicationset/applicationset.proto b/server/applicationset/applicationset.proto index c3583b278af63..25327a35fe28e 100644 --- a/server/applicationset/applicationset.proto +++ b/server/applicationset/applicationset.proto @@ -117,6 +117,14 @@ service ApplicationSetService { option (google.api.http).get = "/api/v1/applicationsets/{name}/events"; } + // Refresh requests immediate reconciliation of an ApplicationSet + rpc Refresh(ApplicationSetGetQuery) returns (github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.ApplicationSet) { + option (google.api.http) = { + post: "/api/v1/applicationsets/{name}/refresh" + body: "*" + }; + } + rpc Watch (ApplicationSetWatchQuery) returns (stream github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.ApplicationSetWatchEvent) { option (google.api.http).get = "/api/v1/stream/applicationsets"; } diff --git a/server/applicationset/applicationset_test.go b/server/applicationset/applicationset_test.go index c8536a8a82104..bced5a17d2d0f 100644 --- a/server/applicationset/applicationset_test.go +++ b/server/applicationset/applicationset_test.go @@ -1,9 +1,15 @@ package applicationset import ( + "context" + "errors" "sort" "testing" + "time" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" + kubetesting "k8s.io/client-go/testing" "sigs.k8s.io/controller-runtime/pkg/client" cr_fake "sigs.k8s.io/controller-runtime/pkg/client/fake" @@ -842,6 +848,166 @@ func TestListResourceEvents(t *testing.T) { }) } +func TestRefreshAppSet(t *testing.T) { + appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { + appset.Name = "AppSet1" + }) + + t.Run("Refresh sets annotation in default namespace", func(t *testing.T) { + appSetServer := newTestAppSetServer(t, appSet1) + + res, err := appSetServer.Refresh(t.Context(), &applicationset.ApplicationSetGetQuery{Name: "AppSet1"}) + require.NoError(t, err) + assert.Equal(t, "true", res.Annotations[common.AnnotationApplicationSetRefresh]) + }) + + t.Run("Refresh in named namespace", func(t *testing.T) { + ns := "external-namespace" + namespacedAppSet := newTestAppSet(func(appset *appsv1.ApplicationSet) { + appset.Name = "AppSet1" + appset.Namespace = ns + }) + appSetServer := newTestAppSetServer(t, namespacedAppSet) + + res, err := appSetServer.Refresh(t.Context(), &applicationset.ApplicationSetGetQuery{ + Name: "AppSet1", + AppsetNamespace: ns, + }) + require.NoError(t, err) + assert.Equal(t, "true", res.Annotations[common.AnnotationApplicationSetRefresh]) + }) + + t.Run("Refresh in not allowed namespace", func(t *testing.T) { + appSetServer := newTestAppSetServer(t, appSet1) + + _, err := appSetServer.Refresh(t.Context(), &applicationset.ApplicationSetGetQuery{ + Name: "AppSet1", + AppsetNamespace: "NOT-ALLOWED", + }) + assert.EqualError(t, err, "namespace 'NOT-ALLOWED' is not permitted") + }) + + t.Run("Refresh for non-existent appset", func(t *testing.T) { + appSetServer := newTestAppSetServer(t, appSet1) + + _, err := appSetServer.Refresh(t.Context(), &applicationset.ApplicationSetGetQuery{Name: "DoesNotExist"}) + require.Error(t, err) + assert.Contains(t, err.Error(), "error getting ApplicationSet") + }) +} + +func TestRefreshAppSetWithConflicts(t *testing.T) { + appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { + appset.Name = "AppSet1" + appset.ResourceVersion = "1" + }) + appSetServer := newTestAppSetServer(t, appSet1) + fakeAppCs := appSetServer.appclientset.(*apps.Clientset) + + getCallCount := 0 + updateCallCount := 0 + + fakeAppCs.Lock() + fakeAppCs.ReactionChain = nil + fakeAppCs.AddReactor("get", "applicationsets", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) { + getCallCount++ + fresh := appSet1.DeepCopy() + if getCallCount > 1 { + fresh.ResourceVersion = "2" + } + return true, fresh, nil + }) + fakeAppCs.AddReactor("update", "applicationsets", func(action kubetesting.Action) (handled bool, ret runtime.Object, err error) { + updateCallCount++ + updateAction := action.(kubetesting.UpdateAction) + appset := updateAction.GetObject().(*appsv1.ApplicationSet) + if appset.ResourceVersion == "1" { + return true, nil, apierrors.NewConflict( + schema.GroupResource{Group: "argoproj.io", Resource: "applicationsets"}, + appset.Name, + errors.New("the object has been modified"), + ) + } + updated := appset.DeepCopy() + updated.ResourceVersion = "3" + return true, updated, nil + }) + fakeAppCs.Unlock() + + res, err := appSetServer.Refresh(context.Background(), &applicationset.ApplicationSetGetQuery{Name: "AppSet1"}) + require.NoError(t, err) + assert.GreaterOrEqual(t, updateCallCount, 2, "Update should be called at least twice (once with conflict, once with success)") + assert.Equal(t, "true", res.Annotations[common.AnnotationApplicationSetRefresh]) +} + +func TestRefreshAppSetTooManyConflicts(t *testing.T) { + appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { + appset.Name = "AppSet1" + appset.ResourceVersion = "1" + }) + appSetServer := newTestAppSetServer(t, appSet1) + fakeAppCs := appSetServer.appclientset.(*apps.Clientset) + + fakeAppCs.Lock() + fakeAppCs.ReactionChain = nil + fakeAppCs.AddReactor("get", "applicationsets", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) { + return true, appSet1.DeepCopy(), nil + }) + fakeAppCs.AddReactor("update", "applicationsets", func(_ kubetesting.Action) (handled bool, ret runtime.Object, err error) { + return true, nil, apierrors.NewConflict( + schema.GroupResource{Group: "argoproj.io", Resource: "applicationsets"}, + appSet1.Name, + errors.New("the object has been modified"), + ) + }) + fakeAppCs.Unlock() + + _, err := appSetServer.refreshAppSet(context.Background(), appSet1) + require.Error(t, err) + assert.Contains(t, err.Error(), "error updating ApplicationSet") + assert.True(t, apierrors.IsConflict(errors.Unwrap(err)), "expected conflict after backoff retries are exhausted") +} + +func TestWaitSync(t *testing.T) { + appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { + appset.Name = "AppSet1" + appset.ResourceVersion = "5" + }) + appSetServer := newTestAppSetServer(t, appSet1) + + t.Run("returns immediately when informer cache is up to date", func(t *testing.T) { + appset := appSet1.DeepCopy() + appset.ResourceVersion = "1" + start := time.Now() + appSetServer.waitSync(appset) + assert.Less(t, time.Since(start), informerSyncTimeout) + }) + + t.Run("waits until timeout when cache never catches up", func(t *testing.T) { + stale := appSet1.DeepCopy() + stale.ResourceVersion = "99999" + oldTimeout := informerSyncTimeout + informerSyncTimeout = 120 * time.Millisecond + defer func() { informerSyncTimeout = oldTimeout }() + start := time.Now() + appSetServer.waitSync(stale) + assert.GreaterOrEqual(t, time.Since(start), 120*time.Millisecond) + }) +} + +func TestRefreshAppSetWaitSync(t *testing.T) { + appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { + appset.Name = "AppSet1" + }) + appSetServer := newTestAppSetServer(t, appSet1) + + start := time.Now() + updated, err := appSetServer.refreshAppSet(context.Background(), appSet1) + require.NoError(t, err) + assert.Equal(t, "true", updated.Annotations[common.AnnotationApplicationSetRefresh]) + assert.Less(t, time.Since(start), informerSyncTimeout, "waitSync after refresh should not block until full timeout when cache is synced") +} + func TestAppSet_Generate_Cluster(t *testing.T) { appSet1 := newTestAppSet(func(appset *appsv1.ApplicationSet) { appset.Name = "AppSet1" diff --git a/ui/src/app/applications/components/application-details/application-details.tsx b/ui/src/app/applications/components/application-details/application-details.tsx index cab1d66e62518..4726a582f23ce 100644 --- a/ui/src/app/applications/components/application-details/application-details.tsx +++ b/ui/src/app/applications/components/application-details/application-details.tsx @@ -915,6 +915,20 @@ Are you sure you want to disable auto-sync and rollback application '${props.mat title: 'Preview Apps', iconClassName: 'fa fa-eye', action: () => selectNode(appFullName, 0, 'preview') + }, + { + iconClassName: classNames('fa fa-redo', { + 'status-icon--spin': AppUtils.isAppSetRefreshing(application as appModels.ApplicationSet) + }), + title: 'Refresh', + disabled: AppUtils.isAppSetRefreshing(application as appModels.ApplicationSet), + action: () => { + if (!AppUtils.isAppSetRefreshing(application as appModels.ApplicationSet)) { + AppUtils.setAppSetRefreshing(application as appModels.ApplicationSet); + appChanged.current.next(application); + services.applications.refreshApplicationSet(application.metadata.name, application.metadata.namespace); + } + } } ] }, @@ -1203,7 +1217,18 @@ Are you sure you want to disable auto-sync and rollback application '${props.mat )} {!isApplication && ( selectNode('')}> - {isAppSelected && } + {isAppSelected && ( + { + if (!AppUtils.isAppSetRefreshing(application as appModels.ApplicationSet)) { + AppUtils.setAppSetRefreshing(application as appModels.ApplicationSet); + appChanged.current.next(application); + services.applications.refreshApplicationSet(application.metadata.name, application.metadata.namespace); + } + }} + /> + )} )} {isApplication && ( diff --git a/ui/src/app/applications/components/applications-list/application-sets-list.tsx b/ui/src/app/applications/components/applications-list/application-sets-list.tsx index 31f97b76436c5..a848b09b12b17 100644 --- a/ui/src/app/applications/components/applications-list/application-sets-list.tsx +++ b/ui/src/app/applications/components/applications-list/application-sets-list.tsx @@ -264,7 +264,13 @@ const useItemsPerContainer = (itemRef: any, containerRef: any): number => { return itemsPer || 1; }; -const ApplicationSetTiles = ({appSets}: {appSets: models.ApplicationSet[]}) => { +const ApplicationSetTiles = ({ + appSets, + refreshApplicationSet +}: { + appSets: models.ApplicationSet[]; + refreshApplicationSet: (appSetName: string, appSetNamespace: string) => void; +}) => { const [selectedAppSet, navAppSet, reset] = useNav(appSets.length); const ctxh = React.useContext(Context); const firstTileRef = React.useRef(null); @@ -325,6 +331,7 @@ const ApplicationSetTiles = ({appSets}: {appSets: models.ApplicationSet[]}) => { pref={pref} ctx={ctx} tileRef={i === 0 ? firstTileRef : undefined} + refreshApplicationSet={refreshApplicationSet} /> ))} @@ -335,7 +342,13 @@ const ApplicationSetTiles = ({appSets}: {appSets: models.ApplicationSet[]}) => { ); }; -const ApplicationSetTable = ({appSets}: {appSets: models.ApplicationSet[]}) => { +const ApplicationSetTable = ({ + appSets, + refreshApplicationSet +}: { + appSets: models.ApplicationSet[]; + refreshApplicationSet: (appSetName: string, appSetNamespace: string) => void; +}) => { const [selectedAppSet, navAppSet, reset] = useNav(appSets.length); const ctxh = React.useContext(Context); const {useKeybinding} = React.useContext(KeybindingContext); @@ -367,7 +380,14 @@ const ApplicationSetTable = ({appSets}: {appSets: models.ApplicationSet[]}) => { {(pref: ViewPreferences) => (
{appSets.map((appSet, i) => ( - + ))}
)} @@ -380,6 +400,19 @@ const ApplicationSetTable = ({appSets}: {appSets: models.ApplicationSet[]}) => { export const ApplicationSetsList = (props: RouteComponentProps) => { const {List, Summary, Tiles} = AppsListViewKey; const sidebarTarget = useSidebarTarget(); + const loaderRef = React.useRef(); + + function refreshAppSet(appSetName: string, appSetNamespace: string) { + if (loaderRef.current) { + const appSets = loaderRef.current.getData() as models.ApplicationSet[]; + const appSet = appSets.find(item => item.metadata.name === appSetName && item.metadata.namespace === appSetNamespace); + if (appSet) { + AppUtils.setAppSetRefreshing(appSet); + loaderRef.current.setData(appSets); + } + } + services.applications.refreshApplicationSet(appSetName, appSetNamespace); + } function onAppSetFilterPrefChanged(ctx: ContextApis, newPref: AppSetsListPreferences) { services.viewPreferences.updatePreferences({appList: newPref as AppsListPreferences}); @@ -419,6 +452,7 @@ export const ApplicationSetsList = (props: RouteComponentProps) => { toolbar={{breadcrumbs: [{title: 'ApplicationSets', path: props.match.url}]}} hideAuth={true}> AppUtils.handlePageVisibility(() => loadApplicationSets(pref.projectsFilter))} loadingRenderer={() => ( @@ -508,7 +542,9 @@ export const ApplicationSetsList = (props: RouteComponentProps) => { data={filteredApps} onPageChange={page => ctx.navigation.goto('.', {page})}> {data => - (pref.view === Tiles && ) || + (pref.view === Tiles && ) || ( + + ) } )} diff --git a/ui/src/app/applications/components/applications-list/applications-list.tsx b/ui/src/app/applications/components/applications-list/applications-list.tsx index 7344cc26a2b21..d34329965806e 100644 --- a/ui/src/app/applications/components/applications-list/applications-list.tsx +++ b/ui/src/app/applications/components/applications-list/applications-list.tsx @@ -354,6 +354,20 @@ export const ApplicationsList = (props: RouteComponentProps) => { services.applications.get(appName, appNamespace, 'application', 'normal'); } + function refreshAppSet(appSetName: string, appSetNamespace: string) { + if (loaderRef.current) { + const applications = loaderRef.current.getData() as models.AbstractApplication[]; + const appSet = applications.find(item => !AppUtils.isApp(item) && item.metadata.name === appSetName && item.metadata.namespace === appSetNamespace) as + | models.ApplicationSet + | undefined; + if (appSet) { + AppUtils.setAppSetRefreshing(appSet); + loaderRef.current.setData(applications); + } + } + services.applications.refreshApplicationSet(appSetName, appSetNamespace); + } + function onAppFilterPrefChanged(ctx: ContextApis, newPref: AppsListPreferences) { services.viewPreferences.updatePreferences({appList: newPref}); ctx.navigation.goto( @@ -541,6 +555,7 @@ export const ApplicationsList = (props: RouteComponentProps) => { ctx.navigation.goto('.', {syncApp: appName, appNamespace}, {replace: true}) } refreshApplication={refreshApp} + refreshApplicationSet={refreshAppSet} deleteApplication={(appName, appNamespace) => AppUtils.deleteApplication(appName, appNamespace, ctx) } @@ -552,6 +567,7 @@ export const ApplicationsList = (props: RouteComponentProps) => { ctx.navigation.goto('.', {syncApp: appName, appNamespace}, {replace: true}) } refreshApplication={refreshApp} + refreshApplicationSet={refreshAppSet} deleteApplication={(appName, appNamespace) => AppUtils.deleteApplication(appName, appNamespace, ctx) } diff --git a/ui/src/app/applications/components/applications-list/applications-table.tsx b/ui/src/app/applications/components/applications-list/applications-table.tsx index 76a94f07a0b10..d02974b697e16 100644 --- a/ui/src/app/applications/components/applications-list/applications-table.tsx +++ b/ui/src/app/applications/components/applications-list/applications-table.tsx @@ -15,6 +15,7 @@ export const ApplicationsTable = (props: { applications: models.AbstractApplication[]; syncApplication: (appName: string, appNamespace: string) => any; refreshApplication: (appName: string, appNamespace: string) => any; + refreshApplicationSet: (appSetName: string, appSetNamespace: string) => void; deleteApplication: (appName: string, appNamespace: string) => any; }) => { const [selectedApp, navApp, reset] = useNav(props.applications.length); @@ -61,7 +62,14 @@ export const ApplicationsTable = (props: { deleteApplication={props.deleteApplication} /> ) : ( - + ) )} diff --git a/ui/src/app/applications/components/applications-list/applications-tiles.tsx b/ui/src/app/applications/components/applications-list/applications-tiles.tsx index 0e91f914f1959..a83e2cfa55da2 100644 --- a/ui/src/app/applications/components/applications-list/applications-tiles.tsx +++ b/ui/src/app/applications/components/applications-list/applications-tiles.tsx @@ -15,6 +15,7 @@ export interface ApplicationTilesProps { applications: models.AbstractApplication[]; syncApplication: (appName: string, appNamespace: string) => any; refreshApplication: (appName: string, appNamespace: string) => any; + refreshApplicationSet: (appSetName: string, appSetNamespace: string) => void; deleteApplication: (appName: string, appNamespace: string) => any; } @@ -45,7 +46,7 @@ const useItemsPerContainer = (itemRef: any, containerRef: any): number => { return itemsPer || 1; }; -export const ApplicationTiles = ({applications, syncApplication, refreshApplication, deleteApplication}: ApplicationTilesProps) => { +export const ApplicationTiles = ({applications, syncApplication, refreshApplication, refreshApplicationSet, deleteApplication}: ApplicationTilesProps) => { const [selectedApp, navApp, reset] = useNav(applications.length); const ctxh = React.useContext(Context); @@ -124,6 +125,7 @@ export const ApplicationTiles = ({applications, syncApplication, refreshApplicat pref={pref} ctx={ctx} tileRef={i === 0 ? firstTileRef : undefined} + refreshApplicationSet={refreshApplicationSet} /> ) )} diff --git a/ui/src/app/applications/components/applications-list/appset-table-row.tsx b/ui/src/app/applications/components/applications-list/appset-table-row.tsx index 0d32b84e531f3..299acecb7e9b8 100644 --- a/ui/src/app/applications/components/applications-list/appset-table-row.tsx +++ b/ui/src/app/applications/components/applications-list/appset-table-row.tsx @@ -1,5 +1,6 @@ import {NotificationType, Tooltip} from 'argo-ui'; import * as React from 'react'; +import classNames from 'classnames'; import Moment from 'react-moment'; import {ContextApis} from '../../../shared/context'; import * as models from '../../../shared/models'; @@ -14,9 +15,10 @@ export interface AppSetTableRowProps { selected: boolean; pref: ViewPreferences; ctx: ContextApis; + refreshApplicationSet: (appSetName: string, appSetNamespace: string) => void; } -export const AppSetTableRow = ({appSet, selected, pref, ctx}: AppSetTableRowProps) => { +export const AppSetTableRow = ({appSet, selected, pref, ctx, refreshApplicationSet}: AppSetTableRowProps) => { const favList = pref.appList.favoritesAppList || []; const healthStatus = getAppSetHealthStatus(appSet); const linkInfo = getApplicationLinkURL(appSet, ctx.baseHref); @@ -108,9 +110,23 @@ export const AppSetTableRow = ({appSet, selected, pref, ctx}: AppSetTableRowProp {/* Status column (takes remaining space since no Source/Destination for AppSets) */} -
+
{healthStatus}
+
+ + + +
); diff --git a/ui/src/app/applications/components/applications-list/appset-tile.tsx b/ui/src/app/applications/components/applications-list/appset-tile.tsx index 8a71a16619e99..907d35fd7f616 100644 --- a/ui/src/app/applications/components/applications-list/appset-tile.tsx +++ b/ui/src/app/applications/components/applications-list/appset-tile.tsx @@ -1,5 +1,6 @@ import {NotificationType, Tooltip} from 'argo-ui'; import * as React from 'react'; +import classNames from 'classnames'; import {ContextApis, AuthSettingsCtx} from '../../../shared/context'; import * as models from '../../../shared/models'; import * as AppUtils from '../utils'; @@ -15,9 +16,10 @@ export interface AppSetTileProps { pref: ViewPreferences; ctx: ContextApis; tileRef?: React.RefObject; + refreshApplicationSet: (appSetName: string, appSetNamespace: string) => void; } -export const AppSetTile = ({appSet, selected, pref, ctx, tileRef}: AppSetTileProps) => { +export const AppSetTile = ({appSet, selected, pref, ctx, tileRef, refreshApplicationSet}: AppSetTileProps) => { const useAuthSettingsCtx = React.useContext(AuthSettingsCtx); const favList = pref.appList.favoritesAppList || []; @@ -157,6 +159,25 @@ export const AppSetTile = ({appSet, selected, pref, ctx, tileRef}: AppSetTilePro
{AppUtils.formatCreationTimestamp(appSet.metadata.creationTimestamp)}
+ + {/* Action buttons */} + diff --git a/ui/src/app/applications/components/resource-details/appset-resource-details.tsx b/ui/src/app/applications/components/resource-details/appset-resource-details.tsx index 94f7adce1ca67..9755c3e1ad5bf 100644 --- a/ui/src/app/applications/components/resource-details/appset-resource-details.tsx +++ b/ui/src/app/applications/components/resource-details/appset-resource-details.tsx @@ -1,5 +1,6 @@ -import {MockupList, Tab, Tabs} from 'argo-ui'; +import {MockupList, Tab, Tabs, Tooltip} from 'argo-ui'; import * as React from 'react'; +import classNames from 'classnames'; import {DataLoader, EventsList, Expandable, YamlEditor} from '../../../shared/components'; import {Timestamp} from '../../../shared/components/timestamp'; import * as models from '../../../shared/models'; @@ -7,16 +8,18 @@ import {services} from '../../../shared/services'; import {Context} from '../../../shared/context'; import {ResourceIcon} from '../resource-icon'; import {ResourceLabel} from '../resource-label'; +import * as AppUtils from '../utils'; import {HealthStatusIcon, getAppSetHealthStatus, getAppSetConditionCategory} from '../utils'; import {AppSetPreviewTab} from './appset-preview-tab'; import './resource-details.scss'; interface AppSetResourceDetailsProps { appSet: models.ApplicationSet; + onRefresh?: () => void; } export const AppSetResourceDetails = (props: AppSetResourceDetailsProps) => { - const {appSet} = props; + const {appSet, onRefresh} = props; const appContext = React.useContext(Context); const tab = new URLSearchParams(appContext.history.location.search).get('tab'); @@ -140,6 +143,18 @@ export const AppSetResourceDetails = (props: AppSetResourceDetailsProps) => {

{appSet.metadata.name}

+ {onRefresh && ( + + + + )} { }); expect(result).not.toHaveProperty('helm'); }); +}); + +describe('ApplicationSet refresh helpers', () => { + const appSet = { + metadata: { + name: 'guestbook', + namespace: 'argocd', + annotations: {} + } + } as ApplicationSet; + + it('setAppSetRefreshing adds refresh annotation', () => { + setAppSetRefreshing(appSet); + expect(appSet.metadata.annotations['argocd.argoproj.io/application-set-refresh']).toBe('true'); + expect(isAppSetRefreshing(appSet)).toBe(true); + }); + + it('appSetRefreshLinkAttrs disables while refreshing', () => { + setAppSetRefreshing(appSet); + expect(appSetRefreshLinkAttrs(appSet)).toEqual({disabled: true}); + }); }); \ No newline at end of file diff --git a/ui/src/app/applications/components/utils.tsx b/ui/src/app/applications/components/utils.tsx index 6ec5bec57c244..140453e3aaaf9 100644 --- a/ui/src/app/applications/components/utils.tsx +++ b/ui/src/app/applications/components/utils.tsx @@ -1620,6 +1620,23 @@ export function refreshLinkAttrs(app: appModels.Application) { return {disabled: isAppRefreshing(app)}; } +export function isAppSetRefreshing(appSet: appModels.ApplicationSet) { + return !!(appSet.metadata.annotations && appSet.metadata.annotations[appModels.ApplicationSetAnnotationRefreshKey]); +} + +export function setAppSetRefreshing(appSet: appModels.ApplicationSet) { + if (!appSet.metadata.annotations) { + appSet.metadata.annotations = {}; + } + if (!appSet.metadata.annotations[appModels.ApplicationSetAnnotationRefreshKey]) { + appSet.metadata.annotations[appModels.ApplicationSetAnnotationRefreshKey] = 'true'; + } +} + +export function appSetRefreshLinkAttrs(appSet: appModels.ApplicationSet) { + return {disabled: isAppSetRefreshing(appSet)}; +} + export const SyncWindowStatusIcon = ({state, window}: {state: appModels.SyncWindowsState; window: appModels.SyncWindow}) => { let className = ''; let color = ''; diff --git a/ui/src/app/shared/models.ts b/ui/src/app/shared/models.ts index 8a5cb120f9308..3377d9d9a2b64 100644 --- a/ui/src/app/shared/models.ts +++ b/ui/src/app/shared/models.ts @@ -161,6 +161,7 @@ export type SyncResourceResult = ResourceResult & { }; export const AnnotationRefreshKey = 'argocd.argoproj.io/refresh'; +export const ApplicationSetAnnotationRefreshKey = 'argocd.argoproj.io/application-set-refresh'; export const AnnotationHookKey = 'argocd.argoproj.io/hook'; export const AnnotationSyncWaveKey = 'argocd.argoproj.io/sync-wave'; export const AnnotationDefaultView = 'pref.argocd.argoproj.io/default-view'; diff --git a/ui/src/app/shared/services/applications-service.ts b/ui/src/app/shared/services/applications-service.ts index 79c140289dc6b..ecdb694ec008f 100644 --- a/ui/src/app/shared/services/applications-service.ts +++ b/ui/src/app/shared/services/applications-service.ts @@ -641,6 +641,13 @@ export class ApplicationsService { .then(res => res.body as models.ApplicationSet); } + public refreshApplicationSet(name: string, namespace: string): Promise { + return requests + .post(`/applicationsets/${name}/refresh`) + .send({appsetNamespace: namespace}) + .then(res => this.parseAppFields(res.body, false) as models.ApplicationSet); + } + public async listApplicationSets(): Promise { return requests.get(`/applicationsets`).then(res => res.body as models.ApplicationSetList); }