godeps: update vmware/govmomi
Update required to continue work on #64021 - The govmomi tag API changed - Pulling in the new vapi/simulator package for testing the VCP Zones impl
This commit is contained in:
3
vendor/github.com/vmware/govmomi/BUILD
generated
vendored
3
vendor/github.com/vmware/govmomi/BUILD
generated
vendored
@@ -37,6 +37,9 @@ filegroup(
|
||||
"//vendor/github.com/vmware/govmomi/simulator:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/sts:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/task:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/internal:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/rest:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/simulator:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/tags:all-srcs",
|
||||
"//vendor/github.com/vmware/govmomi/vim25:all-srcs",
|
||||
],
|
||||
|
2
vendor/github.com/vmware/govmomi/README.md
generated
vendored
2
vendor/github.com/vmware/govmomi/README.md
generated
vendored
@@ -83,4 +83,4 @@ Refer to the [CHANGELOG](CHANGELOG.md) for version to version changes.
|
||||
|
||||
## License
|
||||
|
||||
govmomi is available under the [Apache 2 license](LICENSE).
|
||||
govmomi is available under the [Apache 2 license](LICENSE.txt).
|
||||
|
4
vendor/github.com/vmware/govmomi/simulator/session_manager.go
generated
vendored
4
vendor/github.com/vmware/govmomi/simulator/session_manager.go
generated
vendored
@@ -137,8 +137,12 @@ func (s *SessionManager) LoginByToken(ctx *Context, req *types.LoginByToken) soa
|
||||
func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault {
|
||||
session := ctx.Session
|
||||
delete(s.sessions, session.Key)
|
||||
pc := Map.content().PropertyCollector
|
||||
|
||||
for ref, obj := range ctx.Session.Registry.objects {
|
||||
if ref == pc {
|
||||
continue // don't unregister the PropertyCollector singleton
|
||||
}
|
||||
if _, ok := obj.(RegisterObject); ok {
|
||||
ctx.Map.Remove(ref) // Remove RegisterObject handlers
|
||||
}
|
||||
|
27
vendor/github.com/vmware/govmomi/vapi/internal/BUILD
generated
vendored
Normal file
27
vendor/github.com/vmware/govmomi/vapi/internal/BUILD
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["internal.go"],
|
||||
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/vapi/internal",
|
||||
importpath = "github.com/vmware/govmomi/vapi/internal",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/vmware/govmomi/vim25/mo:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vim25/types:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
126
vendor/github.com/vmware/govmomi/vapi/internal/internal.go
generated
vendored
Normal file
126
vendor/github.com/vmware/govmomi/vapi/internal/internal.go
generated
vendored
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
Copyright (c) 2018 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 internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
const (
|
||||
Path = "/rest/com/vmware"
|
||||
SessionPath = "/cis/session"
|
||||
CategoryPath = "/cis/tagging/category"
|
||||
TagPath = "/cis/tagging/tag"
|
||||
AssociationPath = "/cis/tagging/tag-association"
|
||||
SessionCookieName = "vmware-api-session-id"
|
||||
)
|
||||
|
||||
// AssociatedObject is the same structure as types.ManagedObjectReference,
|
||||
// just with a different field name (ID instead of Value).
|
||||
// In the API we use mo.Reference, this type is only used for wire transfer.
|
||||
type AssociatedObject struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"id"`
|
||||
}
|
||||
|
||||
// Reference implements mo.Reference
|
||||
func (o AssociatedObject) Reference() types.ManagedObjectReference {
|
||||
return types.ManagedObjectReference(o)
|
||||
}
|
||||
|
||||
// Association for tag-association requests.
|
||||
type Association struct {
|
||||
TagID string `json:"tag_id,omitempty"`
|
||||
ObjectID *AssociatedObject `json:"object_id,omitempty"`
|
||||
}
|
||||
|
||||
// NewAssociation returns an Association, converting ref to an AssociatedObject.
|
||||
func NewAssociation(tagID string, ref mo.Reference) Association {
|
||||
obj := AssociatedObject(ref.Reference())
|
||||
return Association{
|
||||
TagID: tagID,
|
||||
ObjectID: &obj,
|
||||
}
|
||||
}
|
||||
|
||||
type CloneURL interface {
|
||||
URL() *url.URL
|
||||
}
|
||||
|
||||
// Resource wraps url.URL with helpers
|
||||
type Resource struct {
|
||||
u *url.URL
|
||||
}
|
||||
|
||||
func URL(c CloneURL, path string) *Resource {
|
||||
r := &Resource{u: c.URL()}
|
||||
r.u.Path = Path + path
|
||||
return r
|
||||
}
|
||||
|
||||
// WithID appends id to the URL.Path
|
||||
func (r *Resource) WithID(id string) *Resource {
|
||||
r.u.Path += "/id:" + id
|
||||
return r
|
||||
}
|
||||
|
||||
// WithAction sets adds action to the URL.RawQuery
|
||||
func (r *Resource) WithAction(action string) *Resource {
|
||||
r.u.RawQuery = url.Values{
|
||||
"~action": []string{action},
|
||||
}.Encode()
|
||||
return r
|
||||
}
|
||||
|
||||
// Request returns a new http.Request for the given method.
|
||||
// An optional body can be provided for POST and PATCH methods.
|
||||
func (r *Resource) Request(method string, body ...interface{}) *http.Request {
|
||||
rdr := io.MultiReader() // empty body by default
|
||||
if len(body) != 0 {
|
||||
rdr = encode(body[0])
|
||||
}
|
||||
req, err := http.NewRequest(method, r.u.String(), rdr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return req
|
||||
}
|
||||
|
||||
type errorReader struct {
|
||||
e error
|
||||
}
|
||||
|
||||
func (e errorReader) Read([]byte) (int, error) {
|
||||
return -1, e.e
|
||||
}
|
||||
|
||||
// encode body as JSON, deferring any errors until io.Reader is used.
|
||||
func encode(body interface{}) io.Reader {
|
||||
var b bytes.Buffer
|
||||
err := json.NewEncoder(&b).Encode(body)
|
||||
if err != nil {
|
||||
return errorReader{err}
|
||||
}
|
||||
return &b
|
||||
}
|
28
vendor/github.com/vmware/govmomi/vapi/rest/BUILD
generated
vendored
Normal file
28
vendor/github.com/vmware/govmomi/vapi/rest/BUILD
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["client.go"],
|
||||
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/vapi/rest",
|
||||
importpath = "github.com/vmware/govmomi/vapi/rest",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/vmware/govmomi/vapi/internal:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vim25:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vim25/soap:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
105
vendor/github.com/vmware/govmomi/vapi/rest/client.go
generated
vendored
Normal file
105
vendor/github.com/vmware/govmomi/vapi/rest/client.go
generated
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright (c) 2018 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 rest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/vmware/govmomi/vapi/internal"
|
||||
"github.com/vmware/govmomi/vim25"
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
// Client extends soap.Client to support JSON encoding, while inheriting security features, debug tracing and session persistence.
|
||||
type Client struct {
|
||||
*soap.Client
|
||||
}
|
||||
|
||||
// NewClient creates a new Client instance.
|
||||
func NewClient(c *vim25.Client) *Client {
|
||||
sc := c.Client.NewServiceClient(internal.Path, "")
|
||||
|
||||
return &Client{sc}
|
||||
}
|
||||
|
||||
// Do sends the http.Request, decoding resBody if provided.
|
||||
func (c *Client) Do(ctx context.Context, req *http.Request, resBody interface{}) error {
|
||||
switch req.Method {
|
||||
case http.MethodPost, http.MethodPatch:
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
return c.Client.Do(ctx, req, func(res *http.Response) error {
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
case http.StatusBadRequest:
|
||||
// TODO: structured error types
|
||||
detail, err := ioutil.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("%s: %s", res.Status, bytes.TrimSpace(detail))
|
||||
default:
|
||||
return fmt.Errorf("%s %s: %s", req.Method, req.URL, res.Status)
|
||||
}
|
||||
|
||||
if resBody == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch b := resBody.(type) {
|
||||
case io.Writer:
|
||||
_, err := io.Copy(b, res.Body)
|
||||
return err
|
||||
default:
|
||||
val := struct {
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}{
|
||||
resBody,
|
||||
}
|
||||
return json.NewDecoder(res.Body).Decode(&val)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Login creates a new session via Basic Authentication with the given url.Userinfo.
|
||||
func (c *Client) Login(ctx context.Context, user *url.Userinfo) error {
|
||||
req := internal.URL(c, internal.SessionPath).Request(http.MethodPost)
|
||||
|
||||
if user != nil {
|
||||
if password, ok := user.Password(); ok {
|
||||
req.SetBasicAuth(user.Username(), password)
|
||||
}
|
||||
}
|
||||
|
||||
return c.Do(ctx, req, nil)
|
||||
}
|
||||
|
||||
// Logout deletes the current session.
|
||||
func (c *Client) Logout(ctx context.Context) error {
|
||||
req := internal.URL(c, internal.SessionPath).Request(http.MethodDelete)
|
||||
return c.Do(ctx, req, nil)
|
||||
}
|
29
vendor/github.com/vmware/govmomi/vapi/simulator/BUILD
generated
vendored
Normal file
29
vendor/github.com/vmware/govmomi/vapi/simulator/BUILD
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["simulator.go"],
|
||||
importmap = "k8s.io/kubernetes/vendor/github.com/vmware/govmomi/vapi/simulator",
|
||||
importpath = "github.com/vmware/govmomi/vapi/simulator",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/google/uuid:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/internal:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/tags:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vim25/types:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
354
vendor/github.com/vmware/govmomi/vapi/simulator/simulator.go
generated
vendored
Normal file
354
vendor/github.com/vmware/govmomi/vapi/simulator/simulator.go
generated
vendored
Normal file
@@ -0,0 +1,354 @@
|
||||
/*
|
||||
Copyright (c) 2018 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 simulator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/vmware/govmomi/vapi/internal"
|
||||
"github.com/vmware/govmomi/vapi/tags"
|
||||
vim "github.com/vmware/govmomi/vim25/types"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
*http.ServeMux
|
||||
sync.Mutex
|
||||
Category map[string]*tags.Category
|
||||
Tag map[string]*tags.Tag
|
||||
Association map[string]map[internal.AssociatedObject]bool
|
||||
}
|
||||
|
||||
// New creates a vAPI simulator.
|
||||
func New(u *url.URL, settings []vim.BaseOptionValue) (string, http.Handler) {
|
||||
s := &handler{
|
||||
ServeMux: http.NewServeMux(),
|
||||
Category: make(map[string]*tags.Category),
|
||||
Tag: make(map[string]*tags.Tag),
|
||||
Association: make(map[string]map[internal.AssociatedObject]bool),
|
||||
}
|
||||
|
||||
handlers := []struct {
|
||||
p string
|
||||
m http.HandlerFunc
|
||||
}{
|
||||
{internal.SessionPath, s.session},
|
||||
{internal.CategoryPath, s.category},
|
||||
{internal.CategoryPath + "/", s.categoryID},
|
||||
{internal.TagPath, s.tag},
|
||||
{internal.TagPath + "/", s.tagID},
|
||||
{internal.AssociationPath, s.association},
|
||||
}
|
||||
|
||||
for i := range handlers {
|
||||
h := handlers[i]
|
||||
s.HandleFunc(internal.Path+h.p, func(w http.ResponseWriter, r *http.Request) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
|
||||
h.m(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
return internal.Path + "/", s
|
||||
}
|
||||
|
||||
// ok responds with http.StatusOK and json encodes val if given.
|
||||
func (s *handler) ok(w http.ResponseWriter, val ...interface{}) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if len(val) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
err := json.NewEncoder(w).Encode(struct {
|
||||
Value interface{} `json:"value,omitempty"`
|
||||
}{
|
||||
val[0],
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handler) fail(w http.ResponseWriter, kind string) {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
||||
err := json.NewEncoder(w).Encode(struct {
|
||||
Type string `json:"type"`
|
||||
Value struct {
|
||||
Messages []string `json:"messages,omitempty"`
|
||||
} `json:"value,omitempty"`
|
||||
}{
|
||||
Type: kind,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP handles vAPI requests.
|
||||
func (s *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost, http.MethodDelete, http.MethodGet, http.MethodPatch:
|
||||
default:
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
h, _ := s.Handler(r)
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (s *handler) decode(r *http.Request, w http.ResponseWriter, val interface{}) bool {
|
||||
defer r.Body.Close()
|
||||
err := json.NewDecoder(r.Body).Decode(val)
|
||||
if err != nil {
|
||||
log.Printf("%s %s: %s", r.Method, r.RequestURI, err)
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *handler) session(w http.ResponseWriter, r *http.Request) {
|
||||
var id string
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
id = uuid.New().String()
|
||||
// TODO: save session
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: internal.SessionCookieName,
|
||||
Value: id,
|
||||
})
|
||||
s.ok(w)
|
||||
case http.MethodDelete:
|
||||
// TODO: delete session
|
||||
s.ok(w)
|
||||
case http.MethodGet:
|
||||
// TODO: test is session is valid
|
||||
s.ok(w, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handler) action(r *http.Request) string {
|
||||
return r.URL.Query().Get("~action")
|
||||
}
|
||||
|
||||
func (s *handler) id(r *http.Request) string {
|
||||
id := path.Base(r.URL.Path)
|
||||
return strings.TrimPrefix(id, "id:")
|
||||
}
|
||||
|
||||
func newID(kind string) string {
|
||||
return fmt.Sprintf("urn:vmomi:InventoryService%s:%s:GLOBAL", kind, uuid.New().String())
|
||||
}
|
||||
|
||||
func (s *handler) category(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
var spec struct {
|
||||
Category tags.Category `json:"create_spec"`
|
||||
}
|
||||
if s.decode(r, w, &spec) {
|
||||
for _, category := range s.Category {
|
||||
if category.Name == spec.Category.Name {
|
||||
s.fail(w, "com.vmware.vapi.std.errors.already_exists")
|
||||
return
|
||||
}
|
||||
}
|
||||
id := newID("Category")
|
||||
spec.Category.ID = id
|
||||
s.Category[id] = &spec.Category
|
||||
s.ok(w, id)
|
||||
}
|
||||
case http.MethodGet:
|
||||
var ids []string
|
||||
for id := range s.Category {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
|
||||
s.ok(w, ids)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handler) categoryID(w http.ResponseWriter, r *http.Request) {
|
||||
id := s.id(r)
|
||||
|
||||
o, ok := s.Category[id]
|
||||
if !ok {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodDelete:
|
||||
delete(s.Category, id)
|
||||
for ix, tag := range s.Tag {
|
||||
if tag.CategoryID == id {
|
||||
delete(s.Tag, ix)
|
||||
delete(s.Association, ix)
|
||||
}
|
||||
}
|
||||
s.ok(w)
|
||||
case http.MethodPatch:
|
||||
var spec struct {
|
||||
Category tags.Category `json:"update_spec"`
|
||||
}
|
||||
if s.decode(r, w, &spec) {
|
||||
ntypes := len(spec.Category.AssociableTypes)
|
||||
if ntypes != 0 {
|
||||
// Validate that AssociableTypes is only appended to.
|
||||
etypes := len(o.AssociableTypes)
|
||||
fail := ntypes < etypes
|
||||
if !fail {
|
||||
fail = !reflect.DeepEqual(o.AssociableTypes, spec.Category.AssociableTypes[:etypes])
|
||||
}
|
||||
if fail {
|
||||
s.fail(w, "com.vmware.vapi.std.errors.invalid_argument")
|
||||
return
|
||||
}
|
||||
}
|
||||
o.Patch(&spec.Category)
|
||||
s.ok(w)
|
||||
}
|
||||
case http.MethodGet:
|
||||
s.ok(w, o)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handler) tag(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
var spec struct {
|
||||
Tag tags.Tag `json:"create_spec"`
|
||||
}
|
||||
if s.decode(r, w, &spec) {
|
||||
for _, tag := range s.Tag {
|
||||
if tag.Name == spec.Tag.Name {
|
||||
s.fail(w, "com.vmware.vapi.std.errors.already_exists")
|
||||
return
|
||||
}
|
||||
}
|
||||
id := newID("Tag")
|
||||
spec.Tag.ID = id
|
||||
s.Tag[id] = &spec.Tag
|
||||
s.Association[id] = make(map[internal.AssociatedObject]bool)
|
||||
s.ok(w, id)
|
||||
}
|
||||
case http.MethodGet:
|
||||
var ids []string
|
||||
for id := range s.Tag {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
s.ok(w, ids)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handler) tagID(w http.ResponseWriter, r *http.Request) {
|
||||
id := s.id(r)
|
||||
|
||||
switch s.action(r) {
|
||||
case "list-tags-for-category":
|
||||
var ids []string
|
||||
for _, tag := range s.Tag {
|
||||
if tag.CategoryID == id {
|
||||
ids = append(ids, tag.ID)
|
||||
}
|
||||
}
|
||||
s.ok(w, ids)
|
||||
return
|
||||
}
|
||||
|
||||
o, ok := s.Tag[id]
|
||||
if !ok {
|
||||
log.Printf("tag not found: %s", id)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodDelete:
|
||||
delete(s.Tag, id)
|
||||
delete(s.Association, id)
|
||||
s.ok(w)
|
||||
case http.MethodPatch:
|
||||
var spec struct {
|
||||
Tag tags.Tag `json:"update_spec"`
|
||||
}
|
||||
if s.decode(r, w, &spec) {
|
||||
o.Patch(&spec.Tag)
|
||||
s.ok(w)
|
||||
}
|
||||
case http.MethodGet:
|
||||
s.ok(w, o)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *handler) association(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
var spec internal.Association
|
||||
if !s.decode(r, w, &spec) {
|
||||
return
|
||||
}
|
||||
|
||||
if spec.TagID != "" {
|
||||
if _, exists := s.Association[spec.TagID]; !exists {
|
||||
log.Printf("association tag not found: %s", spec.TagID)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch s.action(r) {
|
||||
case "attach":
|
||||
s.Association[spec.TagID][*spec.ObjectID] = true
|
||||
s.ok(w)
|
||||
case "detach":
|
||||
delete(s.Association[spec.TagID], *spec.ObjectID)
|
||||
s.ok(w)
|
||||
case "list-attached-tags":
|
||||
var ids []string
|
||||
for id, objs := range s.Association {
|
||||
if objs[*spec.ObjectID] {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
s.ok(w, ids)
|
||||
case "list-attached-objects":
|
||||
var ids []internal.AssociatedObject
|
||||
for id := range s.Association[spec.TagID] {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
s.ok(w, ids)
|
||||
}
|
||||
}
|
6
vendor/github.com/vmware/govmomi/vapi/tags/BUILD
generated
vendored
6
vendor/github.com/vmware/govmomi/vapi/tags/BUILD
generated
vendored
@@ -4,7 +4,6 @@ go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"categories.go",
|
||||
"rest_client.go",
|
||||
"tag_association.go",
|
||||
"tags.go",
|
||||
],
|
||||
@@ -12,8 +11,9 @@ go_library(
|
||||
importpath = "github.com/vmware/govmomi/vapi/tags",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/github.com/vmware/govmomi/vim25/soap:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vim25/types:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/internal:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vapi/rest:go_default_library",
|
||||
"//vendor/github.com/vmware/govmomi/vim25/mo:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
326
vendor/github.com/vmware/govmomi/vapi/tags/categories.go
generated
vendored
326
vendor/github.com/vmware/govmomi/vapi/tags/categories.go
generated
vendored
@@ -1,226 +1,162 @@
|
||||
// Copyright 2017 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.
|
||||
/*
|
||||
Copyright (c) 2018 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 tags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/vapi/internal"
|
||||
)
|
||||
|
||||
const (
|
||||
CategoryURL = "/com/vmware/cis/tagging/category"
|
||||
ErrAlreadyExists = "already_exists"
|
||||
)
|
||||
|
||||
type CategoryCreateSpec struct {
|
||||
CreateSpec CategoryCreate `json:"create_spec"`
|
||||
}
|
||||
|
||||
type CategoryUpdateSpec struct {
|
||||
UpdateSpec CategoryUpdate `json:"update_spec,omitempty"`
|
||||
}
|
||||
|
||||
type CategoryCreate struct {
|
||||
AssociableTypes []string `json:"associable_types"`
|
||||
Cardinality string `json:"cardinality"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type CategoryUpdate struct {
|
||||
AssociableTypes []string `json:"associable_types,omitempty"`
|
||||
Cardinality string `json:"cardinality,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Category provides methods to create, read, update, delete, and enumerate categories.
|
||||
type Category struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
Cardinality string `json:"cardinality"`
|
||||
AssociableTypes []string `json:"associable_types"`
|
||||
UsedBy []string `json:"used_by"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Cardinality string `json:"cardinality,omitempty"`
|
||||
AssociableTypes []string `json:"associable_types,omitempty"`
|
||||
UsedBy []string `json:"used_by,omitempty"`
|
||||
}
|
||||
|
||||
type CategoryInfo struct {
|
||||
Name string
|
||||
CategoryID string
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateCategoryIfNotExist(ctx context.Context, name string, description string, categoryType string, multiValue bool) (*string, error) {
|
||||
categories, err := c.GetCategoriesByName(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if categories == nil {
|
||||
var multiValueStr string
|
||||
if multiValue {
|
||||
multiValueStr = "MULTIPLE"
|
||||
} else {
|
||||
multiValueStr = "SINGLE"
|
||||
func (c *Category) hasType(kind string) bool {
|
||||
for _, k := range c.AssociableTypes {
|
||||
if kind == k {
|
||||
return true
|
||||
}
|
||||
categoryCreate := CategoryCreate{[]string{categoryType}, multiValueStr, description, name}
|
||||
spec := CategoryCreateSpec{categoryCreate}
|
||||
id, err := c.CreateCategory(ctx, &spec)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Patch merges Category changes from the given src.
|
||||
// AssociableTypes can only be appended to and cannot shrink.
|
||||
func (c *Category) Patch(src *Category) {
|
||||
if src.Name != "" {
|
||||
c.Name = src.Name
|
||||
}
|
||||
if src.Description != "" {
|
||||
c.Description = src.Description
|
||||
}
|
||||
if src.Cardinality != "" {
|
||||
c.Cardinality = src.Cardinality
|
||||
}
|
||||
// Note that in order to append to AssociableTypes any existing types must be included in their original order.
|
||||
for _, kind := range src.AssociableTypes {
|
||||
if !c.hasType(kind) {
|
||||
c.AssociableTypes = append(c.AssociableTypes, kind)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCategory creates a new category and returns the category ID.
|
||||
func (c *Manager) CreateCategory(ctx context.Context, category *Category) (string, error) {
|
||||
// create avoids the annoyance of CreateTag requiring field keys to be included in the request,
|
||||
// even though the field value can be empty.
|
||||
type create struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Cardinality string `json:"cardinality"`
|
||||
AssociableTypes []string `json:"associable_types"`
|
||||
}
|
||||
spec := struct {
|
||||
Category create `json:"create_spec"`
|
||||
}{
|
||||
Category: create{
|
||||
Name: category.Name,
|
||||
Description: category.Description,
|
||||
Cardinality: category.Cardinality,
|
||||
AssociableTypes: category.AssociableTypes,
|
||||
},
|
||||
}
|
||||
if spec.Category.AssociableTypes == nil {
|
||||
// otherwise create fails with invalid_argument
|
||||
spec.Category.AssociableTypes = []string{}
|
||||
}
|
||||
url := internal.URL(c, internal.CategoryPath)
|
||||
var res string
|
||||
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
|
||||
}
|
||||
|
||||
// UpdateCategory can update one or more of the AssociableTypes, Cardinality, Description and Name fields.
|
||||
func (c *Manager) UpdateCategory(ctx context.Context, category *Category) error {
|
||||
spec := struct {
|
||||
Category Category `json:"update_spec"`
|
||||
}{
|
||||
Category: Category{
|
||||
AssociableTypes: category.AssociableTypes,
|
||||
Cardinality: category.Cardinality,
|
||||
Description: category.Description,
|
||||
Name: category.Name,
|
||||
},
|
||||
}
|
||||
url := internal.URL(c, internal.CategoryPath).WithID(category.ID)
|
||||
return c.Do(ctx, url.Request(http.MethodPatch, spec), nil)
|
||||
}
|
||||
|
||||
// DeleteCategory deletes an existing category.
|
||||
func (c *Manager) DeleteCategory(ctx context.Context, category *Category) error {
|
||||
url := internal.URL(c, internal.CategoryPath).WithID(category.ID)
|
||||
return c.Do(ctx, url.Request(http.MethodDelete), nil)
|
||||
}
|
||||
|
||||
// GetCategory fetches the category information for the given identifier.
|
||||
// The id parameter can be a Category ID or Category Name.
|
||||
func (c *Manager) GetCategory(ctx context.Context, id string) (*Category, error) {
|
||||
if isName(id) {
|
||||
cat, err := c.GetCategories(ctx)
|
||||
if err != nil {
|
||||
// in case there are two docker daemon try to create inventory category, query the category once again
|
||||
if strings.Contains(err.Error(), "ErrAlreadyExists") {
|
||||
if categories, err = c.GetCategoriesByName(ctx, name); err != nil {
|
||||
return nil, fmt.Errorf("failed to get inventory category for %s", err)
|
||||
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to create inventory category for %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for i := range cat {
|
||||
if cat[i].Name == id {
|
||||
return &cat[i], nil
|
||||
}
|
||||
} else {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
if categories != nil {
|
||||
return &categories[0].ID, nil
|
||||
}
|
||||
// should not happen
|
||||
return nil, fmt.Errorf("failed to create inventory for it's existed, but could not query back. Please check system")
|
||||
|
||||
url := internal.URL(c, internal.CategoryPath).WithID(id)
|
||||
var res Category
|
||||
return &res, c.Do(ctx, url.Request(http.MethodGet), &res)
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateCategory(ctx context.Context, spec *CategoryCreateSpec) (*string, error) {
|
||||
stream, _, status, err := c.call(ctx, http.MethodPost, CategoryURL, spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("create category failed with status code: %d, error message: %s", status, err)
|
||||
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
var pID RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pID); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
|
||||
}
|
||||
return &(pID.Value), nil
|
||||
// ListCategories returns all category IDs in the system.
|
||||
func (c *Manager) ListCategories(ctx context.Context) ([]string, error) {
|
||||
url := internal.URL(c, internal.CategoryPath)
|
||||
var res []string
|
||||
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
|
||||
}
|
||||
|
||||
func (c *RestClient) GetCategory(ctx context.Context, id string) (*Category, error) {
|
||||
|
||||
stream, _, status, err := c.call(ctx, http.MethodGet, fmt.Sprintf("%s/id:%s", CategoryURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("get category failed with status code: %d, error message: %s", status, err)
|
||||
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value Category
|
||||
}
|
||||
|
||||
var pCategory RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pCategory); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
|
||||
}
|
||||
return &(pCategory.Value), nil
|
||||
}
|
||||
|
||||
func (c *RestClient) UpdateCategory(ctx context.Context, id string, spec *CategoryUpdateSpec) error {
|
||||
_, _, status, err := c.call(ctx, http.MethodPatch, fmt.Sprintf("%s/id:%s", CategoryURL, id), spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return fmt.Errorf("update category failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) DeleteCategory(ctx context.Context, id string) error {
|
||||
|
||||
_, _, status, err := c.call(ctx, http.MethodDelete, fmt.Sprintf("%s/id:%s", CategoryURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return fmt.Errorf("delete category failed with status code: %d, error message: %s", status, err)
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListCategories(ctx context.Context) ([]string, error) {
|
||||
|
||||
stream, _, status, err := c.call(ctx, http.MethodGet, CategoryURL, nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("get categories failed with status code: %d, error message: %s", status, err)
|
||||
|
||||
}
|
||||
|
||||
type Categories struct {
|
||||
Value []string
|
||||
}
|
||||
|
||||
var pCategories Categories
|
||||
if err := json.NewDecoder(stream).Decode(&pCategories); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
|
||||
}
|
||||
return pCategories.Value, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListCategoriesByName(ctx context.Context) ([]CategoryInfo, error) {
|
||||
categoryIds, err := c.ListCategories(ctx)
|
||||
// GetCategories fetches an array of category information in the system.
|
||||
func (c *Manager) GetCategories(ctx context.Context) ([]Category, error) {
|
||||
ids, err := c.ListCategories(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get category failed for: %s", err)
|
||||
}
|
||||
|
||||
var categoryInfoSlice []CategoryInfo
|
||||
for _, cID := range categoryIds {
|
||||
category, err := c.GetCategory(ctx, cID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get category %s failed for %s", cID, err)
|
||||
}
|
||||
categoryCreate := &CategoryInfo{Name: category.Name, CategoryID: category.ID}
|
||||
|
||||
categoryInfoSlice = append(categoryInfoSlice, *categoryCreate)
|
||||
|
||||
}
|
||||
return categoryInfoSlice, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) GetCategoriesByName(ctx context.Context, name string) ([]Category, error) {
|
||||
categoryIds, err := c.ListCategories(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get category failed for: %s", err)
|
||||
|
||||
return nil, fmt.Errorf("list categories: %s", err)
|
||||
}
|
||||
|
||||
var categories []Category
|
||||
for _, cID := range categoryIds {
|
||||
category, err := c.GetCategory(ctx, cID)
|
||||
for _, id := range ids {
|
||||
category, err := c.GetCategory(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get category %s failed for %s", cID, err)
|
||||
}
|
||||
if category.Name == name {
|
||||
categories = append(categories, *category)
|
||||
return nil, fmt.Errorf("get category %s: %s", id, err)
|
||||
}
|
||||
|
||||
categories = append(categories, *category)
|
||||
|
||||
}
|
||||
return categories, nil
|
||||
}
|
||||
|
272
vendor/github.com/vmware/govmomi/vapi/tags/rest_client.go
generated
vendored
272
vendor/github.com/vmware/govmomi/vapi/tags/rest_client.go
generated
vendored
@@ -1,272 +0,0 @@
|
||||
// Copyright 2017 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 tags
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/soap"
|
||||
)
|
||||
|
||||
const (
|
||||
RestPrefix = "/rest"
|
||||
loginURL = "/com/vmware/cis/session"
|
||||
sessionIDCookieName = "vmware-api-session-id"
|
||||
)
|
||||
|
||||
type RestClient struct {
|
||||
mu sync.Mutex
|
||||
host string
|
||||
scheme string
|
||||
endpoint *url.URL
|
||||
user *url.Userinfo
|
||||
HTTP *http.Client
|
||||
cookies []*http.Cookie
|
||||
}
|
||||
|
||||
func NewClient(u *url.URL, insecure bool, thumbprint string) *RestClient {
|
||||
endpoint := &url.URL{}
|
||||
*endpoint = *u
|
||||
endpoint.Path = RestPrefix
|
||||
// Ignore "#" anchor
|
||||
endpoint.Fragment = ""
|
||||
|
||||
sc := soap.NewClient(endpoint, insecure)
|
||||
if thumbprint != "" {
|
||||
sc.SetThumbprint(endpoint.Host, thumbprint)
|
||||
}
|
||||
|
||||
user := endpoint.User
|
||||
endpoint.User = nil
|
||||
|
||||
return &RestClient{
|
||||
endpoint: endpoint,
|
||||
user: user,
|
||||
host: endpoint.Host,
|
||||
scheme: endpoint.Scheme,
|
||||
HTTP: &sc.Client,
|
||||
}
|
||||
}
|
||||
|
||||
// NewClientWithSessionID creates a new REST client with a supplied session ID
|
||||
// to re-connect to existing sessions.
|
||||
//
|
||||
// Note that the session is not checked for validity - to check for a valid
|
||||
// session after creating the client, use the Valid method. If the session is
|
||||
// no longer valid and the session needs to be re-saved, Login should be called
|
||||
// again before calling SessionID to extract the new session ID. Clients
|
||||
// created with this function function work in the exact same way as clients
|
||||
// created with NewClient, including supporting re-login on invalid sessions on
|
||||
// all SDK calls.
|
||||
func NewClientWithSessionID(u *url.URL, insecure bool, thumbprint string, sessionID string) *RestClient {
|
||||
c := NewClient(u, insecure, thumbprint)
|
||||
c.SetSessionID(sessionID)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *RestClient) encodeData(data interface{}) (*bytes.Buffer, error) {
|
||||
params := bytes.NewBuffer(nil)
|
||||
if data != nil {
|
||||
if err := json.NewEncoder(params).Encode(data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) call(ctx context.Context, method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
||||
// Logger.Debugf("%s: %s, headers: %+v", method, path, headers)
|
||||
params, err := c.encodeData(data)
|
||||
if err != nil {
|
||||
return nil, nil, -1, err
|
||||
}
|
||||
|
||||
if data != nil {
|
||||
if headers == nil {
|
||||
headers = make(map[string][]string)
|
||||
}
|
||||
headers["Content-Type"] = []string{"application/json"}
|
||||
}
|
||||
|
||||
body, hdr, statusCode, err := c.clientRequest(ctx, method, path, params, headers)
|
||||
if statusCode == http.StatusUnauthorized && strings.Contains(err.Error(), "This method requires authentication") {
|
||||
c.Login(ctx)
|
||||
return c.clientRequest(ctx, method, path, params, headers)
|
||||
}
|
||||
|
||||
return body, hdr, statusCode, err
|
||||
}
|
||||
|
||||
func (c *RestClient) clientRequest(ctx context.Context, method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, http.Header, int, error) {
|
||||
expectedPayload := (method == http.MethodPost || method == http.MethodPut)
|
||||
if expectedPayload && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
|
||||
req, err := c.newRequest(method, path, in)
|
||||
if err != nil {
|
||||
return nil, nil, -1, err
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
c.mu.Lock()
|
||||
if c.cookies != nil {
|
||||
req.AddCookie(c.cookies[0])
|
||||
}
|
||||
c.mu.Unlock()
|
||||
|
||||
if headers != nil {
|
||||
for k, v := range headers {
|
||||
req.Header[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if expectedPayload && req.Header.Get("Content-Type") == "" {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := c.HTTP.Do(req)
|
||||
return c.handleResponse(resp, err)
|
||||
}
|
||||
|
||||
func (c *RestClient) handleResponse(resp *http.Response, err error) (io.ReadCloser, http.Header, int, error) {
|
||||
statusCode := -1
|
||||
if resp != nil {
|
||||
statusCode = resp.StatusCode
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, nil, statusCode, err
|
||||
}
|
||||
return nil, nil, statusCode, err
|
||||
}
|
||||
|
||||
if statusCode < http.StatusOK || statusCode >= http.StatusBadRequest {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, nil, statusCode, err
|
||||
}
|
||||
if len(body) == 0 {
|
||||
return nil, nil, statusCode, err
|
||||
}
|
||||
return nil, nil, statusCode, fmt.Errorf("error response: %s", bytes.TrimSpace(body))
|
||||
|
||||
}
|
||||
|
||||
return resp.Body, resp.Header, statusCode, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) Login(ctx context.Context) error {
|
||||
|
||||
request, err := c.newRequest(http.MethodPost, loginURL, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.user != nil {
|
||||
password, _ := c.user.Password()
|
||||
request.SetBasicAuth(c.user.Username(), password)
|
||||
}
|
||||
resp, err := c.HTTP.Do(request)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp == nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
resp.Body.Close()
|
||||
return err
|
||||
}
|
||||
|
||||
c.cookies = resp.Cookies()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) Logout(ctx context.Context) error {
|
||||
_, _, status, err := c.call(ctx, http.MethodDelete, loginURL, nil, nil)
|
||||
if status != http.StatusOK || err != nil {
|
||||
return err
|
||||
}
|
||||
c.SetSessionID("")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) newRequest(method, urlStr string, body io.Reader) (*http.Request, error) {
|
||||
return http.NewRequest(method, c.endpoint.String()+urlStr, body)
|
||||
}
|
||||
|
||||
// SessionID returns the current session ID of the REST client. An empty string
|
||||
// means there was no session cookie currently loaded.
|
||||
func (c *RestClient) SessionID() string {
|
||||
for _, cookie := range c.cookies {
|
||||
if cookie.Name == sessionIDCookieName {
|
||||
return cookie.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// SetSessionID sets the session cookie with the supplied session ID.
|
||||
//
|
||||
// This does not necessarily mean the session is valid. The session should be
|
||||
// checked with Valid before proceeding, and logged back in if it has expired.
|
||||
//
|
||||
// This function will overwrite any existing session.
|
||||
func (c *RestClient) SetSessionID(sessionID string) {
|
||||
idx := -1
|
||||
for i, cookie := range c.cookies {
|
||||
if cookie.Name == sessionIDCookieName {
|
||||
idx = i
|
||||
}
|
||||
}
|
||||
sessionCookie := &http.Cookie{
|
||||
Name: sessionIDCookieName,
|
||||
Value: sessionID,
|
||||
Path: RestPrefix,
|
||||
}
|
||||
if idx > -1 {
|
||||
c.cookies[idx] = sessionCookie
|
||||
} else {
|
||||
c.cookies = append(c.cookies, sessionCookie)
|
||||
}
|
||||
}
|
||||
|
||||
// Valid checks to see if the session cookies in a REST client are still valid.
|
||||
// This should be used when restoring a session to determine if a new login is
|
||||
// necessary.
|
||||
func (c *RestClient) Valid(ctx context.Context) bool {
|
||||
_, _, statusCode, err := c.clientRequest(ctx, http.MethodPost, loginURL+"?~action=get", nil, nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if statusCode == http.StatusOK {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
213
vendor/github.com/vmware/govmomi/vapi/tags/tag_association.go
generated
vendored
213
vendor/github.com/vmware/govmomi/vapi/tags/tag_association.go
generated
vendored
@@ -1,139 +1,108 @@
|
||||
// Copyright 2017 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.
|
||||
/*
|
||||
Copyright (c) 2018 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
|
||||
|
||||
vUnless 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 tags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/vmware/govmomi/vim25/types"
|
||||
"github.com/vmware/govmomi/vapi/internal"
|
||||
"github.com/vmware/govmomi/vim25/mo"
|
||||
)
|
||||
|
||||
const (
|
||||
TagAssociationURL = "/com/vmware/cis/tagging/tag-association"
|
||||
)
|
||||
|
||||
type AssociatedObject struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
type TagAssociationSpec struct {
|
||||
ObjectID *AssociatedObject `json:"object_id,omitempty"`
|
||||
TagID *string `json:"tag_id,omitempty"`
|
||||
}
|
||||
|
||||
type AttachedTagsInfo struct {
|
||||
Name string
|
||||
TagID string
|
||||
}
|
||||
|
||||
func (c *RestClient) getAssociatedObject(ref *types.ManagedObjectReference) *AssociatedObject {
|
||||
if ref == nil {
|
||||
return nil
|
||||
}
|
||||
object := AssociatedObject{
|
||||
ID: ref.Value,
|
||||
Type: ref.Type,
|
||||
}
|
||||
return &object
|
||||
}
|
||||
|
||||
func (c *RestClient) getAssociationSpec(tagID *string, ref *types.ManagedObjectReference) *TagAssociationSpec {
|
||||
object := c.getAssociatedObject(ref)
|
||||
spec := TagAssociationSpec{
|
||||
TagID: tagID,
|
||||
ObjectID: object,
|
||||
}
|
||||
return &spec
|
||||
}
|
||||
|
||||
func (c *RestClient) AttachTagToObject(ctx context.Context, tagID string, ref *types.ManagedObjectReference) error {
|
||||
spec := c.getAssociationSpec(&tagID, ref)
|
||||
_, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=attach", TagAssociationURL), *spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return fmt.Errorf("attach tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) DetachTagFromObject(ctx context.Context, tagID string, ref *types.ManagedObjectReference) error {
|
||||
spec := c.getAssociationSpec(&tagID, ref)
|
||||
_, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=detach", TagAssociationURL), *spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return fmt.Errorf("detach tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListAttachedTags(ctx context.Context, ref *types.ManagedObjectReference) ([]string, error) {
|
||||
spec := c.getAssociationSpec(nil, ref)
|
||||
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=list-attached-tags", TagAssociationURL), *spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("detach tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value []string
|
||||
}
|
||||
|
||||
var pTag RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
}
|
||||
return pTag.Value, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListAttachedTagsByName(ctx context.Context, ref *types.ManagedObjectReference) ([]AttachedTagsInfo, error) {
|
||||
tagIds, err := c.ListAttachedTags(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get attached tag failed for: %s", err)
|
||||
}
|
||||
|
||||
var attachedTagsInfoSlice []AttachedTagsInfo
|
||||
for _, cID := range tagIds {
|
||||
tag, err := c.GetTag(ctx, cID)
|
||||
func (c *Manager) tagID(ctx context.Context, id string) (string, error) {
|
||||
if isName(id) {
|
||||
tag, err := c.GetTag(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tag %s failed for %s", cID, err)
|
||||
return "", err
|
||||
}
|
||||
attachedTagsCreate := &AttachedTagsInfo{Name: tag.Name, TagID: tag.ID}
|
||||
attachedTagsInfoSlice = append(attachedTagsInfoSlice, *attachedTagsCreate)
|
||||
return tag.ID, nil
|
||||
}
|
||||
return attachedTagsInfoSlice, nil
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListAttachedObjects(ctx context.Context, tagID string) ([]AssociatedObject, error) {
|
||||
spec := c.getAssociationSpec(&tagID, nil)
|
||||
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s?~action=list-attached-objects", TagAssociationURL), *spec, nil)
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("list object failed with status code: %d, error message: %s", status, err)
|
||||
// AttachTag attaches a tag ID to a managed object.
|
||||
func (c *Manager) AttachTag(ctx context.Context, tagID string, ref mo.Reference) error {
|
||||
id, err := c.tagID(ctx, tagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value []AssociatedObject
|
||||
}
|
||||
|
||||
var pTag RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
}
|
||||
|
||||
return pTag.Value, nil
|
||||
spec := internal.NewAssociation(id, ref)
|
||||
url := internal.URL(c, internal.AssociationPath).WithAction("attach")
|
||||
return c.Do(ctx, url.Request(http.MethodPost, spec), nil)
|
||||
}
|
||||
|
||||
// DetachTag detaches a tag ID from a managed object.
|
||||
// If the tag is already removed from the object, then this operation is a no-op and an error will not be thrown.
|
||||
func (c *Manager) DetachTag(ctx context.Context, tagID string, ref mo.Reference) error {
|
||||
id, err := c.tagID(ctx, tagID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
spec := internal.NewAssociation(id, ref)
|
||||
url := internal.URL(c, internal.AssociationPath).WithAction("detach")
|
||||
return c.Do(ctx, url.Request(http.MethodPost, spec), nil)
|
||||
}
|
||||
|
||||
// ListAttachedTags fetches the array of tag IDs attached to the given object.
|
||||
func (c *Manager) ListAttachedTags(ctx context.Context, ref mo.Reference) ([]string, error) {
|
||||
spec := internal.NewAssociation("", ref)
|
||||
url := internal.URL(c, internal.AssociationPath).WithAction("list-attached-tags")
|
||||
var res []string
|
||||
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
|
||||
}
|
||||
|
||||
// GetAttachedTags fetches the array of tags attached to the given object.
|
||||
func (c *Manager) GetAttachedTags(ctx context.Context, ref mo.Reference) ([]Tag, error) {
|
||||
ids, err := c.ListAttachedTags(ctx, ref)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get attached tags %s: %s", ref, err)
|
||||
}
|
||||
|
||||
var info []Tag
|
||||
for _, id := range ids {
|
||||
tag, err := c.GetTag(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tag %s: %s", id, err)
|
||||
}
|
||||
info = append(info, *tag)
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// ListAttachedObjects fetches the array of attached objects for the given tag ID.
|
||||
func (c *Manager) ListAttachedObjects(ctx context.Context, tagID string) ([]mo.Reference, error) {
|
||||
id, err := c.tagID(ctx, tagID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec := internal.Association{
|
||||
TagID: id,
|
||||
}
|
||||
url := internal.URL(c, internal.AssociationPath).WithAction("list-attached-objects")
|
||||
var res []internal.AssociatedObject
|
||||
if err := c.Do(ctx, url.Request(http.MethodPost, spec), &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
refs := make([]mo.Reference, len(res))
|
||||
for i := range res {
|
||||
refs[i] = res[i]
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
388
vendor/github.com/vmware/govmomi/vapi/tags/tags.go
generated
vendored
388
vendor/github.com/vmware/govmomi/vapi/tags/tags.go
generated
vendored
@@ -1,252 +1,202 @@
|
||||
// Copyright 2017 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.
|
||||
/*
|
||||
Copyright (c) 2018 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 tags
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/vmware/govmomi/vapi/internal"
|
||||
"github.com/vmware/govmomi/vapi/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
TagURL = "/com/vmware/cis/tagging/tag"
|
||||
)
|
||||
|
||||
type TagCreateSpec struct {
|
||||
CreateSpec TagCreate `json:"create_spec"`
|
||||
// Manager extends rest.Client, adding tag related methods.
|
||||
type Manager struct {
|
||||
*rest.Client
|
||||
}
|
||||
|
||||
type TagCreate struct {
|
||||
CategoryID string `json:"category_id"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type TagUpdateSpec struct {
|
||||
UpdateSpec TagUpdate `json:"update_spec,omitempty"`
|
||||
}
|
||||
|
||||
type TagUpdate struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
ID string `json:"id"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
CategoryID string `json:"category_id"`
|
||||
UsedBy []string `json:"used_by"`
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateTagIfNotExist(ctx context.Context, name string, description string, categoryID string) (*string, error) {
|
||||
tagCreate := TagCreate{categoryID, description, name}
|
||||
spec := TagCreateSpec{tagCreate}
|
||||
id, err := c.CreateTag(ctx, &spec)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
// NewManager creates a new Manager instance with the given client.
|
||||
func NewManager(client *rest.Client) *Manager {
|
||||
return &Manager{
|
||||
Client: client,
|
||||
}
|
||||
// if already exists, query back
|
||||
if strings.Contains(err.Error(), ErrAlreadyExists) {
|
||||
tagObjs, err := c.GetTagByNameForCategory(ctx, name, categoryID)
|
||||
}
|
||||
|
||||
// isName returns true if the id is not a urn.
|
||||
func isName(id string) bool {
|
||||
return !strings.HasPrefix(id, "urn:")
|
||||
}
|
||||
|
||||
// Tag provides methods to create, read, update, delete, and enumerate tags.
|
||||
type Tag struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
CategoryID string `json:"category_id,omitempty"`
|
||||
UsedBy []string `json:"used_by,omitempty"`
|
||||
}
|
||||
|
||||
// Patch merges updates from the given src.
|
||||
func (t *Tag) Patch(src *Tag) {
|
||||
if src.Name != "" {
|
||||
t.Name = src.Name
|
||||
}
|
||||
if src.Description != "" {
|
||||
t.Description = src.Description
|
||||
}
|
||||
if src.CategoryID != "" {
|
||||
t.CategoryID = src.CategoryID
|
||||
}
|
||||
}
|
||||
|
||||
// CreateTag creates a new tag with the given Name, Description and CategoryID.
|
||||
func (c *Manager) CreateTag(ctx context.Context, tag *Tag) (string, error) {
|
||||
// create avoids the annoyance of CreateTag requiring a "description" key to be included in the request,
|
||||
// even though the field value can be empty.
|
||||
type create struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
CategoryID string `json:"category_id"`
|
||||
}
|
||||
spec := struct {
|
||||
Tag create `json:"create_spec"`
|
||||
}{
|
||||
Tag: create{
|
||||
Name: tag.Name,
|
||||
Description: tag.Description,
|
||||
CategoryID: tag.CategoryID,
|
||||
},
|
||||
}
|
||||
if isName(tag.CategoryID) {
|
||||
cat, err := c.GetCategory(ctx, tag.CategoryID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
spec.Tag.CategoryID = cat.ID
|
||||
}
|
||||
url := internal.URL(c, internal.TagPath)
|
||||
var res string
|
||||
return res, c.Do(ctx, url.Request(http.MethodPost, spec), &res)
|
||||
}
|
||||
|
||||
// UpdateTag can update one or both of the tag Description and Name fields.
|
||||
func (c *Manager) UpdateTag(ctx context.Context, tag *Tag) error {
|
||||
spec := struct {
|
||||
Tag Tag `json:"update_spec"`
|
||||
}{
|
||||
Tag: Tag{
|
||||
Name: tag.Name,
|
||||
Description: tag.Description,
|
||||
},
|
||||
}
|
||||
url := internal.URL(c, internal.TagPath).WithID(tag.ID)
|
||||
return c.Do(ctx, url.Request(http.MethodPatch, spec), nil)
|
||||
}
|
||||
|
||||
// DeleteTag deletes an existing tag.
|
||||
func (c *Manager) DeleteTag(ctx context.Context, tag *Tag) error {
|
||||
url := internal.URL(c, internal.TagPath).WithID(tag.ID)
|
||||
return c.Do(ctx, url.Request(http.MethodDelete), nil)
|
||||
}
|
||||
|
||||
// GetTag fetches the tag information for the given identifier.
|
||||
// The id parameter can be a Tag ID or Tag Name.
|
||||
func (c *Manager) GetTag(ctx context.Context, id string) (*Tag, error) {
|
||||
if isName(id) {
|
||||
tags, err := c.GetTags(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if tagObjs != nil {
|
||||
return &tagObjs[0].ID, nil
|
||||
|
||||
for i := range tags {
|
||||
if tags[i].Name == id {
|
||||
return &tags[i], nil
|
||||
}
|
||||
}
|
||||
|
||||
// should not happen
|
||||
return nil, fmt.Errorf("failed to create tag for it's existed, but could not query back. Please check system")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("created tag failed for %s", err)
|
||||
url := internal.URL(c, internal.TagPath).WithID(id)
|
||||
var res Tag
|
||||
return &res, c.Do(ctx, url.Request(http.MethodGet), &res)
|
||||
|
||||
}
|
||||
|
||||
func (c *RestClient) DeleteTagIfNoObjectAttached(ctx context.Context, id string) error {
|
||||
objs, err := c.ListAttachedObjects(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(objs) > 0 {
|
||||
return fmt.Errorf("tag %s related objects is not empty, do not delete it", id)
|
||||
}
|
||||
return c.DeleteTag(ctx, id)
|
||||
// ListTags returns all tag IDs in the system.
|
||||
func (c *Manager) ListTags(ctx context.Context) ([]string, error) {
|
||||
url := internal.URL(c, internal.TagPath)
|
||||
var res []string
|
||||
return res, c.Do(ctx, url.Request(http.MethodGet), &res)
|
||||
}
|
||||
|
||||
func (c *RestClient) CreateTag(ctx context.Context, spec *TagCreateSpec) (*string, error) {
|
||||
stream, _, status, err := c.call(ctx, http.MethodPost, TagURL, spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("create tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value string
|
||||
}
|
||||
|
||||
var pID RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pID); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
}
|
||||
return &pID.Value, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) GetTag(ctx context.Context, id string) (*Tag, error) {
|
||||
stream, _, status, err := c.call(ctx, http.MethodGet, fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("get tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
type RespValue struct {
|
||||
Value Tag
|
||||
}
|
||||
|
||||
var pTag RespValue
|
||||
if err := json.NewDecoder(stream).Decode(&pTag); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
}
|
||||
return &(pTag.Value), nil
|
||||
}
|
||||
|
||||
func (c *RestClient) UpdateTag(ctx context.Context, id string, spec *TagUpdateSpec) error {
|
||||
_, _, status, err := c.call(ctx, http.MethodPatch, fmt.Sprintf("%s/id:%s", TagURL, id), spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return fmt.Errorf("update tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) DeleteTag(ctx context.Context, id string) error {
|
||||
_, _, status, err := c.call(ctx, http.MethodDelete, fmt.Sprintf("%s/id:%s", TagURL, id), nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return fmt.Errorf("delete tag failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListTags(ctx context.Context) ([]string, error) {
|
||||
stream, _, status, err := c.call(ctx, http.MethodGet, TagURL, nil, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("get tags failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
return c.handleTagIDList(stream)
|
||||
}
|
||||
|
||||
type TagsInfo struct {
|
||||
Name string
|
||||
TagID string
|
||||
}
|
||||
|
||||
func (c *RestClient) ListTagsByName(ctx context.Context) ([]TagsInfo, error) {
|
||||
tagIds, err := c.ListTags(ctx)
|
||||
// GetTags fetches an array of tag information in the system.
|
||||
func (c *Manager) GetTags(ctx context.Context) ([]Tag, error) {
|
||||
ids, err := c.ListTags(ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tags failed for: %s", err)
|
||||
}
|
||||
|
||||
var tagsInfoSlice []TagsInfo
|
||||
for _, cID := range tagIds {
|
||||
tag, err := c.GetTag(ctx, cID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get category %s failed for %s", cID, err)
|
||||
}
|
||||
tagsCreate := &TagsInfo{Name: tag.Name, TagID: tag.ID}
|
||||
|
||||
tagsInfoSlice = append(tagsInfoSlice, *tagsCreate)
|
||||
|
||||
}
|
||||
return tagsInfoSlice, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) ListTagsForCategory(ctx context.Context, id string) ([]string, error) {
|
||||
|
||||
type PostCategory struct {
|
||||
ID string `json:"category_id"`
|
||||
}
|
||||
spec := PostCategory{id}
|
||||
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s/id:%s?~action=list-tags-for-category", TagURL, id), spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("list tags for category failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
|
||||
return c.handleTagIDList(stream)
|
||||
}
|
||||
|
||||
func (c *RestClient) ListTagsInfoForCategory(ctx context.Context, id string) ([]TagsInfo, error) {
|
||||
|
||||
type PostCategory struct {
|
||||
ID string `json:"category_id"`
|
||||
}
|
||||
spec := PostCategory{id}
|
||||
stream, _, status, err := c.call(ctx, http.MethodPost, fmt.Sprintf("%s/id:%s?~action=list-tags-for-category", TagURL, id), spec, nil)
|
||||
|
||||
if status != http.StatusOK || err != nil {
|
||||
return nil, fmt.Errorf("list tags for category failed with status code: %d, error message: %s", status, err)
|
||||
}
|
||||
var tagsInfoSlice []TagsInfo
|
||||
tmp, err := c.handleTagIDList(stream)
|
||||
for _, item := range tmp {
|
||||
tag, err := c.GetTag(ctx, item)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get category %s failed for %s", item, err)
|
||||
}
|
||||
tagsCreate := &TagsInfo{Name: tag.Name, TagID: tag.ID}
|
||||
|
||||
tagsInfoSlice = append(tagsInfoSlice, *tagsCreate)
|
||||
}
|
||||
return tagsInfoSlice, nil
|
||||
}
|
||||
|
||||
func (c *RestClient) handleTagIDList(stream io.ReadCloser) ([]string, error) {
|
||||
type Tags struct {
|
||||
Value []string
|
||||
}
|
||||
|
||||
var pTags Tags
|
||||
if err := json.NewDecoder(stream).Decode(&pTags); err != nil {
|
||||
return nil, fmt.Errorf("decode response body failed for: %s", err)
|
||||
}
|
||||
return pTags.Value, nil
|
||||
}
|
||||
|
||||
// Get tag through tag name and category id
|
||||
func (c *RestClient) GetTagByNameForCategory(ctx context.Context, name string, id string) ([]Tag, error) {
|
||||
tagIds, err := c.ListTagsForCategory(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tag failed for %s", err)
|
||||
}
|
||||
|
||||
var tags []Tag
|
||||
for _, tID := range tagIds {
|
||||
tag, err := c.GetTag(ctx, tID)
|
||||
for _, id := range ids {
|
||||
tag, err := c.GetTag(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tag %s failed for %s", tID, err)
|
||||
}
|
||||
if tag.Name == name {
|
||||
tags = append(tags, *tag)
|
||||
return nil, fmt.Errorf("get category %s failed for %s", id, err)
|
||||
}
|
||||
|
||||
tags = append(tags, *tag)
|
||||
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// The id parameter can be a Category ID or Category Name.
|
||||
func (c *Manager) ListTagsForCategory(ctx context.Context, id string) ([]string, error) {
|
||||
if isName(id) {
|
||||
cat, err := c.GetCategory(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id = cat.ID
|
||||
}
|
||||
|
||||
body := struct {
|
||||
ID string `json:"category_id"`
|
||||
}{id}
|
||||
url := internal.URL(c, internal.TagPath).WithID(id).WithAction("list-tags-for-category")
|
||||
var res []string
|
||||
return res, c.Do(ctx, url.Request(http.MethodPost, body), &res)
|
||||
}
|
||||
|
||||
// The id parameter can be a Category ID or Category Name.
|
||||
func (c *Manager) GetTagsForCategory(ctx context.Context, id string) ([]Tag, error) {
|
||||
ids, err := c.ListTagsForCategory(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tags []Tag
|
||||
for _, id := range ids {
|
||||
tag, err := c.GetTag(ctx, id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get tag %s: %s", id, err)
|
||||
}
|
||||
|
||||
tags = append(tags, *tag)
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
118
vendor/github.com/vmware/govmomi/vim25/soap/client.go
generated
vendored
118
vendor/github.com/vmware/govmomi/vim25/soap/client.go
generated
vendored
@@ -449,12 +449,51 @@ func (c *Client) UnmarshalJSON(b []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) do(ctx context.Context, req *http.Request) (*http.Response, error) {
|
||||
if nil == ctx || nil == ctx.Done() { // ctx.Done() is for ctx
|
||||
return c.Client.Do(req)
|
||||
type kindContext struct{}
|
||||
|
||||
func (c *Client) Do(ctx context.Context, req *http.Request, f func(*http.Response) error) error {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
// Create debugging context for this round trip
|
||||
d := c.d.newRoundTrip()
|
||||
if d.enabled() {
|
||||
defer d.done()
|
||||
}
|
||||
|
||||
return c.Client.Do(req.WithContext(ctx))
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set(`User-Agent`, c.UserAgent)
|
||||
}
|
||||
|
||||
if d.enabled() {
|
||||
d.debugRequest(req)
|
||||
}
|
||||
|
||||
tstart := time.Now()
|
||||
res, err := c.Client.Do(req.WithContext(ctx))
|
||||
tstop := time.Now()
|
||||
|
||||
if d.enabled() {
|
||||
var name string
|
||||
if kind, ok := ctx.Value(kindContext{}).(HasFault); ok {
|
||||
name = fmt.Sprintf("%T", kind)
|
||||
} else {
|
||||
name = fmt.Sprintf("%s %s", req.Method, req.URL)
|
||||
}
|
||||
d.logf("%6dms (%s)", tstop.Sub(tstart)/time.Millisecond, name)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer res.Body.Close()
|
||||
|
||||
if d.enabled() {
|
||||
d.debugResponse(res)
|
||||
}
|
||||
|
||||
return f(res)
|
||||
}
|
||||
|
||||
// Signer can be implemented by soap.Header.Security to sign requests.
|
||||
@@ -493,12 +532,6 @@ func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error
|
||||
reqEnv.Header = &h // XML marshal header only if a field is set
|
||||
}
|
||||
|
||||
// Create debugging context for this round trip
|
||||
d := c.d.newRoundTrip()
|
||||
if d.enabled() {
|
||||
defer d.done()
|
||||
}
|
||||
|
||||
if signer, ok := h.Security.(Signer); ok {
|
||||
b, err = signer.Sign(reqEnv)
|
||||
if err != nil {
|
||||
@@ -517,8 +550,6 @@ func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error
|
||||
panic(err)
|
||||
}
|
||||
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
req.Header.Set(`Content-Type`, `text/xml; charset="utf-8"`)
|
||||
|
||||
action := h.Action
|
||||
@@ -527,54 +558,29 @@ func (c *Client) RoundTrip(ctx context.Context, reqBody, resBody HasFault) error
|
||||
}
|
||||
req.Header.Set(`SOAPAction`, action)
|
||||
|
||||
if c.UserAgent != "" {
|
||||
req.Header.Set(`User-Agent`, c.UserAgent)
|
||||
}
|
||||
return c.Do(context.WithValue(ctx, kindContext{}, resBody), req, func(res *http.Response) error {
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
// OK
|
||||
case http.StatusInternalServerError:
|
||||
// Error, but typically includes a body explaining the error
|
||||
default:
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
|
||||
if d.enabled() {
|
||||
d.debugRequest(req)
|
||||
}
|
||||
dec := xml.NewDecoder(res.Body)
|
||||
dec.TypeFunc = types.TypeFunc()
|
||||
err = dec.Decode(&resEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tstart := time.Now()
|
||||
res, err := c.do(ctx, req)
|
||||
tstop := time.Now()
|
||||
if f := resBody.Fault(); f != nil {
|
||||
return WrapSoapFault(f)
|
||||
}
|
||||
|
||||
if d.enabled() {
|
||||
d.logf("%6dms (%T)", tstop.Sub(tstart)/time.Millisecond, resBody)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.enabled() {
|
||||
d.debugResponse(res)
|
||||
}
|
||||
|
||||
// Close response regardless of what happens next
|
||||
defer res.Body.Close()
|
||||
|
||||
switch res.StatusCode {
|
||||
case http.StatusOK:
|
||||
// OK
|
||||
case http.StatusInternalServerError:
|
||||
// Error, but typically includes a body explaining the error
|
||||
default:
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
|
||||
dec := xml.NewDecoder(res.Body)
|
||||
dec.TypeFunc = types.TypeFunc()
|
||||
err = dec.Decode(&resEnv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f := resBody.Fault(); f != nil {
|
||||
return WrapSoapFault(f)
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Client) CloseIdleConnections() {
|
||||
|
13
vendor/github.com/vmware/govmomi/vim25/soap/debug.go
generated
vendored
13
vendor/github.com/vmware/govmomi/vim25/soap/debug.go
generated
vendored
@@ -21,6 +21,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
@@ -69,6 +70,14 @@ func (d *debugRoundTrip) newFile(suffix string) io.WriteCloser {
|
||||
return debug.NewFile(fmt.Sprintf("%d-%04d.%s", d.cn, d.rn, suffix))
|
||||
}
|
||||
|
||||
func (d *debugRoundTrip) ext(h http.Header) string {
|
||||
ext := "xml"
|
||||
if strings.Contains(h.Get("Content-Type"), "/json") {
|
||||
ext = "json"
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
func (d *debugRoundTrip) debugRequest(req *http.Request) {
|
||||
if d == nil {
|
||||
return
|
||||
@@ -83,7 +92,7 @@ func (d *debugRoundTrip) debugRequest(req *http.Request) {
|
||||
wc.Close()
|
||||
|
||||
// Capture body
|
||||
wc = d.newFile("req.xml")
|
||||
wc = d.newFile("req." + d.ext(req.Header))
|
||||
req.Body = newTeeReader(req.Body, wc)
|
||||
|
||||
// Delay closing until marked done
|
||||
@@ -104,7 +113,7 @@ func (d *debugRoundTrip) debugResponse(res *http.Response) {
|
||||
wc.Close()
|
||||
|
||||
// Capture body
|
||||
wc = d.newFile("res.xml")
|
||||
wc = d.newFile("res." + d.ext(res.Header))
|
||||
res.Body = newTeeReader(res.Body, wc)
|
||||
|
||||
// Delay closing until marked done
|
||||
|
Reference in New Issue
Block a user