sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/sidecar/options.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package sidecar
    18  
    19  import (
    20  	"encoding/json"
    21  	"errors"
    22  	"flag"
    23  	"fmt"
    24  
    25  	"sigs.k8s.io/prow/pkg/gcsupload"
    26  	"sigs.k8s.io/prow/pkg/pod-utils/wrapper"
    27  )
    28  
    29  // NewOptions returns an empty Options with no nil fields
    30  func NewOptions() *Options {
    31  	return &Options{
    32  		GcsOptions: gcsupload.NewOptions(),
    33  		// Do not instantiate DeprecatedWrapperOptions by default
    34  	}
    35  }
    36  
    37  // Options exposes the configuration necessary
    38  // for defining the process being watched and
    39  // where in GCS an upload will land.
    40  type Options struct {
    41  	GcsOptions               *gcsupload.Options `json:"gcs_options"`
    42  	DeprecatedWrapperOptions *wrapper.Options   `json:"wrapper_options,omitempty"` // TODO(fejta): remove july 2019
    43  	// Additional entries to wait for if set
    44  	Entries []wrapper.Options `json:"entries,omitempty"`
    45  
    46  	// EntryError requires all entries to pass in order to exit cleanly.
    47  	EntryError bool `json:"entry_error,omitempty"`
    48  
    49  	// IgnoreInterrupts instructs the waiting process to ignore interrupt
    50  	// signals. An interrupt signal is sent to this process when the kubelet
    51  	// decides to delete the test Pod. This may be as a result of:
    52  	//  - the ProwJob exceeding the `default_job_timeout` as configured for Prow
    53  	//  - the ProwJob exceeding the `timeout` as configured for the job itself
    54  	//  - the Pod exceeding the `pod_running_timeout` as configured for Prow
    55  	//  - cluster instability causing the Pod to be evicted
    56  	//
    57  	// When this happens, the `entrypoint` process also gets the signal, and
    58  	// forwards it to the process under test. `entrypoint` will wait for the
    59  	// test process to exit, either configured with:
    60  	//  - `grace_period` in the default decoration configurations for Prow
    61  	//  - `grace_period` in the job's specific configuration
    62  	// After the grace period, `entrypoint` will forcefully terminate the test
    63  	// process and signal to `sidecar` that the process has exited.
    64  	//
    65  	// In parallel, the kubelet will be waiting on the Pod's `terminationGracePeriod`
    66  	// after sending the interrupt signal, at which point the kubelet will forcefully
    67  	// terminate all containers in the Pod.
    68  	//
    69  	// If `ignore_interrupts` is set, `sidecar` will do nothing upon receipt of
    70  	// the interrupt signal; this implicitly means that upload of logs and artifacts
    71  	// will begin when the test process exits, which may be as long as the grace
    72  	// period if the test process does not gracefully handle interrupts. This will
    73  	// require that the user configures the Pod's termination grace period to be
    74  	// longer than the `entrypoint` grace period for the test process and the time
    75  	// taken by `sidecar` to upload all relevant artifacts.
    76  	IgnoreInterrupts bool `json:"ignore_interrupts,omitempty"`
    77  
    78  	// WriteMemoryProfile makes the program write a memory profile periodically while
    79  	// it runs. Use the sigs.k8s.io/prow/hack/analyze-memory-profiles.py script to
    80  	// load the data into time series and plot it for analysis.
    81  	WriteMemoryProfile bool `json:"write_memory_profile,omitempty"`
    82  
    83  	// CensoringOptions are options that pertain to censoring output before upload.
    84  	CensoringOptions *CensoringOptions `json:"censoring_options,omitempty"`
    85  
    86  	// SecretDirectories is deprecated, use censoring_options.secret_directories instead.
    87  	SecretDirectories []string `json:"secret_directories,omitempty"`
    88  	// CensoringConcurrency is deprecated, use censoring_options.censoring_concurrency instead.
    89  	CensoringConcurrency *int64 `json:"censoring_concurrency,omitempty"`
    90  	// CensoringBufferSize is deprecated, use censoring_options.censoring_buffer_size instead.
    91  	CensoringBufferSize *int `json:"censoring_buffer_size,omitempty"`
    92  }
    93  
    94  type CensoringOptions struct {
    95  	// SecretDirectories are paths to directories containing secret data. The contents
    96  	// of these secret data files will be censored from the logs and artifacts uploaded
    97  	// to the cloud.
    98  	SecretDirectories []string `json:"secret_directories,omitempty"`
    99  	// CensoringConcurrency is the maximum number of goroutines that should be censoring
   100  	// artifacts and logs at any time. If unset, defaults to 10.
   101  	CensoringConcurrency *int64 `json:"censoring_concurrency,omitempty"`
   102  	// CensoringBufferSize is the size in bytes of the buffer allocated for every file
   103  	// being censored. We want to keep as little of the file in memory as possible in
   104  	// order for censoring to be reasonably performant in space. However, to guarantee
   105  	// that we censor every instance of every secret, our buffer size must be at least
   106  	// two times larger than the largest secret we are about to censor. While that size
   107  	// is the smallest possible buffer we could use, if the secrets being censored are
   108  	// small, censoring will not be performant as the number of I/O actions per file
   109  	// would increase. If unset, defaults to 10MiB.
   110  	CensoringBufferSize *int `json:"censoring_buffer_size,omitempty"`
   111  
   112  	// IncludeDirectories are directories which should have their content censored, provided
   113  	// as relative path globs from the base of the artifact directory for the test. If
   114  	// present, only content in these directories will be censored. Entries in this list
   115  	// are parsed with the go-zglob library, allowing for globbed matches.
   116  	IncludeDirectories []string `json:"include_directories,omitempty"`
   117  
   118  	// ExcludeDirectories are directories which should not have their content censored,
   119  	// provided as relative path globs from the base of the artifact directory for the
   120  	// test. If present, content in these directories will not be censored even if the
   121  	// directory also matches a glob in IncludeDirectories. Entries in this list are
   122  	// parsed with the go-zglob library, allowing for globbed matches.
   123  	ExcludeDirectories []string `json:"exclude_directories,omitempty"`
   124  
   125  	// IniFilenames are secret filenames that should be parsed as INI files in order to
   126  	// censor the values in the key-value mapping as well as the full content of the file.
   127  	IniFilenames []string `json:"ini_filenames,omitempty"`
   128  }
   129  
   130  func (o Options) entries() []wrapper.Options {
   131  	var e []wrapper.Options
   132  	if o.DeprecatedWrapperOptions != nil {
   133  		e = append(e, *o.DeprecatedWrapperOptions)
   134  	}
   135  	return append(e, o.Entries...)
   136  }
   137  
   138  // Validate ensures that the set of options are
   139  // self-consistent and valid
   140  func (o *Options) Validate() error {
   141  	opts := CensoringOptions{
   142  		SecretDirectories:    o.SecretDirectories,
   143  		CensoringConcurrency: o.CensoringConcurrency,
   144  		CensoringBufferSize:  o.CensoringBufferSize,
   145  	}
   146  	if o.SecretDirectories != nil || o.CensoringConcurrency != nil || o.CensoringBufferSize != nil {
   147  		if o.CensoringOptions != nil {
   148  			return errors.New("cannot use deprecated options (secret_directories, censoring_{concurrency,buffer_size}) and new options (censoring_options) at the same time")
   149  		}
   150  		o.CensoringOptions = &opts
   151  	}
   152  
   153  	ents := o.entries()
   154  	if len(ents) == 0 {
   155  		return errors.New("no wrapper.Option entries")
   156  	}
   157  	for i, e := range ents {
   158  		if err := e.Validate(); err != nil {
   159  			return fmt.Errorf("entry %d: %w", i, err)
   160  		}
   161  	}
   162  
   163  	return o.GcsOptions.Validate()
   164  }
   165  
   166  const (
   167  	// JSONConfigEnvVar is the environment variable that
   168  	// utilities expect to find a full JSON configuration
   169  	// in when run.
   170  	JSONConfigEnvVar = "SIDECAR_OPTIONS"
   171  )
   172  
   173  // ConfigVar exposese the environment variable used
   174  // to store serialized configuration
   175  func (o *Options) ConfigVar() string {
   176  	return JSONConfigEnvVar
   177  }
   178  
   179  // LoadConfig loads options from serialized config
   180  func (o *Options) LoadConfig(config string) error {
   181  	return json.Unmarshal([]byte(config), o)
   182  }
   183  
   184  // AddFlags binds flags to options
   185  func (o *Options) AddFlags(flags *flag.FlagSet) {
   186  	o.GcsOptions.AddFlags(flags)
   187  	// DeprecatedWrapperOptions flags should be unused, remove immediately
   188  }
   189  
   190  // Complete internalizes command line arguments
   191  func (o *Options) Complete(args []string) {
   192  	o.GcsOptions.Complete(args)
   193  }
   194  
   195  // Encode will encode the set of options in the format that
   196  // is expected for the configuration environment variable
   197  func Encode(options Options) (string, error) {
   198  	encoded, err := json.Marshal(options)
   199  	return string(encoded), err
   200  }