mirror of
https://github.com/project-zot/zot.git
synced 2024-12-16 21:56:37 -05:00
feat(cli): add command to interogate the server version and other details (#1709)
Signed-off-by: Laurentiu Niculae <niculae.laurentiu1@gmail.com>
This commit is contained in:
parent
0dfff561f9
commit
83f287d1f6
10 changed files with 475 additions and 9 deletions
|
@ -78,9 +78,10 @@ var (
|
|||
ErrInvalidURL = errors.New("cli: invalid URL format")
|
||||
ErrExtensionNotEnabled = errors.New("cli: functionality is not built/configured in the current server")
|
||||
ErrUnauthorizedAccess = errors.New("auth: unauthorized access. check credentials")
|
||||
ErrURLNotFound = errors.New("url not found")
|
||||
ErrCannotResetConfigKey = errors.New("cli: cannot reset given config key")
|
||||
ErrConfigNotFound = errors.New("cli: config with the given name does not exist")
|
||||
ErrNoURLProvided = errors.New("cli: no URL provided in argument or via config")
|
||||
ErrNoURLProvided = errors.New("cli: no URL provided by flag or via config")
|
||||
ErrIllegalConfigKey = errors.New("cli: given config key is not allowed")
|
||||
ErrScanNotSupported = errors.New("search: scanning of image media type not supported")
|
||||
ErrCLITimeout = errors.New("cli: Query timed out while waiting for results")
|
||||
|
@ -157,6 +158,8 @@ var (
|
|||
ErrGQLEndpointNotFound = errors.New("cli: the server doesn't have a gql endpoint")
|
||||
ErrGQLQueryNotSupported = errors.New("cli: query is not supported or has different arguments")
|
||||
ErrBadHTTPStatusCode = errors.New("cli: the response doesn't contain the expected status code")
|
||||
ErrFormatNotSupported = errors.New("cli: the given output format is not supported")
|
||||
ErrAPINotSupported = errors.New("registry at the given address doesn't implement the correct API")
|
||||
ErrFileAlreadyCancelled = errors.New("storageDriver: file already cancelled")
|
||||
ErrFileAlreadyClosed = errors.New("storageDriver: file already closed")
|
||||
ErrFileAlreadyCommitted = errors.New("storageDriver: file already committed")
|
||||
|
|
|
@ -11,4 +11,5 @@ func enableCli(rootCmd *cobra.Command) {
|
|||
rootCmd.AddCommand(NewCVECommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewRepoCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewSearchCommand(NewSearchService()))
|
||||
rootCmd.AddCommand(NewServerStatusCommand())
|
||||
}
|
||||
|
|
|
@ -119,13 +119,20 @@ func doHTTPRequest(req *http.Request, verifyTLS bool, debug bool,
|
|||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
if resp.StatusCode == http.StatusUnauthorized {
|
||||
return nil, zerr.ErrUnauthorizedAccess
|
||||
var err error
|
||||
|
||||
switch resp.StatusCode {
|
||||
case http.StatusNotFound:
|
||||
err = zerr.ErrURLNotFound
|
||||
case http.StatusUnauthorized:
|
||||
err = zerr.ErrUnauthorizedAccess
|
||||
default:
|
||||
err = zerr.ErrBadHTTPStatusCode
|
||||
}
|
||||
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
|
||||
return nil, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", zerr.ErrBadHTTPStatusCode, http.StatusOK,
|
||||
return nil, fmt.Errorf("%w: Expected: %d, Got: %d, Body: '%s'", err, http.StatusOK,
|
||||
resp.StatusCode, string(bodyBytes))
|
||||
}
|
||||
|
||||
|
|
191
pkg/cli/client/server_info_cmd.go
Normal file
191
pkg/cli/client/server_info_cmd.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
StatusOnline = "online"
|
||||
StatusOffline = "offline"
|
||||
StatusUnknown = "unknown"
|
||||
)
|
||||
|
||||
func NewServerStatusCommand() *cobra.Command {
|
||||
serverInfoCmd := &cobra.Command{
|
||||
Use: "status",
|
||||
Short: "Information about the server configuration and build information",
|
||||
Long: `Information about the server configuration and build information`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
searchConfig, err := GetSearchConfigFromFlags(cmd, NewSearchService())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return GetServerStatus(searchConfig)
|
||||
},
|
||||
}
|
||||
|
||||
serverInfoCmd.PersistentFlags().String(URLFlag, "",
|
||||
"Specify zot server URL if config-name is not mentioned")
|
||||
serverInfoCmd.PersistentFlags().StringP(ConfigFlag, "c", "",
|
||||
"Specify the registry configuration to use for connection")
|
||||
serverInfoCmd.PersistentFlags().StringP(UserFlag, "u", "",
|
||||
`User Credentials of zot server in "username:password" format`)
|
||||
serverInfoCmd.Flags().StringP(OutputFormatFlag, "f", "text", "Specify the output format [text|json|yaml]")
|
||||
|
||||
return serverInfoCmd
|
||||
}
|
||||
|
||||
func GetServerStatus(config SearchConfig) error {
|
||||
ctx := context.Background()
|
||||
username, password := getUsernameAndPassword(config.User)
|
||||
|
||||
checkAPISupportEndpoint, err := combineServerAndEndpointURL(config.ServURL, constants.RoutePrefix+"/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = makeGETRequest(ctx, checkAPISupportEndpoint, username, password, config.VerifyTLS, config.Debug,
|
||||
nil, config.ResultWriter)
|
||||
if err != nil {
|
||||
serverInfo := ServerInfo{}
|
||||
|
||||
switch {
|
||||
case errors.Is(err, zerr.ErrUnauthorizedAccess):
|
||||
serverInfo.Status = StatusUnknown
|
||||
serverInfo.ErrorMsg = fmt.Sprintf("unauthorised access, %s", getCredentialsSuggestion(username))
|
||||
case errors.Is(err, zerr.ErrBadHTTPStatusCode), errors.Is(err, zerr.ErrURLNotFound):
|
||||
serverInfo.Status = StatusOffline
|
||||
serverInfo.ErrorMsg = fmt.Sprintf("%s: request at %s failed", zerr.ErrAPINotSupported.Error(),
|
||||
checkAPISupportEndpoint)
|
||||
default:
|
||||
serverInfo.Status = StatusOffline
|
||||
serverInfo.ErrorMsg = err.Error()
|
||||
}
|
||||
|
||||
return PrintServerInfo(serverInfo, config)
|
||||
}
|
||||
|
||||
mgmtEndpoint, err := combineServerAndEndpointURL(config.ServURL, fmt.Sprintf("%s%s",
|
||||
constants.RoutePrefix, constants.ExtMgmt))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
serverInfo := ServerInfo{}
|
||||
|
||||
_, err = makeGETRequest(ctx, mgmtEndpoint, username, password, config.VerifyTLS, config.Debug,
|
||||
&serverInfo, config.ResultWriter)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
serverInfo.Status = StatusOnline
|
||||
case errors.Is(err, zerr.ErrURLNotFound):
|
||||
serverInfo.Status = StatusOnline
|
||||
serverInfo.ErrorMsg = fmt.Sprintf("%s%s endpoint is not available", constants.RoutePrefix, constants.ExtMgmt)
|
||||
case errors.Is(err, zerr.ErrUnauthorizedAccess):
|
||||
serverInfo.Status = StatusOnline
|
||||
serverInfo.ErrorMsg = fmt.Sprintf("unauthorised access, %s", getCredentialsSuggestion(username))
|
||||
case errors.Is(err, zerr.ErrBadHTTPStatusCode):
|
||||
serverInfo.Status = StatusOnline
|
||||
serverInfo.ErrorMsg = fmt.Sprintf("%s: request at %s failed", zerr.ErrAPINotSupported.Error(),
|
||||
checkAPISupportEndpoint)
|
||||
default:
|
||||
serverInfo.Status = StatusOffline
|
||||
serverInfo.ErrorMsg = err.Error()
|
||||
}
|
||||
|
||||
return PrintServerInfo(serverInfo, config)
|
||||
}
|
||||
|
||||
func getCredentialsSuggestion(username string) string {
|
||||
if username == "" {
|
||||
return "endpoint requires valid user credentials (add the flag '--user [user]:[password]')"
|
||||
}
|
||||
|
||||
return "given credentials are invalid"
|
||||
}
|
||||
|
||||
func PrintServerInfo(serverInfo ServerInfo, config SearchConfig) error {
|
||||
outputResult, err := serverInfo.ToStringFormat(config.OutputFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintln(config.ResultWriter, outputResult)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ServerInfo struct {
|
||||
Status string `json:"status,omitempty" mapstructure:"status"`
|
||||
ErrorMsg string `json:"error,omitempty" mapstructure:"error"`
|
||||
DistSpecVersion string `json:"distSpecVersion,omitempty" mapstructure:"distSpecVersion"`
|
||||
Commit string `json:"commit,omitempty" mapstructure:"commit"`
|
||||
BinaryType string `json:"binaryType,omitempty" mapstructure:"binaryType"`
|
||||
ReleaseTag string `json:"releaseTag,omitempty" mapstructure:"releaseTag"`
|
||||
}
|
||||
|
||||
func (si *ServerInfo) ToStringFormat(format string) (string, error) {
|
||||
switch format {
|
||||
case "text", "":
|
||||
return si.ToText()
|
||||
case "json":
|
||||
return si.ToJSON()
|
||||
case "yaml", "yml":
|
||||
return si.ToYAML()
|
||||
default:
|
||||
return "", zerr.ErrFormatNotSupported
|
||||
}
|
||||
}
|
||||
|
||||
func (si *ServerInfo) ToText() (string, error) {
|
||||
flagsList := strings.Split(strings.Trim(si.BinaryType, "-"), "-")
|
||||
flags := strings.Join(flagsList, ", ")
|
||||
|
||||
var output string
|
||||
|
||||
if si.ErrorMsg != "" {
|
||||
serverStatus := fmt.Sprintf("Server Status: %s\n"+
|
||||
"Error: %s", si.Status, si.ErrorMsg)
|
||||
|
||||
output = serverStatus
|
||||
} else {
|
||||
serverStatus := fmt.Sprintf("Server Status: %s", si.Status)
|
||||
serverInfo := fmt.Sprintf("Server Version: %s\n"+
|
||||
"Dist Spec Version: %s\n"+
|
||||
"Built with: %s",
|
||||
si.ReleaseTag, si.DistSpecVersion, flags,
|
||||
)
|
||||
|
||||
output = serverStatus + "\n" + serverInfo
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (si *ServerInfo) ToJSON() (string, error) {
|
||||
blob, err := json.MarshalIndent(*si, "", " ")
|
||||
|
||||
return string(blob), err
|
||||
}
|
||||
|
||||
func (si *ServerInfo) ToYAML() (string, error) {
|
||||
body, err := yaml.Marshal(*si)
|
||||
|
||||
return string(body), err
|
||||
}
|
248
pkg/cli/client/server_info_cmd_test.go
Normal file
248
pkg/cli/client/server_info_cmd_test.go
Normal file
|
@ -0,0 +1,248 @@
|
|||
//go:build search
|
||||
// +build search
|
||||
|
||||
package client //nolint:testpackage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
|
||||
zerr "zotregistry.io/zot/errors"
|
||||
"zotregistry.io/zot/pkg/api"
|
||||
"zotregistry.io/zot/pkg/api/config"
|
||||
"zotregistry.io/zot/pkg/api/constants"
|
||||
extconf "zotregistry.io/zot/pkg/extensions/config"
|
||||
test "zotregistry.io/zot/pkg/test/common"
|
||||
)
|
||||
|
||||
func TestServerStatusCommand(t *testing.T) {
|
||||
Convey("ServerStatusCommand", t, func() {
|
||||
port := test.GetFreePort()
|
||||
baseURL := test.GetBaseURL(port)
|
||||
conf := config.New()
|
||||
conf.HTTP.Port = port
|
||||
conf.Storage.GC = false
|
||||
defaultVal := true
|
||||
conf.Extensions = &extconf.ExtensionConfig{
|
||||
Search: &extconf.SearchConfig{BaseConfig: extconf.BaseConfig{Enable: &defaultVal}},
|
||||
}
|
||||
|
||||
ctlr := api.NewController(conf)
|
||||
ctlr.Config.Storage.RootDirectory = t.TempDir()
|
||||
cm := test.NewControllerManager(ctlr)
|
||||
|
||||
cm.StartAndWait(conf.HTTP.Port)
|
||||
defer cm.StopServer()
|
||||
|
||||
configPath := makeConfigFile(fmt.Sprintf(`{"configs":[{"_name":"status-test","url":"%s","showspinner":false}]}`,
|
||||
baseURL))
|
||||
defer os.Remove(configPath)
|
||||
|
||||
args := []string{"status", "--config", "status-test"}
|
||||
cmd := NewCliRootCmd()
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space := regexp.MustCompile(`\s+`)
|
||||
str := space.ReplaceAllString(buff.String(), " ")
|
||||
actual := strings.TrimSpace(str)
|
||||
So(actual, ShouldContainSubstring, config.ReleaseTag)
|
||||
So(actual, ShouldContainSubstring, config.BinaryType)
|
||||
|
||||
// JSON
|
||||
args = []string{"status", "--config", "status-test", "--format", "json"}
|
||||
cmd = NewCliRootCmd()
|
||||
buff = bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space = regexp.MustCompile(`\s+`)
|
||||
str = space.ReplaceAllString(buff.String(), " ")
|
||||
actual = strings.TrimSpace(str)
|
||||
So(actual, ShouldContainSubstring, config.ReleaseTag)
|
||||
So(actual, ShouldContainSubstring, config.BinaryType)
|
||||
|
||||
// YAML
|
||||
args = []string{"status", "--config", "status-test", "--format", "yaml"}
|
||||
cmd = NewCliRootCmd()
|
||||
buff = bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldBeNil)
|
||||
space = regexp.MustCompile(`\s+`)
|
||||
str = space.ReplaceAllString(buff.String(), " ")
|
||||
actual = strings.TrimSpace(str)
|
||||
So(actual, ShouldContainSubstring, config.ReleaseTag)
|
||||
So(actual, ShouldContainSubstring, config.BinaryType)
|
||||
|
||||
// bad type
|
||||
args = []string{"status", "--config", "status-test", "--format", "badType"}
|
||||
cmd = NewCliRootCmd()
|
||||
buff = bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err = cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServerStatusCommandErrors(t *testing.T) {
|
||||
Convey("ServerStatusCommand", t, func() {
|
||||
args := []string{"status"}
|
||||
cmd := NewCliRootCmd()
|
||||
buff := bytes.NewBufferString("")
|
||||
cmd.SetOut(buff)
|
||||
cmd.SetErr(buff)
|
||||
cmd.SetArgs(args)
|
||||
err := cmd.Execute()
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// invalid URL
|
||||
err = GetServerStatus(SearchConfig{
|
||||
ServURL: "a: ds",
|
||||
ResultWriter: os.Stdout,
|
||||
})
|
||||
So(err, ShouldNotBeNil)
|
||||
|
||||
// fail Get request
|
||||
err = GetServerStatus(SearchConfig{
|
||||
ServURL: "http://127.0.0.1:8000",
|
||||
ResultWriter: os.Stdout,
|
||||
})
|
||||
So(err, ShouldBeNil)
|
||||
})
|
||||
|
||||
Convey("HTTP errors", t, func() {
|
||||
port := test.GetFreePort()
|
||||
result := bytes.NewBuffer([]byte{})
|
||||
searchConfig := SearchConfig{
|
||||
SearchService: mockService{},
|
||||
ServURL: fmt.Sprintf("http://127.0.0.1:%v", port),
|
||||
User: "",
|
||||
OutputFormat: "text",
|
||||
ResultWriter: result,
|
||||
}
|
||||
|
||||
Convey("v2 is Unauthorised", func() {
|
||||
server := StartTestHTTPServer(HTTPRoutes{
|
||||
RouteHandler{
|
||||
Route: "/v2/",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
}, port)
|
||||
defer server.Close()
|
||||
|
||||
err := GetServerStatus(searchConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(result.String(), ShouldContainSubstring, "unauthorised access, endpoint requires valid user credentials")
|
||||
|
||||
// with bad user set
|
||||
searchConfig.User = "test:test"
|
||||
err = GetServerStatus(searchConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(result.String(), ShouldContainSubstring, "unauthorised access, given credentials are invalid")
|
||||
})
|
||||
|
||||
Convey("v2 bad http status code", func() {
|
||||
server := StartTestHTTPServer(HTTPRoutes{
|
||||
RouteHandler{
|
||||
Route: "/v2/",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
}, port)
|
||||
defer server.Close()
|
||||
|
||||
err := GetServerStatus(searchConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(result.String(), ShouldContainSubstring, zerr.ErrAPINotSupported.Error())
|
||||
})
|
||||
|
||||
Convey("MGMT errors", func() {
|
||||
Convey("URL not found", func() {
|
||||
server := StartTestHTTPServer(HTTPRoutes{
|
||||
RouteHandler{
|
||||
Route: "/v2/",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
}, port)
|
||||
defer server.Close()
|
||||
|
||||
err := GetServerStatus(searchConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(result.String(), ShouldContainSubstring, "endpoint is not available")
|
||||
})
|
||||
|
||||
Convey("Unauthorized Access", func() {
|
||||
server := StartTestHTTPServer(HTTPRoutes{
|
||||
RouteHandler{
|
||||
Route: "/v2/",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
RouteHandler{
|
||||
Route: constants.RoutePrefix + constants.ExtMgmt,
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
}, port)
|
||||
defer server.Close()
|
||||
|
||||
err := GetServerStatus(searchConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(result.String(), ShouldContainSubstring, "unauthorised access")
|
||||
})
|
||||
|
||||
Convey("Bad status code", func() {
|
||||
server := StartTestHTTPServer(HTTPRoutes{
|
||||
RouteHandler{
|
||||
Route: "/v2/",
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
RouteHandler{
|
||||
Route: constants.RoutePrefix + constants.ExtMgmt,
|
||||
HandlerFunc: func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
},
|
||||
AllowedMethods: []string{http.MethodGet},
|
||||
},
|
||||
}, port)
|
||||
defer server.Close()
|
||||
|
||||
err := GetServerStatus(searchConfig)
|
||||
So(err, ShouldBeNil)
|
||||
So(result.String(), ShouldContainSubstring, zerr.ErrAPINotSupported.Error())
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -1179,7 +1179,7 @@ func TestServeMgmtExtension(t *testing.T) {
|
|||
So(found, ShouldBeTrue)
|
||||
})
|
||||
|
||||
Convey("Mgmt disabled - search unconfigured", t, func(c C) {
|
||||
Convey("Mgmt disabled - Search unconfigured", t, func(c C) {
|
||||
content := `{
|
||||
"storage": {
|
||||
"rootDirectory": "%s"
|
||||
|
@ -1193,9 +1193,6 @@ func TestServeMgmtExtension(t *testing.T) {
|
|||
"output": "%s"
|
||||
},
|
||||
"extensions": {
|
||||
"search": {
|
||||
"enable": false
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
|
|
|
@ -43,7 +43,10 @@ type Auth struct {
|
|||
|
||||
type StrippedConfig struct {
|
||||
DistSpecVersion string `json:"distSpecVersion" mapstructure:"distSpecVersion"`
|
||||
Commit string `json:"commit" mapstructure:"commit"`
|
||||
ReleaseTag string `json:"releaseTag" mapstructure:"releaseTag"`
|
||||
BinaryType string `json:"binaryType" mapstructure:"binaryType"`
|
||||
|
||||
HTTP struct {
|
||||
Auth *Auth `json:"auth,omitempty" mapstructure:"auth"`
|
||||
} `json:"http" mapstructure:"http"`
|
||||
|
|
|
@ -1391,6 +1391,9 @@ const docTemplate = `{
|
|||
"binaryType": {
|
||||
"type": "string"
|
||||
},
|
||||
"commit": {
|
||||
"type": "string"
|
||||
},
|
||||
"distSpecVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1401,6 +1404,9 @@ const docTemplate = `{
|
|||
"$ref": "#/definitions/extensions.Auth"
|
||||
}
|
||||
}
|
||||
},
|
||||
"releaseTag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1382,6 +1382,9 @@
|
|||
"binaryType": {
|
||||
"type": "string"
|
||||
},
|
||||
"commit": {
|
||||
"type": "string"
|
||||
},
|
||||
"distSpecVersion": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -1392,6 +1395,9 @@
|
|||
"$ref": "#/definitions/extensions.Auth"
|
||||
}
|
||||
}
|
||||
},
|
||||
"releaseTag": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -154,6 +154,8 @@ definitions:
|
|||
properties:
|
||||
binaryType:
|
||||
type: string
|
||||
commit:
|
||||
type: string
|
||||
distSpecVersion:
|
||||
type: string
|
||||
http:
|
||||
|
@ -161,6 +163,8 @@ definitions:
|
|||
auth:
|
||||
$ref: '#/definitions/extensions.Auth'
|
||||
type: object
|
||||
releaseTag:
|
||||
type: string
|
||||
type: object
|
||||
github_com_opencontainers_image-spec_specs-go_v1.Descriptor:
|
||||
properties:
|
||||
|
|
Loading…
Reference in a new issue