...

Source file src/k8s.io/client-go/testing/actions.go

Documentation: k8s.io/client-go/testing

     1  /*
     2  Copyright 2015 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 testing
    18  
    19  import (
    20  	"fmt"
    21  	"path"
    22  	"strings"
    23  
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/fields"
    26  	"k8s.io/apimachinery/pkg/labels"
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/runtime/schema"
    29  	"k8s.io/apimachinery/pkg/types"
    30  )
    31  
    32  func NewRootGetAction(resource schema.GroupVersionResource, name string) GetActionImpl {
    33  	action := GetActionImpl{}
    34  	action.Verb = "get"
    35  	action.Resource = resource
    36  	action.Name = name
    37  
    38  	return action
    39  }
    40  
    41  func NewGetAction(resource schema.GroupVersionResource, namespace, name string) GetActionImpl {
    42  	action := GetActionImpl{}
    43  	action.Verb = "get"
    44  	action.Resource = resource
    45  	action.Namespace = namespace
    46  	action.Name = name
    47  
    48  	return action
    49  }
    50  
    51  func NewGetSubresourceAction(resource schema.GroupVersionResource, namespace, subresource, name string) GetActionImpl {
    52  	action := GetActionImpl{}
    53  	action.Verb = "get"
    54  	action.Resource = resource
    55  	action.Subresource = subresource
    56  	action.Namespace = namespace
    57  	action.Name = name
    58  
    59  	return action
    60  }
    61  
    62  func NewRootGetSubresourceAction(resource schema.GroupVersionResource, subresource, name string) GetActionImpl {
    63  	action := GetActionImpl{}
    64  	action.Verb = "get"
    65  	action.Resource = resource
    66  	action.Subresource = subresource
    67  	action.Name = name
    68  
    69  	return action
    70  }
    71  
    72  func NewRootListAction(resource schema.GroupVersionResource, kind schema.GroupVersionKind, opts interface{}) ListActionImpl {
    73  	action := ListActionImpl{}
    74  	action.Verb = "list"
    75  	action.Resource = resource
    76  	action.Kind = kind
    77  	labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
    78  	action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
    79  
    80  	return action
    81  }
    82  
    83  func NewListAction(resource schema.GroupVersionResource, kind schema.GroupVersionKind, namespace string, opts interface{}) ListActionImpl {
    84  	action := ListActionImpl{}
    85  	action.Verb = "list"
    86  	action.Resource = resource
    87  	action.Kind = kind
    88  	action.Namespace = namespace
    89  	labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
    90  	action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
    91  
    92  	return action
    93  }
    94  
    95  func NewRootCreateAction(resource schema.GroupVersionResource, object runtime.Object) CreateActionImpl {
    96  	action := CreateActionImpl{}
    97  	action.Verb = "create"
    98  	action.Resource = resource
    99  	action.Object = object
   100  
   101  	return action
   102  }
   103  
   104  func NewCreateAction(resource schema.GroupVersionResource, namespace string, object runtime.Object) CreateActionImpl {
   105  	action := CreateActionImpl{}
   106  	action.Verb = "create"
   107  	action.Resource = resource
   108  	action.Namespace = namespace
   109  	action.Object = object
   110  
   111  	return action
   112  }
   113  
   114  func NewRootCreateSubresourceAction(resource schema.GroupVersionResource, name, subresource string, object runtime.Object) CreateActionImpl {
   115  	action := CreateActionImpl{}
   116  	action.Verb = "create"
   117  	action.Resource = resource
   118  	action.Subresource = subresource
   119  	action.Name = name
   120  	action.Object = object
   121  
   122  	return action
   123  }
   124  
   125  func NewCreateSubresourceAction(resource schema.GroupVersionResource, name, subresource, namespace string, object runtime.Object) CreateActionImpl {
   126  	action := CreateActionImpl{}
   127  	action.Verb = "create"
   128  	action.Resource = resource
   129  	action.Namespace = namespace
   130  	action.Subresource = subresource
   131  	action.Name = name
   132  	action.Object = object
   133  
   134  	return action
   135  }
   136  
   137  func NewRootUpdateAction(resource schema.GroupVersionResource, object runtime.Object) UpdateActionImpl {
   138  	action := UpdateActionImpl{}
   139  	action.Verb = "update"
   140  	action.Resource = resource
   141  	action.Object = object
   142  
   143  	return action
   144  }
   145  
   146  func NewUpdateAction(resource schema.GroupVersionResource, namespace string, object runtime.Object) UpdateActionImpl {
   147  	action := UpdateActionImpl{}
   148  	action.Verb = "update"
   149  	action.Resource = resource
   150  	action.Namespace = namespace
   151  	action.Object = object
   152  
   153  	return action
   154  }
   155  
   156  func NewRootPatchAction(resource schema.GroupVersionResource, name string, pt types.PatchType, patch []byte) PatchActionImpl {
   157  	action := PatchActionImpl{}
   158  	action.Verb = "patch"
   159  	action.Resource = resource
   160  	action.Name = name
   161  	action.PatchType = pt
   162  	action.Patch = patch
   163  
   164  	return action
   165  }
   166  
   167  func NewPatchAction(resource schema.GroupVersionResource, namespace string, name string, pt types.PatchType, patch []byte) PatchActionImpl {
   168  	action := PatchActionImpl{}
   169  	action.Verb = "patch"
   170  	action.Resource = resource
   171  	action.Namespace = namespace
   172  	action.Name = name
   173  	action.PatchType = pt
   174  	action.Patch = patch
   175  
   176  	return action
   177  }
   178  
   179  func NewRootPatchSubresourceAction(resource schema.GroupVersionResource, name string, pt types.PatchType, patch []byte, subresources ...string) PatchActionImpl {
   180  	action := PatchActionImpl{}
   181  	action.Verb = "patch"
   182  	action.Resource = resource
   183  	action.Subresource = path.Join(subresources...)
   184  	action.Name = name
   185  	action.PatchType = pt
   186  	action.Patch = patch
   187  
   188  	return action
   189  }
   190  
   191  func NewPatchSubresourceAction(resource schema.GroupVersionResource, namespace, name string, pt types.PatchType, patch []byte, subresources ...string) PatchActionImpl {
   192  	action := PatchActionImpl{}
   193  	action.Verb = "patch"
   194  	action.Resource = resource
   195  	action.Subresource = path.Join(subresources...)
   196  	action.Namespace = namespace
   197  	action.Name = name
   198  	action.PatchType = pt
   199  	action.Patch = patch
   200  
   201  	return action
   202  }
   203  
   204  func NewRootUpdateSubresourceAction(resource schema.GroupVersionResource, subresource string, object runtime.Object) UpdateActionImpl {
   205  	action := UpdateActionImpl{}
   206  	action.Verb = "update"
   207  	action.Resource = resource
   208  	action.Subresource = subresource
   209  	action.Object = object
   210  
   211  	return action
   212  }
   213  func NewUpdateSubresourceAction(resource schema.GroupVersionResource, subresource string, namespace string, object runtime.Object) UpdateActionImpl {
   214  	action := UpdateActionImpl{}
   215  	action.Verb = "update"
   216  	action.Resource = resource
   217  	action.Subresource = subresource
   218  	action.Namespace = namespace
   219  	action.Object = object
   220  
   221  	return action
   222  }
   223  
   224  func NewRootDeleteAction(resource schema.GroupVersionResource, name string) DeleteActionImpl {
   225  	return NewRootDeleteActionWithOptions(resource, name, metav1.DeleteOptions{})
   226  }
   227  
   228  func NewRootDeleteActionWithOptions(resource schema.GroupVersionResource, name string, opts metav1.DeleteOptions) DeleteActionImpl {
   229  	action := DeleteActionImpl{}
   230  	action.Verb = "delete"
   231  	action.Resource = resource
   232  	action.Name = name
   233  	action.DeleteOptions = opts
   234  
   235  	return action
   236  }
   237  
   238  func NewRootDeleteSubresourceAction(resource schema.GroupVersionResource, subresource string, name string) DeleteActionImpl {
   239  	action := DeleteActionImpl{}
   240  	action.Verb = "delete"
   241  	action.Resource = resource
   242  	action.Subresource = subresource
   243  	action.Name = name
   244  
   245  	return action
   246  }
   247  
   248  func NewDeleteAction(resource schema.GroupVersionResource, namespace, name string) DeleteActionImpl {
   249  	return NewDeleteActionWithOptions(resource, namespace, name, metav1.DeleteOptions{})
   250  }
   251  
   252  func NewDeleteActionWithOptions(resource schema.GroupVersionResource, namespace, name string, opts metav1.DeleteOptions) DeleteActionImpl {
   253  	action := DeleteActionImpl{}
   254  	action.Verb = "delete"
   255  	action.Resource = resource
   256  	action.Namespace = namespace
   257  	action.Name = name
   258  	action.DeleteOptions = opts
   259  
   260  	return action
   261  }
   262  
   263  func NewDeleteSubresourceAction(resource schema.GroupVersionResource, subresource, namespace, name string) DeleteActionImpl {
   264  	action := DeleteActionImpl{}
   265  	action.Verb = "delete"
   266  	action.Resource = resource
   267  	action.Subresource = subresource
   268  	action.Namespace = namespace
   269  	action.Name = name
   270  
   271  	return action
   272  }
   273  
   274  func NewRootDeleteCollectionAction(resource schema.GroupVersionResource, opts interface{}) DeleteCollectionActionImpl {
   275  	action := DeleteCollectionActionImpl{}
   276  	action.Verb = "delete-collection"
   277  	action.Resource = resource
   278  	labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
   279  	action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
   280  
   281  	return action
   282  }
   283  
   284  func NewDeleteCollectionAction(resource schema.GroupVersionResource, namespace string, opts interface{}) DeleteCollectionActionImpl {
   285  	action := DeleteCollectionActionImpl{}
   286  	action.Verb = "delete-collection"
   287  	action.Resource = resource
   288  	action.Namespace = namespace
   289  	labelSelector, fieldSelector, _ := ExtractFromListOptions(opts)
   290  	action.ListRestrictions = ListRestrictions{labelSelector, fieldSelector}
   291  
   292  	return action
   293  }
   294  
   295  func NewRootWatchAction(resource schema.GroupVersionResource, opts interface{}) WatchActionImpl {
   296  	action := WatchActionImpl{}
   297  	action.Verb = "watch"
   298  	action.Resource = resource
   299  	labelSelector, fieldSelector, resourceVersion := ExtractFromListOptions(opts)
   300  	action.WatchRestrictions = WatchRestrictions{labelSelector, fieldSelector, resourceVersion}
   301  
   302  	return action
   303  }
   304  
   305  func ExtractFromListOptions(opts interface{}) (labelSelector labels.Selector, fieldSelector fields.Selector, resourceVersion string) {
   306  	var err error
   307  	switch t := opts.(type) {
   308  	case metav1.ListOptions:
   309  		labelSelector, err = labels.Parse(t.LabelSelector)
   310  		if err != nil {
   311  			panic(fmt.Errorf("invalid selector %q: %v", t.LabelSelector, err))
   312  		}
   313  		fieldSelector, err = fields.ParseSelector(t.FieldSelector)
   314  		if err != nil {
   315  			panic(fmt.Errorf("invalid selector %q: %v", t.FieldSelector, err))
   316  		}
   317  		resourceVersion = t.ResourceVersion
   318  	default:
   319  		panic(fmt.Errorf("expect a ListOptions %T", opts))
   320  	}
   321  	if labelSelector == nil {
   322  		labelSelector = labels.Everything()
   323  	}
   324  	if fieldSelector == nil {
   325  		fieldSelector = fields.Everything()
   326  	}
   327  	return labelSelector, fieldSelector, resourceVersion
   328  }
   329  
   330  func NewWatchAction(resource schema.GroupVersionResource, namespace string, opts interface{}) WatchActionImpl {
   331  	action := WatchActionImpl{}
   332  	action.Verb = "watch"
   333  	action.Resource = resource
   334  	action.Namespace = namespace
   335  	labelSelector, fieldSelector, resourceVersion := ExtractFromListOptions(opts)
   336  	action.WatchRestrictions = WatchRestrictions{labelSelector, fieldSelector, resourceVersion}
   337  
   338  	return action
   339  }
   340  
   341  func NewProxyGetAction(resource schema.GroupVersionResource, namespace, scheme, name, port, path string, params map[string]string) ProxyGetActionImpl {
   342  	action := ProxyGetActionImpl{}
   343  	action.Verb = "get"
   344  	action.Resource = resource
   345  	action.Namespace = namespace
   346  	action.Scheme = scheme
   347  	action.Name = name
   348  	action.Port = port
   349  	action.Path = path
   350  	action.Params = params
   351  	return action
   352  }
   353  
   354  type ListRestrictions struct {
   355  	Labels labels.Selector
   356  	Fields fields.Selector
   357  }
   358  type WatchRestrictions struct {
   359  	Labels          labels.Selector
   360  	Fields          fields.Selector
   361  	ResourceVersion string
   362  }
   363  
   364  type Action interface {
   365  	GetNamespace() string
   366  	GetVerb() string
   367  	GetResource() schema.GroupVersionResource
   368  	GetSubresource() string
   369  	Matches(verb, resource string) bool
   370  
   371  	// DeepCopy is used to copy an action to avoid any risk of accidental mutation.  Most people never need to call this
   372  	// because the invocation logic deep copies before calls to storage and reactors.
   373  	DeepCopy() Action
   374  }
   375  
   376  type GenericAction interface {
   377  	Action
   378  	GetValue() interface{}
   379  }
   380  
   381  type GetAction interface {
   382  	Action
   383  	GetName() string
   384  }
   385  
   386  type ListAction interface {
   387  	Action
   388  	GetListRestrictions() ListRestrictions
   389  }
   390  
   391  type CreateAction interface {
   392  	Action
   393  	GetObject() runtime.Object
   394  }
   395  
   396  type UpdateAction interface {
   397  	Action
   398  	GetObject() runtime.Object
   399  }
   400  
   401  type DeleteAction interface {
   402  	Action
   403  	GetName() string
   404  	GetDeleteOptions() metav1.DeleteOptions
   405  }
   406  
   407  type DeleteCollectionAction interface {
   408  	Action
   409  	GetListRestrictions() ListRestrictions
   410  }
   411  
   412  type PatchAction interface {
   413  	Action
   414  	GetName() string
   415  	GetPatchType() types.PatchType
   416  	GetPatch() []byte
   417  }
   418  
   419  type WatchAction interface {
   420  	Action
   421  	GetWatchRestrictions() WatchRestrictions
   422  }
   423  
   424  type ProxyGetAction interface {
   425  	Action
   426  	GetScheme() string
   427  	GetName() string
   428  	GetPort() string
   429  	GetPath() string
   430  	GetParams() map[string]string
   431  }
   432  
   433  type ActionImpl struct {
   434  	Namespace   string
   435  	Verb        string
   436  	Resource    schema.GroupVersionResource
   437  	Subresource string
   438  }
   439  
   440  func (a ActionImpl) GetNamespace() string {
   441  	return a.Namespace
   442  }
   443  func (a ActionImpl) GetVerb() string {
   444  	return a.Verb
   445  }
   446  func (a ActionImpl) GetResource() schema.GroupVersionResource {
   447  	return a.Resource
   448  }
   449  func (a ActionImpl) GetSubresource() string {
   450  	return a.Subresource
   451  }
   452  func (a ActionImpl) Matches(verb, resource string) bool {
   453  	// Stay backwards compatible.
   454  	if !strings.Contains(resource, "/") {
   455  		return strings.EqualFold(verb, a.Verb) &&
   456  			strings.EqualFold(resource, a.Resource.Resource)
   457  	}
   458  
   459  	parts := strings.SplitN(resource, "/", 2)
   460  	topresource, subresource := parts[0], parts[1]
   461  
   462  	return strings.EqualFold(verb, a.Verb) &&
   463  		strings.EqualFold(topresource, a.Resource.Resource) &&
   464  		strings.EqualFold(subresource, a.Subresource)
   465  }
   466  func (a ActionImpl) DeepCopy() Action {
   467  	ret := a
   468  	return ret
   469  }
   470  
   471  type GenericActionImpl struct {
   472  	ActionImpl
   473  	Value interface{}
   474  }
   475  
   476  func (a GenericActionImpl) GetValue() interface{} {
   477  	return a.Value
   478  }
   479  
   480  func (a GenericActionImpl) DeepCopy() Action {
   481  	return GenericActionImpl{
   482  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   483  		// TODO this is wrong, but no worse than before
   484  		Value: a.Value,
   485  	}
   486  }
   487  
   488  type GetActionImpl struct {
   489  	ActionImpl
   490  	Name string
   491  }
   492  
   493  func (a GetActionImpl) GetName() string {
   494  	return a.Name
   495  }
   496  
   497  func (a GetActionImpl) DeepCopy() Action {
   498  	return GetActionImpl{
   499  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   500  		Name:       a.Name,
   501  	}
   502  }
   503  
   504  type ListActionImpl struct {
   505  	ActionImpl
   506  	Kind             schema.GroupVersionKind
   507  	Name             string
   508  	ListRestrictions ListRestrictions
   509  }
   510  
   511  func (a ListActionImpl) GetKind() schema.GroupVersionKind {
   512  	return a.Kind
   513  }
   514  
   515  func (a ListActionImpl) GetListRestrictions() ListRestrictions {
   516  	return a.ListRestrictions
   517  }
   518  
   519  func (a ListActionImpl) DeepCopy() Action {
   520  	return ListActionImpl{
   521  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   522  		Kind:       a.Kind,
   523  		Name:       a.Name,
   524  		ListRestrictions: ListRestrictions{
   525  			Labels: a.ListRestrictions.Labels.DeepCopySelector(),
   526  			Fields: a.ListRestrictions.Fields.DeepCopySelector(),
   527  		},
   528  	}
   529  }
   530  
   531  type CreateActionImpl struct {
   532  	ActionImpl
   533  	Name   string
   534  	Object runtime.Object
   535  }
   536  
   537  func (a CreateActionImpl) GetObject() runtime.Object {
   538  	return a.Object
   539  }
   540  
   541  func (a CreateActionImpl) DeepCopy() Action {
   542  	return CreateActionImpl{
   543  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   544  		Name:       a.Name,
   545  		Object:     a.Object.DeepCopyObject(),
   546  	}
   547  }
   548  
   549  type UpdateActionImpl struct {
   550  	ActionImpl
   551  	Object runtime.Object
   552  }
   553  
   554  func (a UpdateActionImpl) GetObject() runtime.Object {
   555  	return a.Object
   556  }
   557  
   558  func (a UpdateActionImpl) DeepCopy() Action {
   559  	return UpdateActionImpl{
   560  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   561  		Object:     a.Object.DeepCopyObject(),
   562  	}
   563  }
   564  
   565  type PatchActionImpl struct {
   566  	ActionImpl
   567  	Name      string
   568  	PatchType types.PatchType
   569  	Patch     []byte
   570  }
   571  
   572  func (a PatchActionImpl) GetName() string {
   573  	return a.Name
   574  }
   575  
   576  func (a PatchActionImpl) GetPatch() []byte {
   577  	return a.Patch
   578  }
   579  
   580  func (a PatchActionImpl) GetPatchType() types.PatchType {
   581  	return a.PatchType
   582  }
   583  
   584  func (a PatchActionImpl) DeepCopy() Action {
   585  	patch := make([]byte, len(a.Patch))
   586  	copy(patch, a.Patch)
   587  	return PatchActionImpl{
   588  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   589  		Name:       a.Name,
   590  		PatchType:  a.PatchType,
   591  		Patch:      patch,
   592  	}
   593  }
   594  
   595  type DeleteActionImpl struct {
   596  	ActionImpl
   597  	Name          string
   598  	DeleteOptions metav1.DeleteOptions
   599  }
   600  
   601  func (a DeleteActionImpl) GetName() string {
   602  	return a.Name
   603  }
   604  
   605  func (a DeleteActionImpl) GetDeleteOptions() metav1.DeleteOptions {
   606  	return a.DeleteOptions
   607  }
   608  
   609  func (a DeleteActionImpl) DeepCopy() Action {
   610  	return DeleteActionImpl{
   611  		ActionImpl:    a.ActionImpl.DeepCopy().(ActionImpl),
   612  		Name:          a.Name,
   613  		DeleteOptions: *a.DeleteOptions.DeepCopy(),
   614  	}
   615  }
   616  
   617  type DeleteCollectionActionImpl struct {
   618  	ActionImpl
   619  	ListRestrictions ListRestrictions
   620  }
   621  
   622  func (a DeleteCollectionActionImpl) GetListRestrictions() ListRestrictions {
   623  	return a.ListRestrictions
   624  }
   625  
   626  func (a DeleteCollectionActionImpl) DeepCopy() Action {
   627  	return DeleteCollectionActionImpl{
   628  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   629  		ListRestrictions: ListRestrictions{
   630  			Labels: a.ListRestrictions.Labels.DeepCopySelector(),
   631  			Fields: a.ListRestrictions.Fields.DeepCopySelector(),
   632  		},
   633  	}
   634  }
   635  
   636  type WatchActionImpl struct {
   637  	ActionImpl
   638  	WatchRestrictions WatchRestrictions
   639  }
   640  
   641  func (a WatchActionImpl) GetWatchRestrictions() WatchRestrictions {
   642  	return a.WatchRestrictions
   643  }
   644  
   645  func (a WatchActionImpl) DeepCopy() Action {
   646  	return WatchActionImpl{
   647  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   648  		WatchRestrictions: WatchRestrictions{
   649  			Labels:          a.WatchRestrictions.Labels.DeepCopySelector(),
   650  			Fields:          a.WatchRestrictions.Fields.DeepCopySelector(),
   651  			ResourceVersion: a.WatchRestrictions.ResourceVersion,
   652  		},
   653  	}
   654  }
   655  
   656  type ProxyGetActionImpl struct {
   657  	ActionImpl
   658  	Scheme string
   659  	Name   string
   660  	Port   string
   661  	Path   string
   662  	Params map[string]string
   663  }
   664  
   665  func (a ProxyGetActionImpl) GetScheme() string {
   666  	return a.Scheme
   667  }
   668  
   669  func (a ProxyGetActionImpl) GetName() string {
   670  	return a.Name
   671  }
   672  
   673  func (a ProxyGetActionImpl) GetPort() string {
   674  	return a.Port
   675  }
   676  
   677  func (a ProxyGetActionImpl) GetPath() string {
   678  	return a.Path
   679  }
   680  
   681  func (a ProxyGetActionImpl) GetParams() map[string]string {
   682  	return a.Params
   683  }
   684  
   685  func (a ProxyGetActionImpl) DeepCopy() Action {
   686  	params := map[string]string{}
   687  	for k, v := range a.Params {
   688  		params[k] = v
   689  	}
   690  	return ProxyGetActionImpl{
   691  		ActionImpl: a.ActionImpl.DeepCopy().(ActionImpl),
   692  		Scheme:     a.Scheme,
   693  		Name:       a.Name,
   694  		Port:       a.Port,
   695  		Path:       a.Path,
   696  		Params:     params,
   697  	}
   698  }
   699  

View as plain text