Skip to content

Commit b5cf0fb

Browse files
authored
Add amt state in orch-cli (#157)
Signed-off-by: punam biswal <[email protected]>
1 parent 09d5f7c commit b5cf0fb

File tree

4 files changed

+292
-8
lines changed

4 files changed

+292
-8
lines changed

internal/cli/cli_suite_test.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,11 @@ func (s *CLITestSuite) runCommand(commandArgs string) (string, error) {
254254

255255
func addCommandArgs(args commandArgs, commandString string) string {
256256
for argName, argValue := range args {
257-
commandString = commandString + fmt.Sprintf(` --%s %s `, argName, argValue)
257+
if argValue == "" {
258+
commandString += fmt.Sprintf(" --%s", argName)
259+
} else {
260+
commandString += fmt.Sprintf(" --%s=%s", argName, argValue)
261+
}
258262
}
259263
return commandString
260264
}

internal/cli/host.go

Lines changed: 206 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package cli
55

66
import (
7+
"bufio"
78
"context"
89
"encoding/json"
910
"errors"
@@ -122,6 +123,32 @@ orch-cli set host host-1234abcd --project itep --power-policy ordered
122123
--power - Set desired power state of host to on|off|cycle|hibernate|reset|sleep
123124
--power-policy - Set the desired power command policy to ordered|immediate
124125
126+
#Set host AMT state to provisioned
127+
orch-cli set host host-1234abcd --project some-project --amt-state provisioned
128+
129+
--amt-state - Set desired AMT state of host to provisioned|unprovisioned
130+
131+
# Generate CSV input file using the --generate-csv flag - the default output will be a base test.csv file.
132+
orch-cli set host --project some-project --generate-csv
133+
134+
# Generate CSV input file using the --generate-csv flag - the defined output will be a base myhosts.csv file.
135+
orch-cli set host --project some-project --generate-csv=myhosts.csv
136+
137+
# Sample input csv file hosts.csv
138+
139+
Name - Name of the machine - mandatory field
140+
ResourceID - Unique Identifier of host - mandatory field
141+
DesiredAmtState - Desired AMT state of host - provisioned|unprovisioned - mandatory field
142+
143+
Name,ResourceID,DesiredAmtState
144+
host-1,host-1234abcd,provisioned
145+
146+
# --dry-run allows for verification of the validity of the input csv file without updating hosts
147+
orch-cli set host --project some-project --import-from-csv test.csv --dry-run
148+
149+
# Set hosts - --import-from-csv is a mandatory flag pointing to the input file. Successfully provisioned host indicated by output - errors provided in output file
150+
orch-cli set host --project some-project --import-from-csv test.csv
151+
125152
#Set host OS Update policy
126153
orch-cli set host host-1234abcd --project itep --osupdatepolicy <resourceID>
127154
@@ -374,6 +401,7 @@ func printHost(writer io.Writer, host *infra.HostResource) {
374401
_, _ = fmt.Fprintf(writer, "-\tDesired Power Status:\t %v\n", *host.DesiredPowerState)
375402
_, _ = fmt.Fprintf(writer, "-\tPower Command Policy :\t %v\n", *host.PowerCommandPolicy)
376403
_, _ = fmt.Fprintf(writer, "-\tPowerOn Time :\t %v\n", *host.PowerOnTime)
404+
_, _ = fmt.Fprintf(writer, "-\tDesired AMT State :\t %v\n", *host.DesiredAmtState)
377405
}
378406

379407
if host.CurrentAmtState != nil && *host.CurrentAmtState != infra.AMTSTATEPROVISIONED {
@@ -1025,12 +1053,31 @@ func getSetHostCommand() *cobra.Command {
10251053
Use: "host <resourceID> [flags]",
10261054
Short: "Sets a host attribute or action",
10271055
Example: setHostExamples,
1028-
Args: cobra.ExactArgs(1),
1056+
Args: func(cmd *cobra.Command, args []string) error {
1057+
generateCSV, _ := cmd.Flags().GetString("generate-csv")
1058+
if generateCSV == "" {
1059+
generateCSV = "test.csv"
1060+
}
1061+
importCSV, _ := cmd.Flags().GetString("import-from-csv")
1062+
if generateCSV != "" || importCSV != "" {
1063+
// No positional arg required for bulk operations
1064+
return nil
1065+
}
1066+
if len(args) != 1 {
1067+
return fmt.Errorf("accepts 1 arg(s), received %d", len(args))
1068+
}
1069+
return nil
1070+
},
10291071
Aliases: hostAliases,
10301072
RunE: runSetHostCommand,
10311073
}
1074+
cmd.PersistentFlags().StringP("import-from-csv", "i", viper.GetString("import-from-csv"), "CSV file containing information about provisioned hosts")
1075+
cmd.PersistentFlags().BoolP("dry-run", "d", viper.GetBool("dry-run"), "Verify the validity of input CSV file")
1076+
cmd.PersistentFlags().StringP("generate-csv", "g", viper.GetString("generate-csv"), "Generates a template CSV file for host import")
1077+
cmd.PersistentFlags().Lookup("generate-csv").NoOptDefVal = filename
10321078
cmd.PersistentFlags().StringP("power", "r", viper.GetString("power"), "Power on|off|cycle|hibernate|reset|sleep")
10331079
cmd.PersistentFlags().StringP("power-policy", "c", viper.GetString("power-policy"), "Set power policy immediate|ordered")
1080+
cmd.PersistentFlags().StringP("amt-state", "a", viper.GetString("amt-state"), "Set AMT state <provisioned|unprovisioned>")
10341081
cmd.PersistentFlags().StringP("osupdatepolicy", "u", viper.GetString("osupdatepolicy"), "Set OS update policy <resourceID>")
10351082

10361083
return cmd
@@ -1415,19 +1462,141 @@ func runDeleteHostCommand(cmd *cobra.Command, args []string) error {
14151462

14161463
// Set attributes for specific Host - finds a host using resource ID
14171464
func runSetHostCommand(cmd *cobra.Command, args []string) error {
1418-
hostID := args[0]
14191465

1466+
generateCSV, _ := cmd.Flags().GetString("generate-csv")
1467+
importCSV, _ := cmd.Flags().GetString("import-from-csv")
14201468
policyFlag, _ := cmd.Flags().GetString("power-policy")
14211469
powerFlag, _ := cmd.Flags().GetString("power")
14221470
updFlag, _ := cmd.Flags().GetString("osupdatepolicy")
1471+
amtFlag, _ := cmd.Flags().GetString("amt-state")
1472+
1473+
// Bulk CSV generation
1474+
if generateCSV != "" {
1475+
// Fetch all hosts (reuse your list logic)
1476+
ctx, hostClient, projectName, err := InfraFactory(cmd)
1477+
if err != nil {
1478+
return err
1479+
}
1480+
pageSize := 100
1481+
hosts := make([]infra.HostResource, 0)
1482+
for offset := 0; ; offset += pageSize {
1483+
resp, err := hostClient.HostServiceListHostsWithResponse(ctx, projectName,
1484+
&infra.HostServiceListHostsParams{
1485+
PageSize: &pageSize,
1486+
Offset: &offset,
1487+
}, auth.AddAuthHeader)
1488+
if err != nil {
1489+
return processError(err)
1490+
}
1491+
hosts = append(hosts, resp.JSON200.Hosts...)
1492+
if !resp.JSON200.HasNext {
1493+
break
1494+
}
1495+
}
1496+
// Write CSV
1497+
// Use absolute path for CSV file if not already absolute
1498+
csvPath := generateCSV
1499+
if !filepath.IsAbs(csvPath) {
1500+
wd, err := os.Getwd()
1501+
if err != nil {
1502+
return err
1503+
}
1504+
csvPath = filepath.Join(wd, csvPath)
1505+
}
1506+
// Check if file already exists
1507+
if _, err := os.Stat(csvPath); err == nil {
1508+
fmt.Printf("File %s already exists not generating\n", csvPath)
1509+
return nil
1510+
}
1511+
f, err := os.Create(csvPath)
1512+
if err != nil {
1513+
return err
1514+
}
1515+
defer f.Close()
1516+
fmt.Fprintln(f, "Name,ResourceID,DesiredAmtState")
1517+
for _, h := range hosts {
1518+
name := h.Name
1519+
resourceID := ""
1520+
desiredAmtState := ""
1521+
if h.ResourceId != nil {
1522+
resourceID = *h.ResourceId
1523+
}
1524+
if h.DesiredAmtState != nil {
1525+
desiredAmtState = string(*h.DesiredAmtState)
1526+
}
1527+
fmt.Fprintf(f, "%s,%s,%s\n", name, resourceID, desiredAmtState)
1528+
}
1529+
fmt.Printf("CSV template generated: %s\n", generateCSV)
1530+
return nil
1531+
}
14231532

1424-
if (policyFlag == "" || strings.HasPrefix(policyFlag, "--")) && (powerFlag == "" || strings.HasPrefix(powerFlag, "--")) && updFlag == "" {
1533+
// Bulk CSV import
1534+
if importCSV != "" {
1535+
file, err := os.Open(importCSV)
1536+
if err != nil {
1537+
return err
1538+
}
1539+
defer file.Close()
1540+
scanner := bufio.NewScanner(file)
1541+
lineNum := 0
1542+
for scanner.Scan() {
1543+
line := scanner.Text()
1544+
lineNum++
1545+
if lineNum == 1 {
1546+
continue // skip header
1547+
}
1548+
fields := strings.Split(line, ",")
1549+
if len(fields) < 3 {
1550+
fmt.Printf("Skipping invalid line %d: %s\n", lineNum, line)
1551+
continue
1552+
}
1553+
name := strings.TrimSpace(fields[0])
1554+
resourceID := strings.TrimSpace(fields[1])
1555+
desiredAmtState := strings.TrimSpace(fields[2])
1556+
// Validate desiredAmtState
1557+
amtState, err := resolveAmtState(desiredAmtState)
1558+
if err != nil {
1559+
fmt.Printf("Invalid AMT state for host %s: %s\n", name, desiredAmtState)
1560+
continue
1561+
}
1562+
// Patch host
1563+
ctx, hostClient, projectName, err := InfraFactory(cmd)
1564+
if err != nil {
1565+
fmt.Printf("InfraFactory error for host %s: %v\n", name, err)
1566+
continue
1567+
}
1568+
resp, err := hostClient.HostServicePatchHostWithResponse(ctx, projectName, resourceID, infra.HostServicePatchHostJSONRequestBody{
1569+
DesiredAmtState: &amtState,
1570+
}, auth.AddAuthHeader)
1571+
if err != nil {
1572+
fmt.Printf("Failed to patch host %s: %v\n", name, err)
1573+
continue
1574+
}
1575+
if err := checkResponse(resp.HTTPResponse, resp.Body, "error while executing host set for AMT"); err != nil {
1576+
fmt.Printf("Failed to patch host %s: %v\n", name, err)
1577+
continue
1578+
}
1579+
fmt.Printf("Host %s (%s) AMT state updated to %s\n", name, resourceID, desiredAmtState)
1580+
}
1581+
if err := scanner.Err(); err != nil {
1582+
return err
1583+
}
1584+
return nil
1585+
}
1586+
1587+
if len(args) == 0 {
1588+
return fmt.Errorf("no host ID provided")
1589+
}
1590+
hostID := args[0]
1591+
1592+
if (policyFlag == "" || strings.HasPrefix(policyFlag, "--")) && (powerFlag == "" || strings.HasPrefix(powerFlag, "--")) && updFlag == "" && (amtFlag == "" || strings.HasPrefix(amtFlag, "--")) {
14251593
return errors.New("a flag must be provided with the set host command and value cannot be \"\"")
14261594
}
14271595

14281596
var power *infra.PowerState
14291597
var policy *infra.PowerCommandPolicy
14301598
var updatePolicy *string
1599+
var amtState *infra.AmtState
14311600

14321601
if policyFlag != "" {
14331602
pol, err := resolvePowerPolicy(policyFlag)
@@ -1449,6 +1618,14 @@ func runSetHostCommand(cmd *cobra.Command, args []string) error {
14491618
updatePolicy = &updFlag
14501619
}
14511620

1621+
if amtFlag != "" {
1622+
amt, err := resolveAmtState(amtFlag)
1623+
if err != nil {
1624+
return err
1625+
}
1626+
amtState = &amt
1627+
}
1628+
14521629
ctx, hostClient, projectName, err := InfraFactory(cmd)
14531630
if err != nil {
14541631
return err
@@ -1490,6 +1667,21 @@ func runSetHostCommand(cmd *cobra.Command, args []string) error {
14901667
}
14911668
}
14921669

1670+
if amtState != nil && host.Instance != nil {
1671+
resp, err := hostClient.HostServicePatchHostWithResponse(ctx, projectName, hostID, infra.HostServicePatchHostJSONRequestBody{
1672+
DesiredAmtState: amtState,
1673+
Name: host.Name,
1674+
}, auth.AddAuthHeader)
1675+
if err != nil {
1676+
return processError(err)
1677+
}
1678+
if err := checkResponse(resp.HTTPResponse, resp.Body, "error while executing host set for AMT"); err != nil {
1679+
return err
1680+
}
1681+
}
1682+
1683+
fmt.Printf("Host %s updated successfully\n", hostID)
1684+
14931685
return nil
14941686
}
14951687

@@ -1767,3 +1959,14 @@ func resolvePower(power string) (infra.PowerState, error) {
17671959
return "", errors.New("incorrect power action provided with --power flag use one of on|off|cycle|hibernate|reset|sleep")
17681960
}
17691961
}
1962+
1963+
func resolveAmtState(amt string) (infra.AmtState, error) {
1964+
switch amt {
1965+
case "provisioned", "AMT_STATE_PROVISIONED":
1966+
return infra.AMTSTATEPROVISIONED, nil
1967+
case "unprovisioned", "AMT_STATE_UNPROVISIONED":
1968+
return infra.AMTSTATEUNPROVISIONED, nil
1969+
default:
1970+
return "", errors.New("incorrect AMT state provided with --amt-state flag use one of provisioned|unprovisioned")
1971+
}
1972+
}

0 commit comments

Comments
 (0)