Skip to content

Commit

Permalink
Merge master
Browse files Browse the repository at this point in the history
  • Loading branch information
ldmberman committed Apr 5, 2016
2 parents 802286a + 1f7974c commit 1c248c8
Show file tree
Hide file tree
Showing 9 changed files with 314 additions and 30 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This is the Command Line Interface (CLI) for the CenturyLink Cloud.

Click a link below to download the latest release for each OS.

[MacOS tar.gz](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-03-30/clc-2016-03-30-darwin-amd64.tar.gz) | [MacOS pkg](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-03-28/clc-2016-03-28.pkg) | [Linux tar.gz](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-03-30/clc-2016-03-30-linux-amd64.tar.gz) | [Windows zip](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-03-30/clc-2016-03-30-windows-amd64.zip)
[MacOS tar.gz](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-04-05/clc-2016-04-05-darwin-amd64.tar.gz) | [MacOS pkg](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-03-28/clc-2016-03-28.pkg) | [Linux tar.gz](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-04-05/clc-2016-04-05-linux-amd64.tar.gz) | [Windows zip](https://github.com/CenturyLinkCloud/clc-go-cli/releases/download/2016-04-05/clc-2016-04-05-windows-amd64.zip)

**Note:** You can see previous releases and release notes on the [releases page](https://github.com/CenturyLinkCloud/clc-go-cli/releases).

Expand Down
94 changes: 94 additions & 0 deletions init.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/centurylinkcloud/clc-go-cli/models/group"
"github.com/centurylinkcloud/clc-go-cli/models/ips"
"github.com/centurylinkcloud/clc-go-cli/models/network"
"github.com/centurylinkcloud/clc-go-cli/models/ospatch"
"github.com/centurylinkcloud/clc-go-cli/models/server"
)

Expand Down Expand Up @@ -874,6 +875,30 @@ func init() {
},
},
})
registerCommandBase(&server.ExecutePackage{}, &[]server.ServerRes{}, commands.CommandExcInfo{
Verb: "POST",
Url: "https://api.ctl.io/v2/operations/{accountAlias}/servers/executePackage",
Resource: "server",
Command: "execute-package",
Help: help.Command{
Brief: []string{"Executes a single package on one or more servers"},
Arguments: []help.Argument{
{
"--server-ids",
[]string{"Required. A list of server IDs to execute the package on"},
},
{
"--package",
[]string{
"Required. The package entity describing which package to run on the specified servers.",
"It has to contain the following fields:",
" package-id: unique identifier of the package to execute",
" paramaters: a JSON string containing a set of key-value pairs for setting the package-specific parameters defined",
},
},
},
},
})

registerCommandBase(&group.GetReq{}, &group.Entity{}, commands.CommandExcInfo{
Verb: "GET",
Expand Down Expand Up @@ -2850,6 +2875,75 @@ func init() {
},
})

registerCommandBase(&ospatch.Patch{}, &[]server.ServerRes{}, commands.CommandExcInfo{
Verb: "POST",
Url: "https://api.ctl.io/v2/operations/{accountAlias}/servers/executePackage",
Resource: "os-patch",
Command: "apply",
Help: help.Command{
Brief: []string{"Patches the given servers with the latest available patches provided by the OS vendor"},
Arguments: []help.Argument{
{
"--server-ids",
[]string{"Required. A list of server IDs to execute the package on"},
},
{
"--os-type",
[]string{
"Required. 'Windows2012' or 'RedHat'",
"'Windows2012' patches Windows 2012 and Windows 2012R2",
"'RedHat' patches RedHat Enterprise Linux 5,6,7, and CentOS 5,6",
},
},
},
},
})
registerCommandBase(&ospatch.List{}, &[]ospatch.PatchInfo{}, commands.CommandExcInfo{
Verb: "GET",
Url: "https://patching.useast.appfog.ctl.io/rest/servers/{accountAlias}/server/{ServerId}/patch",
Resource: "os-patch",
Command: "list",
Help: help.Command{
Brief: []string{"Shows a history of all the patches deployed to a given server"},
Arguments: []help.Argument{
{
"--server-id",
[]string{"Required unless --server-name is specified. The ID of a server to query"},
},
{
"--server-name",
[]string{"Required unless --server-id is specified. The name of a server to query"},
},
},
},
})
registerCommandBase(&ospatch.ListDetails{}, &ospatch.PatchDetails{}, commands.CommandExcInfo{
Verb: "GET",
Url: "https://patching.useast.appfog.ctl.io/rest/servers/{accountAlias}/server/{ServerId}/patch/{ExecutionId}",
Resource: "os-patch",
Command: "list-details",
Help: help.Command{
Brief: []string{"Returns details on all attempted patches for a single execution against a server"},
Arguments: []help.Argument{
{
"--server-id",
[]string{"Required unless --server-name is specified. The ID of a server to query"},
},
{
"--server-name",
[]string{"Required unless --server-id is specified. The name of a server to query"},
},
{
"--execution-id",
[]string{
"Required. Correlation ID for all the patches included with a single update execution, obtained from the os-patch list response",
"or emails regarding a patch request. The execution ID format will be aa#-######",
},
},
},
},
})

registerCommandBase(&backup.AccountPolicyReq{}, &backup.AccountPolicy{}, commands.CommandExcInfo{
Verb: "POST",
Url: "https://api-va1.backup.ctl.io/clc-backup-api/api/accountPolicies",
Expand Down
43 changes: 40 additions & 3 deletions model_loader/model_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ func loadValue(key string, arg interface{}, field reflect.Value) error {
return nil
}
return fmt.Errorf("%s does not accept any value.", key)
case map[string]string:
argMapStringString, err := parseMapStringString(arg)
if err != nil {
if _, ok := err.(ParseObjWrongTypeError); ok {
return fmt.Errorf("Type mismatch: %s must be an object", key)
}
return err
}
field.Set(reflect.ValueOf(argMapStringString))
return nil
}
if isStruct(field) {
argStruct, err := parseStruct(arg)
Expand Down Expand Up @@ -206,6 +216,7 @@ func isSlice(model reflect.Value) bool {
// If arg is already of type map[string]interface{} returns it as is.
func parseStruct(arg interface{}) (map[string]interface{}, error) {
if argMap, isMap := arg.(map[string]interface{}); isMap {
parser.NormalizeKeys(argMap)
return argMap, nil
}

Expand All @@ -219,14 +230,13 @@ func parseStruct(arg interface{}) (map[string]interface{}, error) {
parser.NormalizeKeys(parsed)
return parsed, nil
}
if parsed, err := parser.ParseObject(argString); err == nil {
if parsed, err := parser.ParseObject(argString, true); err == nil {
return parsed, nil
}
return nil, fmt.Errorf("`%s` must be an object specified either in JSON or in key=value,.. format", argString)
}

// Parses an object of type []interface{} either from JSON.
// Also, calls NormalizeKeys with the parsed object.
// If arg is already of type []interface{} returns it as is.
func parseSlice(arg interface{}) ([]interface{}, error) {
if argSlice, isSlice := arg.([]interface{}); isSlice {
Expand All @@ -240,12 +250,39 @@ func parseSlice(arg interface{}) ([]interface{}, error) {

parsed := make([]interface{}, 0)
if err := json.Unmarshal([]byte(argString), &parsed); err == nil {
parser.NormalizeKeys(parsed)
return parsed, nil
}
return []interface{}{arg}, nil
}

// Parses an object of type map[string]string either from JSON or from a=b,c=d,.. notation.
func parseMapStringString(arg interface{}) (map[string]string, error) {
var argMapStringString = make(map[string]string, 0)

argMap, isMap := arg.(map[string]interface{})
if !isMap {
argString, isString := arg.(string)
if !isString {
return nil, ParseObjWrongTypeError{}
}
argMap = make(map[string]interface{}, 0)
err := json.Unmarshal([]byte(argString), &argMap)
if err != nil {
argMap, err = parser.ParseObject(argString, false)
}
if err != nil {
return nil, ParseObjWrongTypeError{}
}
}
for k, v := range argMap {
if _, isString := v.(string); !isString {
return nil, fmt.Errorf("Type mismatch: `%s` must be string", k)
}
argMapStringString[k] = v.(string)
}
return argMapStringString, nil
}

func getEmplySliceType(slice reflect.Value) reflect.Value {
return reflect.New(slice.Type().Elem())
}
82 changes: 69 additions & 13 deletions model_loader/model_loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,24 @@ type modelLoaderTestCase struct {
}

type testModel struct {
FieldString string
FieldInt int64
FieldFloat float64
FieldBool bool
FieldDateTime time.Time
FieldObject testFieldObject
FieldArray []testFieldObject
FieldNil base.NilField
testInnerObject `argument:"composed"`
FieldIntPtr *int64
FieldString string
FieldInt int64
FieldFloat float64
FieldBool bool
FieldDateTime time.Time
FieldObject testFieldObject
FieldArray []testFieldObject
FieldNil base.NilField
testInnerObject `argument:"composed"`
FieldIntPtr *int64
FieldMapStringString map[string]string
}

type testFieldObject struct {
FieldString string
FieldInnerObject testFieldInnerObject
FieldInnerArray []testFieldObject
FieldString string
FieldInnerObject testFieldInnerObject
FieldMapStringString map[string]string
FieldInnerArray []testFieldObject
}

type testFieldInnerObject struct {
Expand Down Expand Up @@ -98,6 +100,42 @@ var testCases = []modelLoaderTestCase{
FieldIntPtr: &two,
},
},
// Handles map[string]string objects.
{
args: map[string]interface{}{
"FieldMapStringString": `{"key1":"value1","key2":"value2"}`,
},
res: testModel{
FieldMapStringString: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
{
args: map[string]interface{}{
"FieldMapStringString": `key1=value1,key2=value2`,
},
res: testModel{
FieldMapStringString: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
{
args: map[string]interface{}{
"FieldObject": `{"FieldMapStringString":{"key1":"value1","key2":"value2"}}`,
},
res: testModel{
FieldObject: testFieldObject{
FieldMapStringString: map[string]string{
"key1": "value1",
"key2": "value2",
},
},
},
},
// Parses JSON and loads it into object field.
{
args: map[string]interface{}{
Expand Down Expand Up @@ -272,6 +310,24 @@ var testCases = []modelLoaderTestCase{
},
err: "Type mismatch: FieldDateTime value must be datetime in `YYYY-MM-DD hh:mm:ss` format.",
},
{
args: map[string]interface{}{
"FieldMapStringString": `just string`,
},
err: "Type mismatch: FieldMapStringString must be an object",
},
{
args: map[string]interface{}{
"FieldMapStringString": `[{"key":"value"}]`,
},
err: "Type mismatch: FieldMapStringString must be an object",
},
{
args: map[string]interface{}{
"FieldObject": `{"FieldMapStringString":{"key":5}}`,
},
err: "Type mismatch: `key` must be string",
},
// Does not accept any values for base.NilField's.
{
args: map[string]interface{}{
Expand Down
33 changes: 33 additions & 0 deletions models/ospatch/entities.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package ospatch

import "github.com/centurylinkcloud/clc-go-cli/base"

type PatchInfo struct {
Execution_id string
Status string
Start_time base.Time
End_time base.Time
Init_messages []struct {
Start_time base.Time
End_time base.Time
Init_begin_message string
Init_end_message string
}
}

type PatchDetails struct {
Execution_id string
Status string
Start_time base.Time
End_time base.Time
Duration string
Begin_message string
End_message string
Patches []struct {
Start_time base.Time
End_time base.Time
Patch_begin_message string
Patch_end_message string
Status string
}
}
50 changes: 50 additions & 0 deletions models/ospatch/patch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package ospatch

import (
"encoding/json"

"github.com/centurylinkcloud/clc-go-cli/errors"
"github.com/centurylinkcloud/clc-go-cli/models/server"
)

var (
packages = map[string]server.PackageDef{
"Windows2012": server.PackageDef{
PackageId: "b229535c-a313-4a31-baf8-6aa71ff4b9ed",
},
"RedHat": server.PackageDef{
PackageId: "c3c6642e-24e1-4c37-b56a-1cf1476ee360",
Parameters: map[string]string{
"patch.debug.mode": "false",
},
},
}
)

type Patch struct {
ServerIds []string `json:"servers"`
OsType string `valid:"required" oneOf:"Windows2012,RedHat"`
}

func (p *Patch) Validate() error {
if len(p.ServerIds) == 0 {
return errors.EmptyField("server-ids")
}
return nil
}

func (p *Patch) MarshalJSON() ([]byte, error) {
return json.Marshal(server.ExecutePackage{
ServerIds: p.ServerIds,
Package: packages[p.OsType],
})
}

type List struct {
server.Server `argument:"composed" URIParam:"ServerId"`
}

type ListDetails struct {
server.Server `argument:"composed" URIParam:"ServerId"`
ExecutionId string `URIParam:"yes" valid:"required"`
}
Loading

0 comments on commit 1c248c8

Please sign in to comment.