github.com/vmware/govmomi@v0.37.2/govc/library/info.go (about)

     1  /*
     2  Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
     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 library
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"flag"
    23  	"fmt"
    24  	"io"
    25  	"path"
    26  	"strings"
    27  	"text/tabwriter"
    28  	"time"
    29  
    30  	"github.com/vmware/govmomi/govc/cli"
    31  	"github.com/vmware/govmomi/govc/flags"
    32  	"github.com/vmware/govmomi/object"
    33  	"github.com/vmware/govmomi/units"
    34  	"github.com/vmware/govmomi/vapi/library"
    35  	"github.com/vmware/govmomi/vapi/library/finder"
    36  
    37  	"github.com/vmware/govmomi/property"
    38  	"github.com/vmware/govmomi/vim25/mo"
    39  	"github.com/vmware/govmomi/vim25/types"
    40  )
    41  
    42  type info struct {
    43  	*flags.ClientFlag
    44  	*flags.OutputFlag
    45  	*flags.DatacenterFlag
    46  
    47  	long bool
    48  	link bool
    49  	url  bool
    50  
    51  	names map[string]string
    52  }
    53  
    54  func init() {
    55  	cli.Register("library.info", &info{})
    56  }
    57  
    58  func (cmd *info) Register(ctx context.Context, f *flag.FlagSet) {
    59  	cmd.ClientFlag, ctx = flags.NewClientFlag(ctx)
    60  	cmd.OutputFlag, ctx = flags.NewOutputFlag(ctx)
    61  	cmd.ClientFlag.Register(ctx, f)
    62  	cmd.OutputFlag.Register(ctx, f)
    63  	cmd.DatacenterFlag, ctx = flags.NewDatacenterFlag(ctx)
    64  	cmd.DatacenterFlag.Register(ctx, f)
    65  
    66  	f.BoolVar(&cmd.long, "l", false, "Long listing format")
    67  	f.BoolVar(&cmd.link, "L", false, "List Datastore path only")
    68  	f.BoolVar(&cmd.url, "U", false, "List pub/sub URL(s) only")
    69  
    70  	cmd.names = make(map[string]string)
    71  }
    72  
    73  func (cmd *info) Process(ctx context.Context) error {
    74  	if err := cmd.ClientFlag.Process(ctx); err != nil {
    75  		return err
    76  	}
    77  	return nil
    78  }
    79  
    80  func (cmd *info) Description() string {
    81  	return `Display library information.
    82  
    83  Examples:
    84    govc library.info
    85    govc library.info /lib1
    86    govc library.info -l /lib1 | grep Size:
    87    govc library.info /lib1/item1
    88    govc library.info /lib1/item1/
    89    govc library.info */
    90    govc device.cdrom.insert -vm $vm -device cdrom-3000 $(govc library.info -L /lib1/item1/file1)
    91    govc library.info -json | jq .
    92    govc library.info -json /lib1/item1 | jq .`
    93  }
    94  
    95  type infoResultsWriter struct {
    96  	Result []finder.FindResult `json:"result"`
    97  	m      *library.Manager
    98  	cmd    *info
    99  }
   100  
   101  func (r infoResultsWriter) MarshalJSON() ([]byte, error) {
   102  	return json.Marshal(r.Result)
   103  }
   104  
   105  func (r infoResultsWriter) Dump() interface{} {
   106  	res := make([]interface{}, len(r.Result))
   107  	for i := range r.Result {
   108  		res[i] = r.Result[0].GetResult()
   109  	}
   110  	return res
   111  }
   112  
   113  func (r infoResultsWriter) Write(w io.Writer) error {
   114  	if r.cmd.link {
   115  		for _, j := range r.Result {
   116  			p, err := r.cmd.getDatastoreFilePath(j)
   117  			if err != nil {
   118  				return err
   119  			}
   120  			if !r.cmd.long {
   121  				var path object.DatastorePath
   122  				path.FromString(p)
   123  				p = path.Path
   124  			}
   125  			fmt.Fprintln(w, p)
   126  		}
   127  		return nil
   128  	}
   129  
   130  	tw := tabwriter.NewWriter(w, 2, 0, 2, ' ', 0)
   131  	defer tw.Flush()
   132  	for _, j := range r.Result {
   133  		switch t := j.GetResult().(type) {
   134  		case library.Library:
   135  			if err := r.writeLibrary(tw, t, j); err != nil {
   136  				return err
   137  			}
   138  		case library.Item:
   139  			if err := r.writeItem(tw, t, j); err != nil {
   140  				return err
   141  			}
   142  		case library.File:
   143  			if err := r.writeFile(tw, t, j); err != nil {
   144  				return err
   145  			}
   146  		}
   147  		tw.Flush()
   148  	}
   149  	return nil
   150  }
   151  
   152  func (r infoResultsWriter) writeLibrary(
   153  	w io.Writer, v library.Library, res finder.FindResult) error {
   154  
   155  	published := v.Publication != nil && *v.Publication.Published
   156  
   157  	if r.cmd.url {
   158  		switch {
   159  		case v.Subscription != nil:
   160  			_, _ = fmt.Fprintf(w, "%s\n", v.Subscription.SubscriptionURL)
   161  		case published:
   162  			_, _ = fmt.Fprintf(w, "%s\n", v.Publication.PublishURL)
   163  		}
   164  
   165  		return nil
   166  	}
   167  
   168  	fmt.Fprintf(w, "Name:\t%s\n", v.Name)
   169  	fmt.Fprintf(w, "  ID:\t%s\n", v.ID)
   170  	fmt.Fprintf(w, "  Path:\t%s\n", res.GetPath())
   171  	if v.Description != nil {
   172  		fmt.Fprintf(w, "  Description:\t%s\n", *v.Description)
   173  	}
   174  	fmt.Fprintf(w, "  Version:\t%s\n", v.Version)
   175  	fmt.Fprintf(w, "  Created:\t%s\n", v.CreationTime.Format(time.ANSIC))
   176  	fmt.Fprintf(w, "  Security Policy ID\t%s\n", v.SecurityPolicyID)
   177  	fmt.Fprintf(w, "  StorageBackings:\t\n")
   178  	for _, d := range v.Storage {
   179  		fmt.Fprintf(w, "    DatastoreID:\t%s\n", d.DatastoreID)
   180  		fmt.Fprintf(w, "    Type:\t%s\n", d.Type)
   181  	}
   182  	if r.cmd.long {
   183  		fmt.Fprintf(w, "  Datastore Path:\t%s\n", r.cmd.getDatastorePath(res))
   184  		items, err := r.m.GetLibraryItems(context.Background(), v.ID)
   185  		if err != nil {
   186  			return err
   187  		}
   188  		var size int64
   189  		for i := range items {
   190  			size += items[i].Size
   191  		}
   192  		fmt.Fprintf(w, "  Size:\t%s\n", units.ByteSize(size))
   193  		fmt.Fprintf(w, "  Items:\t%d\n", len(items))
   194  	}
   195  	if v.Subscription != nil {
   196  		dl := "All"
   197  		if v.Subscription.OnDemand != nil && *v.Subscription.OnDemand {
   198  			dl = "On Demand"
   199  		}
   200  
   201  		fmt.Fprintf(w, "  Subscription:\t\n")
   202  		fmt.Fprintf(w, "    AutoSync:\t%t\n", *v.Subscription.AutomaticSyncEnabled)
   203  		fmt.Fprintf(w, "    URL:\t%s\n", v.Subscription.SubscriptionURL)
   204  		fmt.Fprintf(w, "    Auth:\t%s\n", v.Subscription.AuthenticationMethod)
   205  		fmt.Fprintf(w, "    Download:\t%s\n", dl)
   206  	}
   207  	if published {
   208  		fmt.Fprintf(w, "  Publication:\t\n")
   209  		fmt.Fprintf(w, "    URL:\t%s\n", v.Publication.PublishURL)
   210  		fmt.Fprintf(w, "    Auth:\t%s\n", v.Publication.AuthenticationMethod)
   211  	}
   212  	return nil
   213  }
   214  
   215  func (r infoResultsWriter) writeItem(
   216  	w io.Writer, v library.Item, res finder.FindResult) error {
   217  
   218  	fmt.Fprintf(w, "Name:\t%s\n", v.Name)
   219  	fmt.Fprintf(w, "  ID:\t%s\n", v.ID)
   220  	fmt.Fprintf(w, "  Path:\t%s\n", res.GetPath())
   221  	if v.Description != nil {
   222  		fmt.Fprintf(w, "  Description:\t%s\n", *v.Description)
   223  	}
   224  	fmt.Fprintf(w, "  Type:\t%s\n", v.Type)
   225  	fmt.Fprintf(w, "  Size:\t%s\n", units.ByteSize(v.Size))
   226  	fmt.Fprintf(w, "  Cached:\t%t\n", v.Cached)
   227  	fmt.Fprintf(w, "  Created:\t%s\n", v.CreationTime.Format(time.ANSIC))
   228  	fmt.Fprintf(w, "  Modified:\t%s\n", v.LastModifiedTime.Format(time.ANSIC))
   229  	fmt.Fprintf(w, "  Version:\t%s\n", v.Version)
   230  	if v.SecurityCompliance != nil {
   231  		fmt.Fprintf(w, "  Security Compliance:\t%t\n", *v.SecurityCompliance)
   232  	}
   233  	if v.CertificateVerification != nil {
   234  		fmt.Fprintf(w, "  Certificate Status:\t%s\n", v.CertificateVerification.Status)
   235  	}
   236  	if r.cmd.long {
   237  		fmt.Fprintf(w, "  Datastore Path:\t%s\n", r.cmd.getDatastorePath(res))
   238  	}
   239  
   240  	return nil
   241  }
   242  
   243  func (r infoResultsWriter) writeFile(
   244  	w io.Writer, v library.File, res finder.FindResult) error {
   245  
   246  	size := "-"
   247  	if v.Size != nil {
   248  		size = units.ByteSize(*v.Size).String()
   249  	}
   250  	fmt.Fprintf(w, "Name:\t%s\n", v.Name)
   251  	fmt.Fprintf(w, "  Path:\t%s\n", res.GetPath())
   252  	fmt.Fprintf(w, "  Size:\t%s\n", size)
   253  	fmt.Fprintf(w, "  Version:\t%s\n", v.Version)
   254  
   255  	if r.cmd.long {
   256  		fmt.Fprintf(w, "  Datastore Path:\t%s\n", r.cmd.getDatastorePath(res))
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  func (cmd *info) Run(ctx context.Context, f *flag.FlagSet) error {
   263  	c, err := cmd.RestClient()
   264  	if err != nil {
   265  		return err
   266  	}
   267  
   268  	m := library.NewManager(c)
   269  	finder := finder.NewFinder(m)
   270  	findResults, err := finder.Find(ctx, f.Args()...)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	// Lookup the names(s) of the library's datastore(s).
   275  	for i := range findResults {
   276  		if t, ok := findResults[i].GetResult().(library.Library); ok {
   277  			for j := range t.Storage {
   278  				if t.Storage[j].Type == "DATASTORE" {
   279  					t.Storage[j].DatastoreID = cmd.getDatastoreName(t.Storage[j].DatastoreID)
   280  				}
   281  			}
   282  		}
   283  	}
   284  	return cmd.WriteResult(&infoResultsWriter{findResults, m, cmd})
   285  }
   286  
   287  func (cmd *info) getDatastoreName(id string) string {
   288  	if name, ok := cmd.names[id]; ok {
   289  		return name
   290  	}
   291  
   292  	c, err := cmd.Client()
   293  	if err != nil {
   294  		return id
   295  	}
   296  
   297  	obj := types.ManagedObjectReference{
   298  		Type:  "Datastore",
   299  		Value: id,
   300  	}
   301  	pc := property.DefaultCollector(c)
   302  	var me mo.ManagedEntity
   303  
   304  	err = pc.RetrieveOne(context.Background(), obj, []string{"name"}, &me)
   305  	if err != nil {
   306  		return id
   307  	}
   308  
   309  	cmd.names[id] = me.Name
   310  	return me.Name
   311  }
   312  
   313  func (cmd *info) getDatastorePath(r finder.FindResult) string {
   314  	p, _ := cmd.getDatastoreFilePath(r)
   315  	return p
   316  }
   317  
   318  func (cmd *info) getDatastoreFilePath(r finder.FindResult) (string, error) {
   319  	switch x := r.GetResult().(type) {
   320  	case library.Library:
   321  		id := ""
   322  		if len(x.Storage) != 0 {
   323  			id = cmd.getDatastoreName(x.Storage[0].DatastoreID)
   324  		}
   325  		return fmt.Sprintf("[%s] contentlib-%s", id, x.ID), nil
   326  	case library.Item:
   327  		return fmt.Sprintf("%s/%s", cmd.getDatastorePath(r.GetParent()), x.ID), nil
   328  	case library.File:
   329  		return cmd.getDatastoreFileItemPath(r)
   330  	default:
   331  		return "", fmt.Errorf("unsupported type=%T", x)
   332  	}
   333  }
   334  
   335  // getDatastoreFileItemPath returns the absolute datastore path for a library.File
   336  func (cmd *info) getDatastoreFileItemPath(r finder.FindResult) (string, error) {
   337  	name := r.GetName()
   338  	dir := cmd.getDatastorePath(r.GetParent())
   339  	p := path.Join(dir, name)
   340  
   341  	lib := r.GetParent().GetParent().GetResult().(library.Library)
   342  	if len(lib.Storage) == 0 {
   343  		return p, nil
   344  	}
   345  
   346  	ctx := context.Background()
   347  	c, err := cmd.Client()
   348  	if err != nil {
   349  		return p, err
   350  	}
   351  
   352  	ref := types.ManagedObjectReference{Type: "Datastore", Value: lib.Storage[0].DatastoreID}
   353  	ds := object.NewDatastore(c, ref)
   354  
   355  	b, err := ds.Browser(ctx)
   356  	if err != nil {
   357  		return p, err
   358  	}
   359  
   360  	// The file ID isn't available via the API, so we use DatastoreBrowser to search
   361  	ext := path.Ext(name)
   362  	pat := strings.Replace(name, ext, "*"+ext, 1)
   363  	spec := types.HostDatastoreBrowserSearchSpec{
   364  		MatchPattern: []string{pat},
   365  	}
   366  
   367  	task, err := b.SearchDatastore(ctx, dir, &spec)
   368  	if err != nil {
   369  		return p, err
   370  	}
   371  
   372  	info, err := task.WaitForResult(ctx, nil)
   373  	if err != nil {
   374  		return p, err
   375  	}
   376  
   377  	res, ok := info.Result.(types.HostDatastoreBrowserSearchResults)
   378  	if !ok {
   379  		return p, fmt.Errorf("search(%s) result type=%T", pat, info.Result)
   380  	}
   381  
   382  	if len(res.File) != 1 {
   383  		return p, fmt.Errorf("search(%s) result files=%d", pat, len(res.File))
   384  	}
   385  
   386  	return path.Join(dir, res.File[0].GetFileInfo().Path), nil
   387  }