@@ -34,6 +34,7 @@ import (
3434 "strings"
3535 "time"
3636
37+ "k8s.io/apimachinery/pkg/util/sets"
3738 utilvalidation "k8s.io/apimachinery/pkg/util/validation"
3839 "k8s.io/apimachinery/pkg/util/validation/field"
3940)
5253 // character cannot be used to create invalid sequences. This is intended as a broad defense against malformed
5354 // input that could cause an escape.
5455 reServiceNameUnsafeCharacters = regexp .MustCompile (`[^a-zA-Z\-_.:0-9@]+` )
56+ reRelativeDate = regexp .MustCompile (`^(\+|\-)?[\d]+(s|m|h|d)$` )
5557)
5658
5759// journalServer returns text output from the OS specific service logger to view
@@ -112,6 +114,19 @@ type options struct {
112114 // Pattern filters log entries by the provided regex pattern. On Linux nodes, this pattern will be read as a
113115 // PCRE2 regex, on Windows nodes it will be read as a PowerShell regex. Support for this is implementation specific.
114116 Pattern string
117+ ocAdm
118+ }
119+
120+ // ocAdm encapsulates the oc adm node-logs specific options
121+ type ocAdm struct {
122+ // Since is an ISO timestamp or relative date from which to show logs
123+ Since string
124+ // Until is an ISO timestamp or relative date until which to show logs
125+ Until string
126+ // Format is the alternate format (short, cat, json, short-unix) to display journal logs
127+ Format string
128+ // CaseSensitive controls the case sensitivity of pattern searches
129+ CaseSensitive bool
115130}
116131
117132// newNodeLogQuery parses query values and converts all known options into nodeLogQuery
@@ -120,7 +135,7 @@ func newNodeLogQuery(query url.Values) (*nodeLogQuery, field.ErrorList) {
120135 var nlq nodeLogQuery
121136 var err error
122137
123- queries , ok := query ["query" ]
138+ queries , okQuery := query ["query" ]
124139 if len (queries ) > 0 {
125140 for _ , q := range queries {
126141 // The presence of / or \ is a hint that the query is for a log file. If the query is for foo.log without a
@@ -132,11 +147,20 @@ func newNodeLogQuery(query url.Values) (*nodeLogQuery, field.ErrorList) {
132147 }
133148 }
134149 }
150+ units , okUnit := query ["unit" ]
151+ if len (units ) > 0 {
152+ for _ , u := range units {
153+ // We don't check for files as the heuristics do not apply to unit
154+ if strings .TrimSpace (u ) != "" { // Prevent queries with just spaces
155+ nlq .Services = append (nlq .Services , u )
156+ }
157+ }
158+ }
135159
136160 // Prevent specifying an empty or blank space query.
137161 // Example: kubectl get --raw /api/v1/nodes/$node/proxy/logs?query=" "
138- if ok && (len (nlq .Files ) == 0 && len (nlq .Services ) == 0 ) {
139- allErrs = append (allErrs , field .Invalid (field .NewPath ("query " ), queries , "may not be empty" ))
162+ if ( okQuery || okUnit ) && (len (nlq .Files ) == 0 && len (nlq .Services ) == 0 ) {
163+ allErrs = append (allErrs , field .Invalid (field .NewPath ("unit " ), queries , "unit cannot be empty" ))
140164 }
141165
142166 var sinceTime time.Time
@@ -174,6 +198,9 @@ func newNodeLogQuery(query url.Values) (*nodeLogQuery, field.ErrorList) {
174198
175199 var tailLines int
176200 tailLinesValue := query .Get ("tailLines" )
201+ if len (tailLinesValue ) == 0 {
202+ tailLinesValue = query .Get ("tail" )
203+ }
177204 if len (tailLinesValue ) > 0 {
178205 tailLines , err = strconv .Atoi (tailLinesValue )
179206 if err != nil {
@@ -184,15 +211,28 @@ func newNodeLogQuery(query url.Values) (*nodeLogQuery, field.ErrorList) {
184211 }
185212
186213 pattern := query .Get ("pattern" )
214+ if len (pattern ) == 0 {
215+ pattern = query .Get ("grep" )
216+ }
187217 if len (pattern ) > 0 {
188218 nlq .Pattern = pattern
219+ caseSensitiveValue := query .Get ("case-sensitive" )
220+ if len (caseSensitiveValue ) > 0 {
221+ caseSensitive , err := strconv .ParseBool (query .Get ("case-sensitive" ))
222+ if err != nil {
223+ allErrs = append (allErrs , field .Invalid (field .NewPath ("case-sensitive" ), query .Get ("case-sensitive" ),
224+ err .Error ()))
225+ } else {
226+ nlq .CaseSensitive = caseSensitive
227+ }
228+ }
189229 }
190230
191- if len ( allErrs ) > 0 {
192- return nil , allErrs
193- }
231+ nlq . Since = query . Get ( "since" )
232+ nlq . Until = query . Get ( "until" )
233+ nlq . Format = query . Get ( "output" )
194234
195- if reflect . DeepEqual ( nlq , nodeLogQuery {}) {
235+ if len ( allErrs ) > 0 {
196236 return nil , allErrs
197237 }
198238
@@ -217,14 +257,13 @@ func validateServices(services []string) field.ErrorList {
217257func (n * nodeLogQuery ) validate () field.ErrorList {
218258 allErrs := validateServices (n .Services )
219259 switch {
220- case len (n .Files ) == 0 && len (n .Services ) == 0 :
221- allErrs = append (allErrs , field .Required (field .NewPath ("query" ), "cannot be empty with options" ))
260+ // OCP: Allow len(n.Files) == 0 && len(n.Services) == 0 as we want to be able to return all journal / WinEvent logs
222261 case len (n .Files ) > 0 && len (n .Services ) > 0 :
223262 allErrs = append (allErrs , field .Invalid (field .NewPath ("query" ), fmt .Sprintf ("%v, %v" , n .Files , n .Services ),
224263 "cannot specify a file and service" ))
225264 case len (n .Files ) > 1 :
226265 allErrs = append (allErrs , field .Invalid (field .NewPath ("query" ), n .Files , "cannot specify more than one file" ))
227- case len (n .Files ) == 1 && n .options != ( options {}):
266+ case len (n .Files ) == 1 && ! reflect . DeepEqual ( n .options , options {}):
228267 allErrs = append (allErrs , field .Invalid (field .NewPath ("query" ), n .Files , "cannot specify file with options" ))
229268 case len (n .Files ) == 1 :
230269 if root , err := os .OpenRoot (nodeLogDir ); err != nil {
@@ -260,6 +299,35 @@ func (n *nodeLogQuery) validate() field.ErrorList {
260299 allErrs = append (allErrs , field .Invalid (field .NewPath ("pattern" ), n .Pattern , err .Error ()))
261300 }
262301
302+ // "oc adm node-logs" specific validation
303+
304+ if n .SinceTime != nil && (len (n .Since ) > 0 || len (n .Until ) > 0 ) {
305+ allErrs = append (allErrs , field .Forbidden (field .NewPath ("sinceTime" ),
306+ "`since or until` and `sinceTime` cannot be specified" ))
307+ }
308+
309+ if n .UntilTime != nil && (len (n .Since ) > 0 || len (n .Until ) > 0 ) {
310+ allErrs = append (allErrs , field .Forbidden (field .NewPath ("untilTime" ),
311+ "`since or until` and `untilTime` cannot be specified" ))
312+ }
313+
314+ if err := validateDate (n .Since ); err != nil {
315+ allErrs = append (allErrs , field .Invalid (field .NewPath ("since" ), n .Since , err .Error ()))
316+ }
317+
318+ if err := validateDate (n .Until ); err != nil {
319+ allErrs = append (allErrs , field .Invalid (field .NewPath ("until" ), n .Until , err .Error ()))
320+ }
321+
322+ allowedFormats := sets .New [string ]("short-precise" , "json" , "short" , "short-unix" , "short-iso" ,
323+ "short-iso-precise" , "cat" , "" )
324+ if len (n .Format ) > 0 && runtime .GOOS == "windows" {
325+ allErrs = append (allErrs , field .Invalid (field .NewPath ("output" ), n .Format ,
326+ "output is not supported on Windows" ))
327+ } else if ! allowedFormats .Has (n .Format ) {
328+ allErrs = append (allErrs , field .NotSupported (field .NewPath ("output" ), n .Format , allowedFormats .UnsortedList ()))
329+ }
330+
263331 return allErrs
264332}
265333
@@ -282,19 +350,20 @@ func (n *nodeLogQuery) copyForBoot(ctx context.Context, w io.Writer, previousBoo
282350 return
283351 }
284352 nativeLoggers , fileLoggers := n .splitNativeVsFileLoggers (ctx )
285- if len (nativeLoggers ) > 0 {
286- n .copyServiceLogs (ctx , w , nativeLoggers , previousBoot )
287- }
288353
289- if len (fileLoggers ) > 0 && n .options != ( options {}) {
354+ if len (fileLoggers ) > 0 && ! reflect . DeepEqual ( n .options , options {}) {
290355 fmt .Fprintf (w , "\n options present and query resolved to log files for %v\n try without specifying options\n " ,
291356 fileLoggers )
292357 return
293358 }
294359
295360 if len (fileLoggers ) > 0 {
296361 copyFileLogs (ctx , w , fileLoggers )
362+ return
297363 }
364+ // OCP: Return all logs in the case where nativeLoggers == ""
365+ n .copyServiceLogs (ctx , w , nativeLoggers , previousBoot )
366+
298367}
299368
300369// splitNativeVsFileLoggers checks if each service logs to native OS logs or to a file and returns a list of services
@@ -440,3 +509,16 @@ func safeServiceName(s string) error {
440509 }
441510 return nil
442511}
512+
513+ func validateDate (date string ) error {
514+ if len (date ) == 0 {
515+ return nil
516+ }
517+ if reRelativeDate .MatchString (date ) {
518+ return nil
519+ }
520+ if _ , err := time .Parse (dateLayout , date ); err == nil {
521+ return nil
522+ }
523+ return fmt .Errorf ("date must be a relative time of the form '(+|-)[0-9]+(s|m|h|d)' or a date in 'YYYY-MM-DD HH:MM:SS' form" )
524+ }
0 commit comments