tests: upload & test in vCenter. Closes #338
This commit is contained in:
parent
02346faff8
commit
9cce43d384
255 changed files with 127290 additions and 3 deletions
360
vendor/github.com/vmware/govmomi/session/cache/session.go
generated
vendored
Normal file
360
vendor/github.com/vmware/govmomi/session/cache/session.go
generated
vendored
Normal file
|
|
@ -0,0 +1,360 @@
|
|||
/*
|
||||
Copyright (c) 2020 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/vmware/govmomi/session"
|
||||
"github.com/vmware/govmomi/vapi/rest"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// Client interface to support client session caching
|
||||
type Client interface {
|
||||
json.Marshaler
|
||||
json.Unmarshaler
|
||||
|
||||
Valid() bool
|
||||
Path() string
|
||||
}
|
||||
|
||||
// Session provides methods to cache authenticated vim25.Client and rest.Client sessions.
|
||||
// Use of session cache avoids the expense of creating and deleting vSphere sessions.
|
||||
// It also helps avoid the problem of "leaking sessions", as Session.Login will only
|
||||
// create a new authenticated session if the cached session does not exist or is invalid.
|
||||
// By default, username/password authentication is used to create new sessions.
|
||||
// The Session.Login{SOAP,REST} fields can be set to use other methods,
|
||||
// such as SAML token authentication (see govc session.login for example).
|
||||
type Session struct {
|
||||
URL *url.URL // URL of a vCenter or ESXi instance
|
||||
DirSOAP string // DirSOAP cache directory. Defaults to "$HOME/.govmomi/sessions"
|
||||
DirREST string // DirREST cache directory. Defaults to "$HOME/.govmomi/rest_sessions"
|
||||
Insecure bool // Insecure param for soap.NewClient (tls.Config.InsecureSkipVerify)
|
||||
Passthrough bool // Passthrough disables caching when set to true
|
||||
|
||||
LoginSOAP func(context.Context, *vim25.Client) error // LoginSOAP defaults to session.Manager.Login()
|
||||
LoginREST func(context.Context, *rest.Client) error // LoginREST defaults to rest.Client.Login()
|
||||
}
|
||||
|
||||
var (
|
||||
home = os.Getenv("GOVMOMI_HOME")
|
||||
)
|
||||
|
||||
func init() {
|
||||
if home == "" {
|
||||
dir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
dir = os.Getenv("HOME")
|
||||
}
|
||||
home = filepath.Join(dir, ".govmomi")
|
||||
}
|
||||
}
|
||||
|
||||
// Endpoint returns a copy of the Session.URL with Password, Query and Fragment removed.
|
||||
func (s *Session) Endpoint() *url.URL {
|
||||
if s.URL == nil {
|
||||
return nil
|
||||
}
|
||||
p := &url.URL{
|
||||
Scheme: s.URL.Scheme,
|
||||
Host: s.URL.Host,
|
||||
Path: s.URL.Path,
|
||||
}
|
||||
if u := s.URL.User; u != nil {
|
||||
p.User = url.User(u.Username()) // Remove password
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// key is a digest of the URL scheme + username + host + Client.Path()
|
||||
func (s *Session) key(path string) string {
|
||||
p := s.Endpoint()
|
||||
p.Path = path
|
||||
|
||||
// Key session file off of full URI and insecure setting.
|
||||
// Hash key to get a predictable, canonical format.
|
||||
key := fmt.Sprintf("%s#insecure=%t", p.String(), s.Insecure)
|
||||
return fmt.Sprintf("%040x", sha1.Sum([]byte(key)))
|
||||
}
|
||||
|
||||
func (s *Session) file(p string) string {
|
||||
dir := ""
|
||||
|
||||
switch p {
|
||||
case rest.Path:
|
||||
dir = s.DirREST
|
||||
if dir == "" {
|
||||
dir = filepath.Join(home, "rest_sessions")
|
||||
}
|
||||
default:
|
||||
dir = s.DirSOAP
|
||||
if dir == "" {
|
||||
dir = filepath.Join(home, "sessions")
|
||||
}
|
||||
}
|
||||
|
||||
return filepath.Join(dir, s.key(p))
|
||||
}
|
||||
|
||||
// Save a Client in the file cache.
|
||||
// Session will not be saved if Session.Passthrough is true.
|
||||
func (s *Session) Save(c Client) error {
|
||||
if s.Passthrough {
|
||||
return nil
|
||||
}
|
||||
|
||||
p := s.file(c.Path())
|
||||
|
||||
err := os.MkdirAll(filepath.Dir(p), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(p, os.O_CREATE|os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = json.NewEncoder(f).Encode(c)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
func (s *Session) get(c Client) (bool, error) {
|
||||
f, err := os.Open(s.file(c.Path()))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(f)
|
||||
err = dec.Decode(c)
|
||||
if err != nil {
|
||||
_ = f.Close()
|
||||
return false, err
|
||||
}
|
||||
|
||||
return c.Valid(), f.Close()
|
||||
}
|
||||
|
||||
func localTicket(ctx context.Context, m *session.Manager) (*url.Userinfo, error) {
|
||||
name := os.Getenv("USER")
|
||||
u, err := user.Current()
|
||||
if err == nil {
|
||||
name = u.Username
|
||||
}
|
||||
|
||||
ticket, err := m.AcquireLocalTicket(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
password, err := ioutil.ReadFile(ticket.PasswordFilePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return url.UserPassword(ticket.UserName, string(password)), nil
|
||||
}
|
||||
|
||||
func (s *Session) loginSOAP(ctx context.Context, c *vim25.Client) error {
|
||||
m := session.NewManager(c)
|
||||
u := s.URL.User
|
||||
name := u.Username()
|
||||
|
||||
if name == "" && !c.IsVC() {
|
||||
// If no username is provided, try to acquire a local ticket.
|
||||
// When invoked remotely, ESX returns an InvalidRequestFault.
|
||||
// So, rather than return an error here, fallthrough to Login() with the original User to
|
||||
// to avoid what would be a confusing error message.
|
||||
luser, lerr := localTicket(ctx, m)
|
||||
if lerr == nil {
|
||||
// We are running directly on an ESX or Workstation host and can use the ticket with Login()
|
||||
u = luser
|
||||
name = u.Username()
|
||||
}
|
||||
}
|
||||
if name == "" {
|
||||
// ServiceContent does not require authentication
|
||||
return nil
|
||||
}
|
||||
|
||||
return m.Login(ctx, u)
|
||||
}
|
||||
|
||||
func (s *Session) loginREST(ctx context.Context, c *rest.Client) error {
|
||||
return c.Login(ctx, s.URL.User)
|
||||
}
|
||||
|
||||
func soapSessionValid(ctx context.Context, client *vim25.Client) (bool, error) {
|
||||
m := session.NewManager(client)
|
||||
u, err := m.UserSession(ctx)
|
||||
if err != nil {
|
||||
if soap.IsSoapFault(err) {
|
||||
fault := soap.ToSoapFault(err).VimFault()
|
||||
// If the PropertyCollector is not found, the saved session for this URL is not valid
|
||||
if _, ok := fault.(types.ManagedObjectNotFound); ok {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, err
|
||||
}
|
||||
|
||||
return u != nil, nil
|
||||
}
|
||||
|
||||
func restSessionValid(ctx context.Context, client *rest.Client) (bool, error) {
|
||||
s, err := client.Session(ctx)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return s != nil, nil
|
||||
}
|
||||
|
||||
// Load a Client from the file cache.
|
||||
// Returns false if no cache exists or is invalid.
|
||||
// An error is returned if the file cannot be opened or is not json encoded.
|
||||
// After loading the Client from the file:
|
||||
// Returns true if the session is still valid, false otherwise indicating the client requires authentication.
|
||||
// An error is returned if the session ID cannot be validated.
|
||||
// Returns false if Session.Passthrough is true.
|
||||
func (s *Session) Load(ctx context.Context, c Client, config func(*soap.Client) error) (bool, error) {
|
||||
if s.Passthrough {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ok, err := s.get(c)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
}
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
switch client := c.(type) {
|
||||
case *vim25.Client:
|
||||
if config != nil {
|
||||
if err := config(client.Client); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return soapSessionValid(ctx, client)
|
||||
case *rest.Client:
|
||||
if config != nil {
|
||||
if err := config(client.Client); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return restSessionValid(ctx, client)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported client type=%T", client))
|
||||
}
|
||||
}
|
||||
|
||||
// Login returns a cached session via Load() if valid.
|
||||
// Otherwise, creates a new authenticated session and saves to the cache.
|
||||
// The config func can be used to apply soap.Client configuration, such as TLS settings.
|
||||
// When Session.Passthrough is true, Login will always create a new session.
|
||||
func (s *Session) Login(ctx context.Context, c Client, config func(*soap.Client) error) error {
|
||||
ok, err := s.Load(ctx, c, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
sc := soap.NewClient(s.URL, s.Insecure)
|
||||
|
||||
if config != nil {
|
||||
err = config(sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch client := c.(type) {
|
||||
case *vim25.Client:
|
||||
vc, err := vim25.NewClient(ctx, sc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
login := s.loginSOAP
|
||||
if s.LoginSOAP != nil {
|
||||
login = s.LoginSOAP
|
||||
}
|
||||
if err = login(ctx, vc); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*client = *vc
|
||||
c = client
|
||||
case *rest.Client:
|
||||
client.Client = sc.NewServiceClient(rest.Path, "")
|
||||
|
||||
login := s.loginREST
|
||||
if s.LoginREST != nil {
|
||||
login = s.LoginREST
|
||||
}
|
||||
if err = login(ctx, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c = client
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported client type=%T", client))
|
||||
}
|
||||
|
||||
return s.Save(c)
|
||||
}
|
||||
|
||||
// Login calls the Logout method for the given Client if Session.Passthrough is true.
|
||||
// Otherwise returns nil.
|
||||
func (s *Session) Logout(ctx context.Context, c Client) error {
|
||||
if s.Passthrough {
|
||||
switch client := c.(type) {
|
||||
case *vim25.Client:
|
||||
return session.NewManager(client).Logout(ctx)
|
||||
case *rest.Client:
|
||||
return client.Logout(ctx)
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported client type=%T", client))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
40
vendor/github.com/vmware/govmomi/session/keep_alive.go
generated
vendored
Normal file
40
vendor/github.com/vmware/govmomi/session/keep_alive.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
Copyright (c) 2015-2020 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/session/keepalive"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
// KeepAlive is a backward compatible wrapper around KeepAliveHandler.
|
||||
func KeepAlive(roundTripper soap.RoundTripper, idleTime time.Duration) soap.RoundTripper {
|
||||
return KeepAliveHandler(roundTripper, idleTime, nil)
|
||||
}
|
||||
|
||||
// KeepAliveHandler is a backward compatible wrapper around keepalive.NewHandlerSOAP.
|
||||
func KeepAliveHandler(roundTripper soap.RoundTripper, idleTime time.Duration, handler func(soap.RoundTripper) error) soap.RoundTripper {
|
||||
var f func() error
|
||||
if handler != nil {
|
||||
f = func() error {
|
||||
return handler(roundTripper)
|
||||
}
|
||||
}
|
||||
return keepalive.NewHandlerSOAP(roundTripper, idleTime, f)
|
||||
}
|
||||
204
vendor/github.com/vmware/govmomi/session/keepalive/handler.go
generated
vendored
Normal file
204
vendor/github.com/vmware/govmomi/session/keepalive/handler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
/*
|
||||
Copyright (c) 2020 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package keepalive
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/vmware/govmomi/vapi/rest"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
// handler contains the generic keep alive settings and logic
|
||||
type handler struct {
|
||||
mu sync.Mutex
|
||||
notifyStop chan struct{}
|
||||
notifyWaitGroup sync.WaitGroup
|
||||
|
||||
idle time.Duration
|
||||
send func() error
|
||||
}
|
||||
|
||||
// NewHandlerSOAP returns a soap.RoundTripper for use with a vim25.Client
|
||||
// The idle time specifies the interval in between send() requests. Defaults to 10 minutes.
|
||||
// The send func is used to keep a session alive. Defaults to calling vim25 GetCurrentTime().
|
||||
// The keep alive goroutine starts when a Login method is called and runs until Logout is called or send returns an error.
|
||||
func NewHandlerSOAP(c soap.RoundTripper, idle time.Duration, send func() error) *HandlerSOAP {
|
||||
h := &handler{
|
||||
idle: idle,
|
||||
send: send,
|
||||
}
|
||||
|
||||
if send == nil {
|
||||
h.send = func() error {
|
||||
return h.keepAliveSOAP(c)
|
||||
}
|
||||
}
|
||||
|
||||
return &HandlerSOAP{h, c}
|
||||
}
|
||||
|
||||
// NewHandlerREST returns an http.RoundTripper for use with a rest.Client
|
||||
// The idle time specifies the interval in between send() requests. Defaults to 10 minutes.
|
||||
// The send func is used to keep a session alive. Defaults to calling the rest.Client.Session() method
|
||||
// The keep alive goroutine starts when a Login method is called and runs until Logout is called or send returns an error.
|
||||
func NewHandlerREST(c *rest.Client, idle time.Duration, send func() error) *HandlerREST {
|
||||
h := &handler{
|
||||
idle: idle,
|
||||
send: send,
|
||||
}
|
||||
|
||||
if send == nil {
|
||||
h.send = func() error {
|
||||
return h.keepAliveREST(c)
|
||||
}
|
||||
}
|
||||
|
||||
return &HandlerREST{h, c.Transport}
|
||||
}
|
||||
|
||||
func (h *handler) keepAliveSOAP(rt soap.RoundTripper) error {
|
||||
ctx := context.Background()
|
||||
_, err := methods.GetCurrentTime(ctx, rt)
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *handler) keepAliveREST(c *rest.Client) error {
|
||||
ctx := context.Background()
|
||||
|
||||
s, err := c.Session(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if s != nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New(http.StatusText(http.StatusUnauthorized))
|
||||
}
|
||||
|
||||
// Start explicitly starts the keep alive go routine.
|
||||
// For use with session cache.Client, as cached sessions may not involve Login/Logout via RoundTripper.
|
||||
func (h *handler) Start() {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if h.notifyStop != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if h.idle == 0 {
|
||||
h.idle = time.Minute * 10
|
||||
}
|
||||
|
||||
// This channel must be closed to terminate idle timer.
|
||||
h.notifyStop = make(chan struct{})
|
||||
h.notifyWaitGroup.Add(1)
|
||||
|
||||
go func() {
|
||||
for t := time.NewTimer(h.idle); ; {
|
||||
select {
|
||||
case <-h.notifyStop:
|
||||
h.notifyWaitGroup.Done()
|
||||
t.Stop()
|
||||
return
|
||||
case <-t.C:
|
||||
if err := h.send(); err != nil {
|
||||
h.notifyWaitGroup.Done()
|
||||
h.Stop()
|
||||
return
|
||||
}
|
||||
t.Reset(h.idle)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Stop explicitly stops the keep alive go routine.
|
||||
// For use with session cache.Client, as cached sessions may not involve Login/Logout via RoundTripper.
|
||||
func (h *handler) Stop() {
|
||||
h.mu.Lock()
|
||||
defer h.mu.Unlock()
|
||||
|
||||
if h.notifyStop != nil {
|
||||
close(h.notifyStop)
|
||||
h.notifyWaitGroup.Wait()
|
||||
h.notifyStop = nil
|
||||
}
|
||||
}
|
||||
|
||||
// HandlerSOAP is a keep alive implementation for use with vim25.Client
|
||||
type HandlerSOAP struct {
|
||||
*handler
|
||||
|
||||
roundTripper soap.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements soap.RoundTripper
|
||||
func (h *HandlerSOAP) RoundTrip(ctx context.Context, req, res soap.HasFault) error {
|
||||
// Stop ticker on logout.
|
||||
switch req.(type) {
|
||||
case *methods.LogoutBody:
|
||||
h.Stop()
|
||||
}
|
||||
|
||||
err := h.roundTripper.RoundTrip(ctx, req, res)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Start ticker on login.
|
||||
switch req.(type) {
|
||||
case *methods.LoginBody, *methods.LoginExtensionByCertificateBody, *methods.LoginByTokenBody:
|
||||
h.Start()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandlerREST is a keep alive implementation for use with rest.Client
|
||||
type HandlerREST struct {
|
||||
*handler
|
||||
|
||||
roundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper
|
||||
func (h *HandlerREST) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
if req.URL.Path != "/rest/com/vmware/cis/session" {
|
||||
return h.roundTripper.RoundTrip(req)
|
||||
}
|
||||
|
||||
if req.Method == http.MethodDelete { // Logout
|
||||
h.Stop()
|
||||
}
|
||||
|
||||
res, err := h.roundTripper.RoundTrip(req)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if req.Method == http.MethodPost { // Login
|
||||
h.Start()
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
283
vendor/github.com/vmware/govmomi/session/manager.go
generated
vendored
Normal file
283
vendor/github.com/vmware/govmomi/session/manager.go
generated
vendored
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
/*
|
||||
Copyright (c) 2015 VMware, Inc. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/property"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/methods"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
// Locale defaults to "en_US" and can be overridden via this var or the GOVMOMI_LOCALE env var.
|
||||
// A value of "_" uses the server locale setting.
|
||||
var Locale = os.Getenv("GOVMOMI_LOCALE")
|
||||
|
||||
func init() {
|
||||
if Locale == "_" {
|
||||
Locale = ""
|
||||
} else if Locale == "" {
|
||||
Locale = "en_US"
|
||||
}
|
||||
}
|
||||
|
||||
// Secret returns the contents if a file path value is given, otherwise returns value itself.
|
||||
func Secret(value string) (string, error) {
|
||||
if len(value) == 0 {
|
||||
return value, nil
|
||||
}
|
||||
contents, err := ioutil.ReadFile(value)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return "", err
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
return strings.TrimSpace(string(contents)), nil
|
||||
}
|
||||
|
||||
type Manager struct {
|
||||
client *vim25.Client
|
||||
userSession *types.UserSession
|
||||
}
|
||||
|
||||
func NewManager(client *vim25.Client) *Manager {
|
||||
m := Manager{
|
||||
client: client,
|
||||
}
|
||||
|
||||
return &m
|
||||
}
|
||||
|
||||
func (sm Manager) Reference() types.ManagedObjectReference {
|
||||
return *sm.client.ServiceContent.SessionManager
|
||||
}
|
||||
|
||||
func (sm *Manager) SetLocale(ctx context.Context, locale string) error {
|
||||
req := types.SetLocale{
|
||||
This: sm.Reference(),
|
||||
Locale: locale,
|
||||
}
|
||||
|
||||
_, err := methods.SetLocale(ctx, sm.client, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sm *Manager) Login(ctx context.Context, u *url.Userinfo) error {
|
||||
req := types.Login{
|
||||
This: sm.Reference(),
|
||||
Locale: Locale,
|
||||
}
|
||||
|
||||
if u != nil {
|
||||
req.UserName = u.Username()
|
||||
if pw, ok := u.Password(); ok {
|
||||
req.Password = pw
|
||||
}
|
||||
}
|
||||
|
||||
login, err := methods.Login(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm.userSession = &login.Returnval
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoginExtensionByCertificate uses the vCenter SDK tunnel to login using a client certificate.
|
||||
// The client certificate can be set using the soap.Client.SetCertificate method.
|
||||
// See: https://kb.vmware.com/s/article/2004305
|
||||
func (sm *Manager) LoginExtensionByCertificate(ctx context.Context, key string) error {
|
||||
c := sm.client
|
||||
u := c.URL()
|
||||
if u.Hostname() != "sdkTunnel" {
|
||||
sc := c.Tunnel()
|
||||
c = &vim25.Client{
|
||||
Client: sc,
|
||||
RoundTripper: sc,
|
||||
ServiceContent: c.ServiceContent,
|
||||
}
|
||||
// When http.Transport.Proxy is used, our thumbprint checker is bypassed, resulting in:
|
||||
// "Post https://sdkTunnel:8089/sdk: x509: certificate is valid for $vcenter_hostname, not sdkTunnel"
|
||||
// The only easy way around this is to disable verification for the call to LoginExtensionByCertificate().
|
||||
// TODO: find a way to avoid disabling InsecureSkipVerify.
|
||||
c.DefaultTransport().TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
|
||||
req := types.LoginExtensionByCertificate{
|
||||
This: sm.Reference(),
|
||||
ExtensionKey: key,
|
||||
Locale: Locale,
|
||||
}
|
||||
|
||||
login, err := methods.LoginExtensionByCertificate(ctx, c, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Copy the session cookie
|
||||
sm.client.Jar.SetCookies(u, c.Jar.Cookies(c.URL()))
|
||||
|
||||
sm.userSession = &login.Returnval
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Manager) LoginByToken(ctx context.Context) error {
|
||||
req := types.LoginByToken{
|
||||
This: sm.Reference(),
|
||||
Locale: Locale,
|
||||
}
|
||||
|
||||
login, err := methods.LoginByToken(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm.userSession = &login.Returnval
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sm *Manager) Logout(ctx context.Context) error {
|
||||
req := types.Logout{
|
||||
This: sm.Reference(),
|
||||
}
|
||||
|
||||
_, err := methods.Logout(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm.userSession = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// UserSession retrieves and returns the SessionManager's CurrentSession field.
|
||||
// Nil is returned if the session is not authenticated.
|
||||
func (sm *Manager) UserSession(ctx context.Context) (*types.UserSession, error) {
|
||||
var mgr mo.SessionManager
|
||||
|
||||
pc := property.DefaultCollector(sm.client)
|
||||
err := pc.RetrieveOne(ctx, sm.Reference(), []string{"currentSession"}, &mgr)
|
||||
if err != nil {
|
||||
// It's OK if we can't retrieve properties because we're not authenticated
|
||||
if f, ok := err.(types.HasFault); ok {
|
||||
switch f.Fault().(type) {
|
||||
case *types.NotAuthenticated:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mgr.CurrentSession, nil
|
||||
}
|
||||
|
||||
func (sm *Manager) TerminateSession(ctx context.Context, sessionId []string) error {
|
||||
req := types.TerminateSession{
|
||||
This: sm.Reference(),
|
||||
SessionId: sessionId,
|
||||
}
|
||||
|
||||
_, err := methods.TerminateSession(ctx, sm.client, &req)
|
||||
return err
|
||||
}
|
||||
|
||||
// SessionIsActive checks whether the session that was created at login is
|
||||
// still valid. This function only works against vCenter.
|
||||
func (sm *Manager) SessionIsActive(ctx context.Context) (bool, error) {
|
||||
if sm.userSession == nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
req := types.SessionIsActive{
|
||||
This: sm.Reference(),
|
||||
SessionID: sm.userSession.Key,
|
||||
UserName: sm.userSession.UserName,
|
||||
}
|
||||
|
||||
active, err := methods.SessionIsActive(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return active.Returnval, err
|
||||
}
|
||||
|
||||
func (sm *Manager) AcquireGenericServiceTicket(ctx context.Context, spec types.BaseSessionManagerServiceRequestSpec) (*types.SessionManagerGenericServiceTicket, error) {
|
||||
req := types.AcquireGenericServiceTicket{
|
||||
This: sm.Reference(),
|
||||
Spec: spec,
|
||||
}
|
||||
|
||||
res, err := methods.AcquireGenericServiceTicket(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
func (sm *Manager) AcquireLocalTicket(ctx context.Context, userName string) (*types.SessionManagerLocalTicket, error) {
|
||||
req := types.AcquireLocalTicket{
|
||||
This: sm.Reference(),
|
||||
UserName: userName,
|
||||
}
|
||||
|
||||
res, err := methods.AcquireLocalTicket(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &res.Returnval, nil
|
||||
}
|
||||
|
||||
func (sm *Manager) AcquireCloneTicket(ctx context.Context) (string, error) {
|
||||
req := types.AcquireCloneTicket{
|
||||
This: sm.Reference(),
|
||||
}
|
||||
|
||||
res, err := methods.AcquireCloneTicket(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return res.Returnval, nil
|
||||
}
|
||||
|
||||
func (sm *Manager) CloneSession(ctx context.Context, ticket string) error {
|
||||
req := types.CloneSession{
|
||||
This: sm.Reference(),
|
||||
CloneTicket: ticket,
|
||||
}
|
||||
|
||||
res, err := methods.CloneSession(ctx, sm.client, &req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sm.userSession = &res.Returnval
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue