44package cli
55
66import (
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
126153orch-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 , "-\t Desired Power Status:\t %v\n " , * host .DesiredPowerState )
375402 _ , _ = fmt .Fprintf (writer , "-\t Power Command Policy :\t %v\n " , * host .PowerCommandPolicy )
376403 _ , _ = fmt .Fprintf (writer , "-\t PowerOn Time :\t %v\n " , * host .PowerOnTime )
404+ _ , _ = fmt .Fprintf (writer , "-\t Desired 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
14171464func 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