<commit_before>/*
Copyright 2017 Matthew Lord (mattalord@gmail.com)

WARNING: This is experimental and for demonstration purposes only!

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package instances

import (
  "os"
  "log"
  "errors"
  "strings"
  "strconv"
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
)

// member variables that start with capital letters are public/exported
type Instance struct {
  Mysql_host string
  Mysql_port string
  Mysql_user string
  mysql_pass string

  // The status related vars can serve as an effective cache
  Group_name string
  Server_uuid string
  Member_state string
  Online_participants uint8
  Has_quorum bool
  Read_only bool
  db *sql.DB
}

// enable debug logging for all instances
var Debug bool = false

// setup debug logging for all instances
var DebugLog = log.New(os.Stderr,
               "DEBUG: ",
               log.Ldate|log.Ltime|log.Lshortfile)

func New( myh string, myp string, myu string, mys string ) * Instance {
  return &Instance{ Mysql_host: myh, Mysql_port: myp, Mysql_user: myu, mysql_pass: mys }
}

func (me *Instance) Connect() error {
  var err error

  if( me.db == nil ){
    conn_string := me.Mysql_user + ":" + me.mysql_pass + "@tcp(" + me.Mysql_host + ":" + me.Mysql_port + ")/performance_schema"

    if( Debug ){
      DebugLog.Printf( "Making SQL connection using: %s\n", conn_string )
    }

    me.db, err = sql.Open( "mysql", conn_string )

    if( err != nil ){
      DebugLog.Printf( "Error during sql.Open: %v", err )
    }
  }

  err = me.db.Ping()

  if( err == nil ){
    query_str := "SELECT variable_value FROM global_variables WHERE variable_name='group_replication_group_name'"

    if( Debug ){
      DebugLog.Printf( "Checking group name on '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, query_str )
    }

    err = me.db.QueryRow( query_str ).Scan( &me.Group_name )

    if( err != nil || me.Group_name == "" ){
      err = errors.New( "Specified MySQL Instance is not a member of any Group Replication cluster!" )
    }

    query_str = "SELECT variable_value, member_state FROM global_variables gv INNER JOIN replication_group_members rgm ON(gv.variable_value=rgm.member_id) WHERE gv.variable_name='server_uuid'"

    if( Debug ){
      DebugLog.Printf( "Checking status of '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, query_str )
    }

    err = me.db.QueryRow( query_str ).Scan( &me.Server_uuid, &me.Member_state )
  }

  return err
}

func (me *Instance) HasQuorum() (bool, error) {
  quorum_query := "SELECT IF( MEMBER_STATE='ONLINE' AND ((SELECT COUNT(*) FROM replication_group_members WHERE MEMBER_STATE != 'ONLINE') >= ((SELECT COUNT(*) FROM replication_group_members)/2) = 0), 'true', 'false' ) FROM replication_group_members JOIN replication_group_member_stats USING(member_id)"

  if( Debug ){
    DebugLog.Printf( "Checking if '%s:%s' has a quorum. Query: %s\n", me.Mysql_host, me.Mysql_port, quorum_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( quorum_query ).Scan( &me.Has_quorum )
  }

  return me.Has_quorum, err
}

func (me *Instance) IsReadOnly() (bool, error) {
  ro_query := "SELECT variable_value FROM global_variables WHERE variable_name='super_read_only'"

  if( Debug ){
    DebugLog.Printf( "Checking if '%s:%s' is read only. Query: %s\n", me.Mysql_host, me.Mysql_port, ro_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( ro_query ).Scan( &me.Read_only )
  }

  return me.Read_only, err
}

func (me *Instance) GetMembers() (*[]Instance, error) {
  membership_query := "SELECT member_id, member_host, member_port, member_state FROM replication_group_members"
  member_slice := []Instance{}
  me.Online_participants = 0

  if( Debug ){
    DebugLog.Printf( "Getting group members from '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, membership_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    rows, err := me.db.Query( membership_query )

    if( err == nil ){
      defer rows.Close()

      for( rows.Next() ){
        member := New( "", "", me.Mysql_user, me.mysql_pass )
        err = rows.Scan( &member.Server_uuid, &member.Mysql_host, &member.Mysql_port, &member.Member_state )
        if( member.Member_state == "ONLINE" ){
          me.Online_participants++
        }
        member_slice = append( member_slice, *member )
      }

      if( Debug ){
        DebugLog.Printf( "Group member info found for '%s:%s' -- ONLINE member count: %d, Members: %+v\n", me.Mysql_host, me.Mysql_port, me.Online_participants, member_slice )
      }
    }
  }

  return &member_slice, err
}

func (me *Instance) Shutdown() error {
  shutdown_query := "SHUTDOWN"

  if( Debug ){
    DebugLog.Printf( "Shutting down node '%s:%s'\n", me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( shutdown_query )
  }

  return err
}

func (me *Instance) TransactionsExecuted() (string, error) {
  // since this is such a fast changing metric, I won't cache the value in the struct
  var gtids string
  gtid_query := "SELECT @@global.GTID_EXECUTED"

  if( Debug ){
    DebugLog.Printf( "Getting the transactions executed on '%s:%s'\n", me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( gtid_query ).Scan( &gtids )
  }

  return gtids, err
}

func (me *Instance) TransactionsExecutedCount() (uint64, error) {
  var err error
  var gtid_set string
  var cnt uint64

  gtid_set, err = me.TransactionsExecuted()

  if( err != nil ){
    cnt, err = TransactionCount( gtid_set )
  }

  return cnt, err
}

func (me *Instance) ApplierQueueLength() (uint64, error) {
  // since this is such a fast changing metric, I won't cache the value in the struct
  var qlen uint64
  var gtid_subset string
  gtid_subset_query := "SELECT GTID_SUBTRACT( (SELECT Received_transaction_set FROM performance_schema.replication_connection_status WHERE Channel_name = 'group_replication_applier' ), (SELECT @@global.GTID_EXECUTED) )"

  if( Debug ){
    DebugLog.Printf( "Getting the applier queue length on '%s:%s'\n", me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( gtid_subset_query ).Scan( &gtid_subset )
  }

  qlen, err = TransactionCount( gtid_subset )

  return qlen, err
}

/*
 This is a global function to count a total of all the GTIDs in a set
 An example set being:
"39a07a39-4b82-44d2-a3cd-978511564a57:1-37,
49311a3a-e058-46ba-8e7b-857b5db7d33f:1,
550fa9ee-a1f8-4b6d-9bfe-c03c12cd1c72:1-550757:1001496-1749225:2001496-2835762,
de6858e8-0669-4b82-a188-d2906daa6d91:1-119927"
With the total transaction count for that set being: 2252719
*/
func TransactionCount( gtid_set string ) (uint64, error) {
  var err error
  var gtid_count uint64 = 0
  next_dash_pos := 0
  next_colon_pos := 0
  next_comma_pos := 0
  colon_pos := strings.IndexRune( gtid_set, ':' )
  var firstval uint64 = 0
  var secondval uint64 = 0
  var nextval uint64 = 0

  if( Debug ){
    DebugLog.Printf( "Calculating total number of GTIDs from a set of: %s\n", gtid_set )
  }

  for colon_pos != -1 {
    // lets get rid of everything before the current colon, and the colon itself, as it's UUID info that we don't care about
    gtid_set = gtid_set[colon_pos+1:]

    next_dash_pos = strings.IndexRune( gtid_set, '-' )
    next_colon_pos = strings.IndexRune( gtid_set, ':' )
    next_comma_pos = strings.IndexRune( gtid_set, ',' )

    firstval = 0
    secondval = 0
    nextval = 0

    if( next_dash_pos < next_colon_pos && next_dash_pos < next_comma_pos ){
      if( next_colon_pos < next_comma_pos ){
        firstval, err = strconv.ParseUint( gtid_set[:next_dash_pos], 10, 64 )
        secondval, err = strconv.ParseUint( gtid_set[next_dash_pos+1 : next_colon_pos], 10, 64 )

        // the first GTID counts too
        firstval = firstval-1

        nextval = secondval - firstval
      } else {
        firstval, err = strconv.ParseUint( gtid_set[:next_dash_pos], 10, 64 )
        secondval, err = strconv.ParseUint( gtid_set[next_dash_pos+1 : next_comma_pos], 10, 64 )

        // the first GTID counts too
        firstval = firstval-1

        nextval = secondval - firstval
      }
    } else if( next_colon_pos == -1 && next_dash_pos != -1 ){
      firstval, err = strconv.ParseUint( gtid_set[:next_dash_pos], 10, 64 )
      secondval, err = strconv.ParseUint( gtid_set[next_dash_pos+1:], 10, 64 )

      // the first GTID counts too
      firstval = firstval-1

      nextval = secondval - firstval
    } else {
      nextval = 1
    }

    if( err != nil ){
      break
    }

    if( Debug ){
      DebugLog.Printf( "The current calculation is: (%d - %d)\n", secondval, firstval )
      DebugLog.Printf( "Current total: %d, adding %d\n", gtid_count, nextval )
    }

    gtid_count = gtid_count + nextval

    colon_pos = strings.IndexRune( gtid_set, ':' )

    if( Debug ){
      DebugLog.Printf( "Remaining unprocessed GTID string: %s\n", gtid_set )
    }
  }

  return gtid_count, err
}

func (me *Instance) ForceMembers( fms string ) error {
  force_membership_query := "SET GLOBAL group_replication_force_members='" + fms + "'"

  if( Debug ){
    DebugLog.Printf( "Forcing group membership on '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, force_membership_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( force_membership_query )
  }

  return err
}

func (me *Instance) SetReadOnly( ro bool ) error {
  ro_query := "SET GLOBAL super_read_only="

  if( ro ){
    ro_query = ro_query + "ON"
  } else {
    ro_query = ro_query + "OFF"
  }

  if( Debug ){
    DebugLog.Printf( "Setting read_only mode to %t on '%s:%s'\n", ro, me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( ro_query )
    me.Read_only = ro
  }

  return err
}

func (me *Instance) SetOfflineMode( om bool ) error {
  om_query := "SET GLOBAL offline_mode="

  if( om ){
    om_query = om_query + "ON"
  } else {
    om_query = om_query + "OFF"
  }

  if( Debug ){
    DebugLog.Printf( "Setting offline mode to %t on '%s:%s'\n", om, me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( om_query )
  }

  return err
}

func (me *Instance) Cleanup() error {
  var err error = nil

  if( me.db != nil ){
    err = me.db.Close()
  }

  return err
}
<commit_msg>IsReadOnly() is now checked in Connect()<commit_after>/*
Copyright 2017 Matthew Lord (mattalord@gmail.com)

WARNING: This is experimental and for demonstration purposes only!

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package instances

import (
  "os"
  "log"
  "errors"
  "strings"
  "strconv"
  "database/sql"
  _ "github.com/go-sql-driver/mysql"
)

// member variables that start with capital letters are public/exported
type Instance struct {
  Mysql_host string
  Mysql_port string
  Mysql_user string
  mysql_pass string

  // The status related vars can serve as an effective cache
  Group_name string
  Server_uuid string
  Member_state string
  Online_participants uint8
  Has_quorum bool
  Read_only bool
  db *sql.DB
}

// enable debug logging for all instances
var Debug bool = false

// setup debug logging for all instances
var DebugLog = log.New(os.Stderr,
               "DEBUG: ",
               log.Ldate|log.Ltime|log.Lshortfile)

func New( myh string, myp string, myu string, mys string ) * Instance {
  return &Instance{ Mysql_host: myh, Mysql_port: myp, Mysql_user: myu, mysql_pass: mys }
}

func (me *Instance) Connect() error {
  var err error

  if( me.db == nil ){
    conn_string := me.Mysql_user + ":" + me.mysql_pass + "@tcp(" + me.Mysql_host + ":" + me.Mysql_port + ")/performance_schema"

    if( Debug ){
      DebugLog.Printf( "Making SQL connection using: %s\n", conn_string )
    }

    me.db, err = sql.Open( "mysql", conn_string )

    if( err != nil ){
      DebugLog.Printf( "Error during sql.Open: %v", err )
    }
  }

  err = me.db.Ping()

  if( err == nil ){
    query_str := "SELECT variable_value FROM global_variables WHERE variable_name='group_replication_group_name'"

    if( Debug ){
      DebugLog.Printf( "Checking group name on '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, query_str )
    }

    err = me.db.QueryRow( query_str ).Scan( &me.Group_name )

    if( err != nil || me.Group_name == "" ){
      err = errors.New( "Specified MySQL Instance is not a member of any Group Replication cluster!" )
    } else {
      query_str = "SELECT variable_value, member_state FROM global_variables gv INNER JOIN replication_group_members rgm ON(gv.variable_value=rgm.member_id) WHERE gv.variable_name='server_uuid'"

      if( Debug ){
        DebugLog.Printf( "Checking status of '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, query_str )
      }

      err = me.db.QueryRow( query_str ).Scan( &me.Server_uuid, &me.Member_state )

      if( err == nil ){
        _, err = me.IsReadOnly()
      }
    }
  }

  return err
}

func (me *Instance) HasQuorum() (bool, error) {
  quorum_query := "SELECT IF( MEMBER_STATE='ONLINE' AND ((SELECT COUNT(*) FROM replication_group_members WHERE MEMBER_STATE != 'ONLINE') >= ((SELECT COUNT(*) FROM replication_group_members)/2) = 0), 'true', 'false' ) FROM replication_group_members JOIN replication_group_member_stats USING(member_id)"

  if( Debug ){
    DebugLog.Printf( "Checking if '%s:%s' has a quorum. Query: %s\n", me.Mysql_host, me.Mysql_port, quorum_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( quorum_query ).Scan( &me.Has_quorum )
  }

  return me.Has_quorum, err
}

func (me *Instance) IsReadOnly() (bool, error) {
  ro_query := "SELECT variable_value FROM global_variables WHERE variable_name='super_read_only'"

  if( Debug ){
    DebugLog.Printf( "Checking if '%s:%s' is read only. Query: %s\n", me.Mysql_host, me.Mysql_port, ro_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( ro_query ).Scan( &me.Read_only )
  }

  return me.Read_only, err
}

func (me *Instance) GetMembers() (*[]Instance, error) {
  membership_query := "SELECT member_id, member_host, member_port, member_state FROM replication_group_members"
  member_slice := []Instance{}
  me.Online_participants = 0

  if( Debug ){
    DebugLog.Printf( "Getting group members from '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, membership_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    rows, err := me.db.Query( membership_query )

    if( err == nil ){
      defer rows.Close()

      for( rows.Next() ){
        member := New( "", "", me.Mysql_user, me.mysql_pass )
        err = rows.Scan( &member.Server_uuid, &member.Mysql_host, &member.Mysql_port, &member.Member_state )
        if( member.Member_state == "ONLINE" ){
          me.Online_participants++
        }
        member_slice = append( member_slice, *member )
      }

      if( Debug ){
        DebugLog.Printf( "Group member info found for '%s:%s' -- ONLINE member count: %d, Members: %+v\n", me.Mysql_host, me.Mysql_port, me.Online_participants, member_slice )
      }
    }
  }

  return &member_slice, err
}

func (me *Instance) Shutdown() error {
  shutdown_query := "SHUTDOWN"

  if( Debug ){
    DebugLog.Printf( "Shutting down node '%s:%s'\n", me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( shutdown_query )
  }

  return err
}

func (me *Instance) TransactionsExecuted() (string, error) {
  // since this is such a fast changing metric, I won't cache the value in the struct
  var gtids string
  gtid_query := "SELECT @@global.GTID_EXECUTED"

  if( Debug ){
    DebugLog.Printf( "Getting the transactions executed on '%s:%s'\n", me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( gtid_query ).Scan( &gtids )
  }

  return gtids, err
}

func (me *Instance) TransactionsExecutedCount() (uint64, error) {
  var err error
  var gtid_set string
  var cnt uint64

  gtid_set, err = me.TransactionsExecuted()

  if( err != nil ){
    cnt, err = TransactionCount( gtid_set )
  }

  return cnt, err
}

func (me *Instance) ApplierQueueLength() (uint64, error) {
  // since this is such a fast changing metric, I won't cache the value in the struct
  var qlen uint64
  var gtid_subset string
  gtid_subset_query := "SELECT GTID_SUBTRACT( (SELECT Received_transaction_set FROM performance_schema.replication_connection_status WHERE Channel_name = 'group_replication_applier' ), (SELECT @@global.GTID_EXECUTED) )"

  if( Debug ){
    DebugLog.Printf( "Getting the applier queue length on '%s:%s'\n", me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    err = me.db.QueryRow( gtid_subset_query ).Scan( &gtid_subset )
  }

  qlen, err = TransactionCount( gtid_subset )

  return qlen, err
}

/*
 This is a global function to count a total of all the GTIDs in a set
 An example set being:
"39a07a39-4b82-44d2-a3cd-978511564a57:1-37,
49311a3a-e058-46ba-8e7b-857b5db7d33f:1,
550fa9ee-a1f8-4b6d-9bfe-c03c12cd1c72:1-550757:1001496-1749225:2001496-2835762,
de6858e8-0669-4b82-a188-d2906daa6d91:1-119927"
With the total transaction count for that set being: 2252719
*/
func TransactionCount( gtid_set string ) (uint64, error) {
  var err error
  var gtid_count uint64 = 0
  next_dash_pos := 0
  next_colon_pos := 0
  next_comma_pos := 0
  colon_pos := strings.IndexRune( gtid_set, ':' )
  var firstval uint64 = 0
  var secondval uint64 = 0
  var nextval uint64 = 0

  if( Debug ){
    DebugLog.Printf( "Calculating total number of GTIDs from a set of: %s\n", gtid_set )
  }

  for colon_pos != -1 {
    // lets get rid of everything before the current colon, and the colon itself, as it's UUID info that we don't care about
    gtid_set = gtid_set[colon_pos+1:]

    next_dash_pos = strings.IndexRune( gtid_set, '-' )
    next_colon_pos = strings.IndexRune( gtid_set, ':' )
    next_comma_pos = strings.IndexRune( gtid_set, ',' )

    firstval = 0
    secondval = 0
    nextval = 0

    if( next_dash_pos < next_colon_pos && next_dash_pos < next_comma_pos ){
      if( next_colon_pos < next_comma_pos ){
        firstval, err = strconv.ParseUint( gtid_set[:next_dash_pos], 10, 64 )
        secondval, err = strconv.ParseUint( gtid_set[next_dash_pos+1 : next_colon_pos], 10, 64 )

        // the first GTID counts too
        firstval = firstval-1

        nextval = secondval - firstval
      } else {
        firstval, err = strconv.ParseUint( gtid_set[:next_dash_pos], 10, 64 )
        secondval, err = strconv.ParseUint( gtid_set[next_dash_pos+1 : next_comma_pos], 10, 64 )

        // the first GTID counts too
        firstval = firstval-1

        nextval = secondval - firstval
      }
    } else if( next_colon_pos == -1 && next_dash_pos != -1 ){
      firstval, err = strconv.ParseUint( gtid_set[:next_dash_pos], 10, 64 )
      secondval, err = strconv.ParseUint( gtid_set[next_dash_pos+1:], 10, 64 )

      // the first GTID counts too
      firstval = firstval-1

      nextval = secondval - firstval
    } else {
      nextval = 1
    }

    if( err != nil ){
      break
    }

    if( Debug ){
      DebugLog.Printf( "The current calculation is: (%d - %d)\n", secondval, firstval )
      DebugLog.Printf( "Current total: %d, adding %d\n", gtid_count, nextval )
    }

    gtid_count = gtid_count + nextval

    colon_pos = strings.IndexRune( gtid_set, ':' )

    if( Debug ){
      DebugLog.Printf( "Remaining unprocessed GTID string: %s\n", gtid_set )
    }
  }

  return gtid_count, err
}

func (me *Instance) ForceMembers( fms string ) error {
  force_membership_query := "SET GLOBAL group_replication_force_members='" + fms + "'"

  if( Debug ){
    DebugLog.Printf( "Forcing group membership on '%s:%s'. Query: %s\n", me.Mysql_host, me.Mysql_port, force_membership_query )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( force_membership_query )
  }

  return err
}

func (me *Instance) SetReadOnly( ro bool ) error {
  ro_query := "SET GLOBAL super_read_only="

  if( ro ){
    ro_query = ro_query + "ON"
  } else {
    ro_query = ro_query + "OFF"
  }

  if( Debug ){
    DebugLog.Printf( "Setting read_only mode to %t on '%s:%s'\n", ro, me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( ro_query )
    me.Read_only = ro
  }

  return err
}

func (me *Instance) SetOfflineMode( om bool ) error {
  om_query := "SET GLOBAL offline_mode="

  if( om ){
    om_query = om_query + "ON"
  } else {
    om_query = om_query + "OFF"
  }

  if( Debug ){
    DebugLog.Printf( "Setting offline mode to %t on '%s:%s'\n", om, me.Mysql_host, me.Mysql_port )
  }

  err := me.db.Ping()

  if( err == nil ){
    _, err = me.db.Exec( om_query )
  }

  return err
}

func (me *Instance) Cleanup() error {
  var err error = nil

  if( me.db != nil ){
    err = me.db.Close()
  }

  return err
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"os"
	"path"

	log "github.com/Sirupsen/logrus"

	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/colorstring"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-io/goinp/goinp"
	"github.com/bitrise-tools/codesigndoc/certutil"
	"github.com/bitrise-tools/codesigndoc/osxkeychain"
	"github.com/bitrise-tools/codesigndoc/provprofile"
	"github.com/bitrise-tools/codesigndoc/utils"
	"github.com/bitrise-tools/codesigndoc/xcode"
	"github.com/codegangsta/cli"
)

const (
	confExportOutputDirPath = "./codesigndoc_exports"
)

func printFinished() {
	fmt.Println()
	fmt.Println(colorstring.Green("That's all."))
	fmt.Println("You just have to upload the found code signing files and you'll be good to go!")
}

func scan(c *cli.Context) {
	projectPath := c.String(FileParamKey)
	if projectPath == "" {
		askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `)
   or Workspace (` + colorstring.Green(".xcworkspace") + `) file, the one you usually open in Xcode,
   then hit Enter.

  (Note: if you have a Workspace file you should most likely use that)`
		fmt.Println()
		projpth, err := goinp.AskForString(askText)
		if err != nil {
			log.Fatalf("Failed to read input: %s", err)
		}
		projectPath = projpth
	}
	log.Debugf("projectPath: %s", projectPath)
	xcodeCmd := xcode.CommandModel{
		ProjectFilePath: projectPath,
	}

	schemeToUse := c.String(SchemeParamKey)
	if schemeToUse == "" {
		log.Println("🔦  Scanning Schemes ...")
		schemes, err := xcodeCmd.ScanSchemes()
		if err != nil {
			log.Fatalf("Failed to scan Schemes: %s", err)
		}
		log.Debugf("schemes: %v", schemes)

		fmt.Println()
		selectedScheme, err := goinp.SelectFromStrings("Select the Scheme you usually use in Xcode", schemes)
		if err != nil {
			log.Fatalf("Failed to select Scheme: %s", err)
		}
		log.Debugf("selected scheme: %v", selectedScheme)
		schemeToUse = selectedScheme
	}
	xcodeCmd.Scheme = schemeToUse

	fmt.Println()
	log.Println("🔦  Running an Xcode Archive, to get all the required code signing settings...")
	codeSigningSettings, err := xcodeCmd.ScanCodeSigningSettings()
	if err != nil {
		log.Fatalf("Failed to detect code signing settings: %s", err)
	}
	log.Debugf("codeSigningSettings: %#v", codeSigningSettings)

	fmt.Println()
	utils.Printlnf("=== Required Identities/Certificates (%d) ===", len(codeSigningSettings.Identities))
	for idx, anIdentity := range codeSigningSettings.Identities {
		utils.Printlnf(" * (%d): %s", idx+1, anIdentity.Title)
	}
	fmt.Println("========================================")

	fmt.Println()
	utils.Printlnf("=== Required Provisioning Profiles (%d) ===", len(codeSigningSettings.ProvProfiles))
	for idx, aProvProfile := range codeSigningSettings.ProvProfiles {
		utils.Printlnf(" * (%d): %s (UUID: %s)", idx+1, aProvProfile.Title, aProvProfile.UUID)
	}
	fmt.Println("======================================")

	//
	// --- Code Signing issue checks / report
	//

	if len(codeSigningSettings.Identities) < 1 {
		log.Fatal("No Code Signing Identity detected!")
	}
	if len(codeSigningSettings.Identities) > 1 {
		log.Warning("More than one Code Signing Identity (certificate) is required to sign your app!")
		log.Warning("You should check your settings and make sure a single Identity/Certificate can be used")
		log.Warning(" for Archiving your app!")
	}

	if len(codeSigningSettings.ProvProfiles) < 1 {
		log.Fatal("No Provisioning Profiles detected!")
	}

	//
	// --- Export
	//

	if !c.Bool(AllowExportParamKey) {
		isShouldExport, err := goinp.AskForBool("Do you want to export these files?")
		if err != nil {
			log.Fatalf("Failed to process your input: %s", err)
		}
		if !isShouldExport {
			printFinished()
			return
		}
	} else {
		log.Debug("Allow Export flag was set - doing export without asking")
	}

	fmt.Println()
	log.Println("Exporting the required Identities (Certificates) ...")
	fmt.Println(" You'll most likely see popups (one for each Identity) from Keychain,")
	fmt.Println(" you will have to accept (Allow) those to be able to export the Identity")
	fmt.Println()

	absExportOutputDirPath, err := pathutil.AbsPath(confExportOutputDirPath)
	log.Debugf("absExportOutputDirPath: %s", absExportOutputDirPath)
	if err != nil {
		log.Fatalf("Failed to determin Absolute path of export dir: %s", confExportOutputDirPath)
	}
	if exist, err := pathutil.IsDirExists(absExportOutputDirPath); err != nil {
		log.Fatalf("Failed to determin whether the export directory already exists: %s", err)
	} else if !exist {
		if err := os.Mkdir(absExportOutputDirPath, 0777); err != nil {
			log.Fatalf("Failed to create export output directory at path: %s | error: %s", absExportOutputDirPath, err)
		}
	} else {
		log.Debugf("Export output dir already exists at path: %s", absExportOutputDirPath)
	}

	identityExportRefs := osxkeychain.CreateEmptyCFTypeRefSlice()
	defer osxkeychain.ReleaseRefList(identityExportRefs)

	fmt.Println()
	for _, aIdentity := range codeSigningSettings.Identities {
		log.Infof(" * Exporting Identity: %s", aIdentity.Title)
		foundIdentityRefs, err := osxkeychain.FindIdentity(aIdentity.Title)
		if err != nil {
			log.Fatalf("Failed to Export Identity: %s", err)
		}
		log.Debugf("foundIdentityRefs: %d", len(foundIdentityRefs))
		if len(foundIdentityRefs) < 1 {
			log.Fatalf("No Identity found in Keychain!")
		}

		// check validity
		validIdentityRefs := osxkeychain.CreateEmptyCFTypeRefSlice()
		for _, aIdentityRef := range foundIdentityRefs {
			cert, err := osxkeychain.GetCertificateDataFromIdentityRef(aIdentityRef)
			if err != nil {
				log.Fatalf("Failed to read certificate data: %s", err)
			}

			if err := certutil.CheckCertificateValidity(cert); err != nil {
				log.Warningf("Certificate is not valid, skipping: %s", err)
				continue
			}

			validIdentityRefs = append(validIdentityRefs, aIdentityRef)
		}

		if len(validIdentityRefs) < 1 {
			log.Fatalf("Identity found found in Keychain, but no Valid identity found!")
		}
		if len(validIdentityRefs) > 1 {
			log.Warning("Multiple matching Identities found in Keychain! Most likely you have duplicate identity in separate Keychains, like one in System.keychain and one in your Login.keychain,")
			log.Warning(" or you have revoked versions of the Certificate too.")
		}
		identityExportRefs = append(identityExportRefs, validIdentityRefs...)
	}

	if err := osxkeychain.ExportFromKeychain(identityExportRefs, path.Join(absExportOutputDirPath, "Identities.p12")); err != nil {
		log.Fatalf("Failed to export from Keychain: %s", err)
	}

	fmt.Println()
	for _, aProvProfile := range codeSigningSettings.ProvProfiles {
		log.Infof(" * Exporting Provisioning Profile: %s (UUID: %s)", aProvProfile.Title, aProvProfile.UUID)
		filePth, err := provprofile.FindProvProfileFile(aProvProfile)
		if err != nil {
			log.Fatalf("Failed to find Provisioning Profile: %s", err)
		}
		log.Infof("  File found at: %s", filePth)

		if err := cmdex.RunCommand("cp", filePth, absExportOutputDirPath+"/"); err != nil {
			log.Fatalf("Failed to copy the Provisioning Profile into the export directory: %s", err)
		}

		// if err := provprofile.PrintFileInfo(filePth); err != nil {
		// 	log.Fatalf("Err: %s", err)
		// }
	}

	fmt.Println()
	fmt.Printf(colorstring.Green("Exports finished")+" you can find the exported files at: %s\n", absExportOutputDirPath)
	if err := cmdex.RunCommand("open", absExportOutputDirPath); err != nil {
		log.Errorf("Failed to open the export directory in Finder: %s", absExportOutputDirPath)
	}
	fmt.Println("Opened the directory in Finder.")
	fmt.Println()

	printFinished()
}
<commit_msg>better highlight for more important Warning lines<commit_after>package cli

import (
	"fmt"
	"os"
	"path"

	log "github.com/Sirupsen/logrus"

	"github.com/bitrise-io/go-utils/cmdex"
	"github.com/bitrise-io/go-utils/colorstring"
	"github.com/bitrise-io/go-utils/pathutil"
	"github.com/bitrise-io/goinp/goinp"
	"github.com/bitrise-tools/codesigndoc/certutil"
	"github.com/bitrise-tools/codesigndoc/osxkeychain"
	"github.com/bitrise-tools/codesigndoc/provprofile"
	"github.com/bitrise-tools/codesigndoc/utils"
	"github.com/bitrise-tools/codesigndoc/xcode"
	"github.com/codegangsta/cli"
)

const (
	confExportOutputDirPath = "./codesigndoc_exports"
)

func printFinished() {
	fmt.Println()
	fmt.Println(colorstring.Green("That's all."))
	fmt.Println("You just have to upload the found code signing files and you'll be good to go!")
}

func scan(c *cli.Context) {
	projectPath := c.String(FileParamKey)
	if projectPath == "" {
		askText := `Please drag-and-drop your Xcode Project (` + colorstring.Green(".xcodeproj") + `)
   or Workspace (` + colorstring.Green(".xcworkspace") + `) file, the one you usually open in Xcode,
   then hit Enter.

  (Note: if you have a Workspace file you should most likely use that)`
		fmt.Println()
		projpth, err := goinp.AskForString(askText)
		if err != nil {
			log.Fatalf("Failed to read input: %s", err)
		}
		projectPath = projpth
	}
	log.Debugf("projectPath: %s", projectPath)
	xcodeCmd := xcode.CommandModel{
		ProjectFilePath: projectPath,
	}

	schemeToUse := c.String(SchemeParamKey)
	if schemeToUse == "" {
		log.Println("🔦  Scanning Schemes ...")
		schemes, err := xcodeCmd.ScanSchemes()
		if err != nil {
			log.Fatalf("Failed to scan Schemes: %s", err)
		}
		log.Debugf("schemes: %v", schemes)

		fmt.Println()
		selectedScheme, err := goinp.SelectFromStrings("Select the Scheme you usually use in Xcode", schemes)
		if err != nil {
			log.Fatalf("Failed to select Scheme: %s", err)
		}
		log.Debugf("selected scheme: %v", selectedScheme)
		schemeToUse = selectedScheme
	}
	xcodeCmd.Scheme = schemeToUse

	fmt.Println()
	log.Println("🔦  Running an Xcode Archive, to get all the required code signing settings...")
	codeSigningSettings, err := xcodeCmd.ScanCodeSigningSettings()
	if err != nil {
		log.Fatalf("Failed to detect code signing settings: %s", err)
	}
	log.Debugf("codeSigningSettings: %#v", codeSigningSettings)

	fmt.Println()
	utils.Printlnf("=== Required Identities/Certificates (%d) ===", len(codeSigningSettings.Identities))
	for idx, anIdentity := range codeSigningSettings.Identities {
		utils.Printlnf(" * (%d): %s", idx+1, anIdentity.Title)
	}
	fmt.Println("========================================")

	fmt.Println()
	utils.Printlnf("=== Required Provisioning Profiles (%d) ===", len(codeSigningSettings.ProvProfiles))
	for idx, aProvProfile := range codeSigningSettings.ProvProfiles {
		utils.Printlnf(" * (%d): %s (UUID: %s)", idx+1, aProvProfile.Title, aProvProfile.UUID)
	}
	fmt.Println("======================================")

	//
	// --- Code Signing issue checks / report
	//

	if len(codeSigningSettings.Identities) < 1 {
		log.Fatal("No Code Signing Identity detected!")
	}
	if len(codeSigningSettings.Identities) > 1 {
		log.Warning(colorstring.Yellow("More than one Code Signing Identity (certificate) is required to sign your app!"))
		log.Warning("You should check your settings and make sure a single Identity/Certificate can be used")
		log.Warning(" for Archiving your app!")
	}

	if len(codeSigningSettings.ProvProfiles) < 1 {
		log.Fatal("No Provisioning Profiles detected!")
	}

	//
	// --- Export
	//

	if !c.Bool(AllowExportParamKey) {
		isShouldExport, err := goinp.AskForBool("Do you want to export these files?")
		if err != nil {
			log.Fatalf("Failed to process your input: %s", err)
		}
		if !isShouldExport {
			printFinished()
			return
		}
	} else {
		log.Debug("Allow Export flag was set - doing export without asking")
	}

	fmt.Println()
	log.Println("Exporting the required Identities (Certificates) ...")
	fmt.Println(" You'll most likely see popups (one for each Identity) from Keychain,")
	fmt.Println(" you will have to accept (Allow) those to be able to export the Identity")
	fmt.Println()

	absExportOutputDirPath, err := pathutil.AbsPath(confExportOutputDirPath)
	log.Debugf("absExportOutputDirPath: %s", absExportOutputDirPath)
	if err != nil {
		log.Fatalf("Failed to determin Absolute path of export dir: %s", confExportOutputDirPath)
	}
	if exist, err := pathutil.IsDirExists(absExportOutputDirPath); err != nil {
		log.Fatalf("Failed to determin whether the export directory already exists: %s", err)
	} else if !exist {
		if err := os.Mkdir(absExportOutputDirPath, 0777); err != nil {
			log.Fatalf("Failed to create export output directory at path: %s | error: %s", absExportOutputDirPath, err)
		}
	} else {
		log.Debugf("Export output dir already exists at path: %s", absExportOutputDirPath)
	}

	identityExportRefs := osxkeychain.CreateEmptyCFTypeRefSlice()
	defer osxkeychain.ReleaseRefList(identityExportRefs)

	fmt.Println()
	for _, aIdentity := range codeSigningSettings.Identities {
		log.Infof(" * Exporting Identity: %s", aIdentity.Title)
		foundIdentityRefs, err := osxkeychain.FindIdentity(aIdentity.Title)
		if err != nil {
			log.Fatalf("Failed to Export Identity: %s", err)
		}
		log.Debugf("foundIdentityRefs: %d", len(foundIdentityRefs))
		if len(foundIdentityRefs) < 1 {
			log.Fatalf("No Identity found in Keychain!")
		}

		// check validity
		validIdentityRefs := osxkeychain.CreateEmptyCFTypeRefSlice()
		for _, aIdentityRef := range foundIdentityRefs {
			cert, err := osxkeychain.GetCertificateDataFromIdentityRef(aIdentityRef)
			if err != nil {
				log.Fatalf("Failed to read certificate data: %s", err)
			}

			if err := certutil.CheckCertificateValidity(cert); err != nil {
				log.Warning(colorstring.Yellowf("Certificate is not valid, skipping: %s", err))
				continue
			}

			validIdentityRefs = append(validIdentityRefs, aIdentityRef)
		}

		if len(validIdentityRefs) < 1 {
			log.Fatalf("Identity found found in Keychain, but no Valid identity found!")
		}
		if len(validIdentityRefs) > 1 {
			log.Warning(colorstring.Yellow("Multiple matching Identities found in Keychain! Most likely you have duplicate identity in separate Keychains, like one in System.keychain and one in your Login.keychain,"))
			log.Warning(colorstring.Yellow(" or you have revoked versions of the Certificate too."))
		}
		identityExportRefs = append(identityExportRefs, validIdentityRefs...)
	}

	if err := osxkeychain.ExportFromKeychain(identityExportRefs, path.Join(absExportOutputDirPath, "Identities.p12")); err != nil {
		log.Fatalf("Failed to export from Keychain: %s", err)
	}

	fmt.Println()
	for _, aProvProfile := range codeSigningSettings.ProvProfiles {
		log.Infof(" * Exporting Provisioning Profile: %s (UUID: %s)", aProvProfile.Title, aProvProfile.UUID)
		filePth, err := provprofile.FindProvProfileFile(aProvProfile)
		if err != nil {
			log.Fatalf("Failed to find Provisioning Profile: %s", err)
		}
		log.Infof("  File found at: %s", filePth)

		if err := cmdex.RunCommand("cp", filePth, absExportOutputDirPath+"/"); err != nil {
			log.Fatalf("Failed to copy the Provisioning Profile into the export directory: %s", err)
		}

		// if err := provprofile.PrintFileInfo(filePth); err != nil {
		// 	log.Fatalf("Err: %s", err)
		// }
	}

	fmt.Println()
	fmt.Printf(colorstring.Green("Exports finished")+" you can find the exported files at: %s\n", absExportOutputDirPath)
	if err := cmdex.RunCommand("open", absExportOutputDirPath); err != nil {
		log.Errorf("Failed to open the export directory in Finder: %s", absExportOutputDirPath)
	}
	fmt.Println("Opened the directory in Finder.")
	fmt.Println()

	printFinished()
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"fmt"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/goph/emperror"
	"github.com/goph/fxt"
	"github.com/goph/fxt/debug"
	"github.com/goph/fxt/errors"
	fxlog "github.com/goph/fxt/log"
	"github.com/goph/healthz"
	"go.uber.org/fx"
)

func main() {
	status := healthz.NewStatusChecker(healthz.Healthy)
	var ext struct {
		Config       *Config
		Closer       fxt.Closer
		Logger       log.Logger
		ErrorHandler emperror.Handler

		DebugErr debug.Err
	}

	app := fx.New(
		fx.NopLogger,
		fxt.Bootstrap,
		fx.Provide(
			NewConfig,

			// Log and error handling
			NewLoggerConfig,
			fxlog.NewLogger,
			errors.NewHandler,

			// Debug server
			NewDebugConfig,
			debug.NewServer,
			debug.NewHealthCollector,
		),
		fx.Invoke(func(collector healthz.Collector) {
			collector.RegisterChecker(healthz.ReadinessCheck, status)
		}),
		fx.Extract(&ext),
	)

	err := app.Err()
	if err != nil {
		panic(err)
	}

	// Close resources even when there is an error
	defer ext.Closer.Close()

	// Register error handler to recover from panics
	defer emperror.HandleRecover(ext.ErrorHandler)

	level.Info(ext.Logger).Log(
		"msg", fmt.Sprintf("starting %s", FriendlyServiceName),
		"version", Version,
		"commit_hash", CommitHash,
		"build_date", BuildDate,
	)

	err = app.Start(context.Background())
	if err != nil {
		// Try gracefully stopping already started resources
		app.Stop(context.Background())

		panic(err)
	}

	select {
	case sig := <-app.Done():
		level.Info(ext.Logger).Log("msg", fmt.Sprintf("captured %v signal", sig))

	case err := <-ext.DebugErr:
		if err != nil {
			err = emperror.WithStack(emperror.WithMessage(err, "debug server crashed"))
			ext.ErrorHandler.Handle(err)
		}
	}

	status.SetStatus(healthz.Unhealthy)

	ctx, cancel := context.WithTimeout(context.Background(), ext.Config.ShutdownTimeout)
	defer cancel()

	err = app.Stop(ctx)
	emperror.HandleIfErr(ext.ErrorHandler, err)
}
<commit_msg>Apply timeout when shutting down as a result of start up error<commit_after>package main

import (
	"context"
	"fmt"

	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/goph/emperror"
	"github.com/goph/fxt"
	"github.com/goph/fxt/debug"
	"github.com/goph/fxt/errors"
	fxlog "github.com/goph/fxt/log"
	"github.com/goph/healthz"
	"go.uber.org/fx"
)

func main() {
	status := healthz.NewStatusChecker(healthz.Healthy)
	var ext struct {
		Config       *Config
		Closer       fxt.Closer
		Logger       log.Logger
		ErrorHandler emperror.Handler

		DebugErr debug.Err
	}

	app := fx.New(
		fx.NopLogger,
		fxt.Bootstrap,
		fx.Provide(
			NewConfig,

			// Log and error handling
			NewLoggerConfig,
			fxlog.NewLogger,
			errors.NewHandler,

			// Debug server
			NewDebugConfig,
			debug.NewServer,
			debug.NewHealthCollector,
		),
		fx.Invoke(func(collector healthz.Collector) {
			collector.RegisterChecker(healthz.ReadinessCheck, status)
		}),
		fx.Extract(&ext),
	)

	err := app.Err()
	if err != nil {
		panic(err)
	}

	// Close resources even when there is an error
	defer ext.Closer.Close()

	// Register error handler to recover from panics
	defer emperror.HandleRecover(ext.ErrorHandler)

	level.Info(ext.Logger).Log(
		"msg", fmt.Sprintf("starting %s", FriendlyServiceName),
		"version", Version,
		"commit_hash", CommitHash,
		"build_date", BuildDate,
	)

	err = app.Start(context.Background())
	if err != nil {
		// Try gracefully stopping already started resources
		ctx, cancel := context.WithTimeout(context.Background(), ext.Config.ShutdownTimeout)
		defer cancel()

		app.Stop(ctx)

		panic(err)
	}

	select {
	case sig := <-app.Done():
		level.Info(ext.Logger).Log("msg", fmt.Sprintf("captured %v signal", sig))

	case err := <-ext.DebugErr:
		if err != nil {
			err = emperror.WithStack(emperror.WithMessage(err, "debug server crashed"))
			ext.ErrorHandler.Handle(err)
		}
	}

	status.SetStatus(healthz.Unhealthy)

	ctx, cancel := context.WithTimeout(context.Background(), ext.Config.ShutdownTimeout)
	defer cancel()

	err = app.Stop(ctx)
	emperror.HandleIfErr(ext.ErrorHandler, err)
}
<|endoftext|>


<commit_before>/*
 * MinIO Client (C) 2014-2019 MinIO, Inc.
 *
 * 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 cmd

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/probe"
	"github.com/minio/minio/pkg/console"
	"github.com/minio/minio/pkg/words"
	"github.com/pkg/profile"

	completeinstall "github.com/posener/complete/cmd/install"
)

var (
	// global flags for mc.
	mcFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "autocompletion",
			Usage: "install auto-completion for your shell",
		},
	}
)

// Help template for mc
var mcHelpTemplate = `NAME:
  {{.Name}} - {{.Usage}}

USAGE:
  {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]

COMMANDS:
  {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
  {{end}}{{if .VisibleFlags}}
GLOBAL FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}{{end}}
TIP:
  Use '{{.Name}} --autocompletion' to enable shell autocompletion

VERSION:
  ` + ReleaseTag +
	`{{ "\n"}}{{range $key, $value := ExtraInfo}}
{{$key}}:
  {{$value}}
{{end}}`

// Main starts mc application
func Main(args []string) {
	if len(args) > 1 {
		switch args[1] {
		case "mc", filepath.Base(args[0]):
			mainComplete()
			return
		}
	}

	// Enable profiling supported modes are [cpu, mem, block].
	// ``MC_PROFILER`` supported options are [cpu, mem, block].
	switch os.Getenv("MC_PROFILER") {
	case "cpu":
		defer profile.Start(profile.CPUProfile, profile.ProfilePath(mustGetProfileDir())).Stop()
	case "mem":
		defer profile.Start(profile.MemProfile, profile.ProfilePath(mustGetProfileDir())).Stop()
	case "block":
		defer profile.Start(profile.BlockProfile, profile.ProfilePath(mustGetProfileDir())).Stop()
	}

	probe.Init() // Set project's root source path.
	probe.SetAppInfo("Release-Tag", ReleaseTag)
	probe.SetAppInfo("Commit", ShortCommitID)

	// Fetch terminal size, if not available, automatically
	// set globalQuiet to true.
	if w, e := pb.GetTerminalWidth(); e != nil {
		globalQuiet = true
	} else {
		globalTermWidth = w
	}

	// Set the mc app name.
	appName := filepath.Base(args[0])
	if runtime.GOOS == "windows" && strings.HasSuffix(strings.ToLower(appName), ".exe") {
		// Trim ".exe" from Windows executable.
		appName = appName[:strings.LastIndex(appName, ".")]
	}

	// Monitor OS exit signals and cancel the global context in such case
	go trapSignals(os.Interrupt, syscall.SIGTERM, syscall.SIGKILL)

	// Run the app - exit on error.
	if err := registerApp(appName).Run(args); err != nil {
		os.Exit(1)
	}
}

// Function invoked when invalid command is passed.
func commandNotFound(ctx *cli.Context, command string) {
	msg := fmt.Sprintf("`%s` is not a mc command. See `mc --help`.", command)
	closestCommands := findClosestCommands(command)
	if len(closestCommands) > 0 {
		msg += "\n\nDid you mean one of these?\n"
		if len(closestCommands) == 1 {
			cmd := closestCommands[0]
			msg += fmt.Sprintf("        `%s`", cmd)
		} else {
			for _, cmd := range closestCommands {
				msg += fmt.Sprintf("        `%s`\n", cmd)
			}
		}
	}
	fatalIf(errDummy().Trace(), msg)
}

// Check for sane config environment early on and gracefully report.
func checkConfig() {
	// Refresh the config once.
	loadMcConfig = loadMcConfigFactory()
	// Ensures config file is sane.
	config, err := loadMcConfig()
	// Verify if the path is accesible before validating the config
	fatalIf(err.Trace(mustGetMcConfigPath()), "Unable to access configuration file.")

	// Validate and print error messges
	ok, errMsgs := validateConfigFile(config)
	if !ok {
		var errorMsg bytes.Buffer
		for index, errMsg := range errMsgs {
			// Print atmost 10 errors
			if index > 10 {
				break
			}
			errorMsg.WriteString(errMsg + "\n")
		}
		console.Fatal(errorMsg.String())
	}
}

func migrate() {
	// Fix broken config files if any.
	fixConfig()

	// Migrate config files if any.
	migrateConfig()

	// Migrate session files if any.
	migrateSession()

	// Migrate shared urls if any.
	migrateShare()
}

// Get os/arch/platform specific information.
// Returns a map of current os/arch/platform/memstats.
func getSystemData() map[string]string {
	host, e := os.Hostname()
	fatalIf(probe.NewError(e), "Unable to determine the hostname.")

	memstats := &runtime.MemStats{}
	runtime.ReadMemStats(memstats)
	mem := fmt.Sprintf("Used: %s | Allocated: %s | UsedHeap: %s | AllocatedHeap: %s",
		pb.Format(int64(memstats.Alloc)).To(pb.U_BYTES),
		pb.Format(int64(memstats.TotalAlloc)).To(pb.U_BYTES),
		pb.Format(int64(memstats.HeapAlloc)).To(pb.U_BYTES),
		pb.Format(int64(memstats.HeapSys)).To(pb.U_BYTES))
	platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s", host, runtime.GOOS, runtime.GOARCH)
	goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU()))
	return map[string]string{
		"PLATFORM": platform,
		"RUNTIME":  goruntime,
		"MEM":      mem,
	}
}

// initMC - initialize 'mc'.
func initMC() {
	// Check if mc config exists.
	if !isMcConfigExists() {
		err := saveMcConfig(newMcConfig())
		fatalIf(err.Trace(), "Unable to save new mc config.")

		if !globalQuiet && !globalJSON {
			console.Infoln("Configuration written to `" + mustGetMcConfigPath() + "`. Please update your access credentials.")
		}
	}

	// Check if mc session directory exists.
	if !isSessionDirExists() {
		fatalIf(createSessionDir().Trace(), "Unable to create session config directory.")
	}

	// Check if mc share directory exists.
	if !isShareDirExists() {
		initShareConfig()
	}

	// Check if certs dir exists
	if !isCertsDirExists() {
		fatalIf(createCertsDir().Trace(), "Unable to create `CAs` directory.")
	}

	// Check if CAs dir exists
	if !isCAsDirExists() {
		fatalIf(createCAsDir().Trace(), "Unable to create `CAs` directory.")
	}

	// Load all authority certificates present in CAs dir
	loadRootCAs()

}

func installAutoCompletion() {
	if runtime.GOOS == "windows" {
		console.Infoln("autocompletion feature is not available for this operating system")
		return
	}

	if completeinstall.IsInstalled(filepath.Base(os.Args[0])) || completeinstall.IsInstalled("mc") {
		console.Infoln("autocompletion is already enabled in your '$SHELLRC'")
		return
	}

	err := completeinstall.Install(filepath.Base(os.Args[0]))
	if err != nil {
		fatalIf(probe.NewError(err), "Unable to install auto-completion.")
	} else {
		console.Infoln("enabled autocompletion in '$SHELLRC'. Please restart your shell.")
	}
}

func registerBefore(ctx *cli.Context) error {
	// Set the config directory.
	setMcConfigDir(ctx.GlobalString("config-dir"))

	// Migrate any old version of config / state files to newer format.
	migrate()

	// Set global flags.
	setGlobalsFromContext(ctx)

	// Initialize default config files.
	initMC()

	// Check if config can be read.
	checkConfig()

	return nil
}

// findClosestCommands to match a given string with commands trie tree.
func findClosestCommands(command string) []string {
	var closestCommands []string
	for _, value := range commandsTree.PrefixMatch(command) {
		closestCommands = append(closestCommands, value.(string))
	}
	sort.Strings(closestCommands)
	// Suggest other close commands - allow missed, wrongly added and even transposed characters
	for _, value := range commandsTree.Walk(commandsTree.Root()) {
		if sort.SearchStrings(closestCommands, value.(string)) < len(closestCommands) {
			continue
		}
		// 2 is arbitrary and represents the max allowed number of typed errors
		if words.DamerauLevenshteinDistance(command, value.(string)) < 2 {
			closestCommands = append(closestCommands, value.(string))
		}
	}
	return closestCommands
}

// Check for updates and print a notification message
func checkUpdate(ctx *cli.Context) {
	// Do not print update messages, if quiet flag is set.
	if ctx.Bool("quiet") || ctx.GlobalBool("quiet") {
		// Its OK to ignore any errors during doUpdate() here.
		if updateMsg, _, currentReleaseTime, latestReleaseTime, err := getUpdateInfo(2 * time.Second); err == nil {
			printMsg(updateMessage{
				Status:  "success",
				Message: updateMsg,
			})
		} else {
			printMsg(updateMessage{
				Status:  "success",
				Message: prepareUpdateMessage("Run `mc update`", latestReleaseTime.Sub(currentReleaseTime)),
			})
		}
	}
}

var appCmds = []cli.Command{
	lsCmd,
	mbCmd,
	rbCmd,
	cpCmd,
	mirrorCmd,
	catCmd,
	headCmd,
	pipeCmd,
	shareCmd,
	findCmd,
	sqlCmd,
	statCmd,
	mvCmd,
	treeCmd,
	duCmd,
	lockCmd,
	retentionCmd,
	legalHoldCmd,
	diffCmd,
	rmCmd,
	eventCmd,
	ilmCmd,
	watchCmd,
	policyCmd,
	tagCmd,
	adminCmd,
	configCmd,
	updateCmd,
}

func registerApp(name string) *cli.App {
	for _, cmd := range appCmds {
		registerCmd(cmd)
	}

	cli.HelpFlag = cli.BoolFlag{
		Name:  "help, h",
		Usage: "show help",
	}

	app := cli.NewApp()
	app.Name = name
	app.Action = func(ctx *cli.Context) {
		if strings.HasPrefix(ReleaseTag, "RELEASE.") {
			// Check for new updates from dl.min.io.
			checkUpdate(ctx)
		}

		if ctx.Bool("autocompletion") || ctx.GlobalBool("autocompletion") {
			// Install shell completions
			installAutoCompletion()
			return
		}

		cli.ShowAppHelp(ctx)
	}

	app.Before = registerBefore
	app.ExtraInfo = func() map[string]string {
		if globalDebug {
			return getSystemData()
		}
		return make(map[string]string)
	}

	app.HideHelpCommand = true
	app.Usage = "MinIO Client for cloud storage and filesystems."
	app.Commands = commands
	app.Author = "MinIO, Inc."
	app.Version = ReleaseTag
	app.Flags = append(mcFlags, globalFlags...)
	app.CustomAppHelpTemplate = mcHelpTemplate
	app.CommandNotFound = commandNotFound // handler function declared above.
	app.EnableBashCompletion = true

	return app
}

// mustGetProfilePath must get location that the profile will be written to.
func mustGetProfileDir() string {
	return filepath.Join(mustGetMcConfigDir(), globalProfileDir)
}
<commit_msg>Return 1 as exit code when the cmd is unknown (#3249)<commit_after>/*
 * MinIO Client (C) 2014-2019 MinIO, Inc.
 *
 * 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 cmd

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/cheggaaa/pb"
	"github.com/minio/cli"
	"github.com/minio/mc/pkg/probe"
	"github.com/minio/minio/pkg/console"
	"github.com/minio/minio/pkg/words"
	"github.com/pkg/profile"

	completeinstall "github.com/posener/complete/cmd/install"
)

var (
	// global flags for mc.
	mcFlags = []cli.Flag{
		cli.BoolFlag{
			Name:  "autocompletion",
			Usage: "install auto-completion for your shell",
		},
	}
)

// Help template for mc
var mcHelpTemplate = `NAME:
  {{.Name}} - {{.Usage}}

USAGE:
  {{.Name}} {{if .VisibleFlags}}[FLAGS] {{end}}COMMAND{{if .VisibleFlags}} [COMMAND FLAGS | -h]{{end}} [ARGUMENTS...]

COMMANDS:
  {{range .VisibleCommands}}{{join .Names ", "}}{{ "\t" }}{{.Usage}}
  {{end}}{{if .VisibleFlags}}
GLOBAL FLAGS:
  {{range .VisibleFlags}}{{.}}
  {{end}}{{end}}
TIP:
  Use '{{.Name}} --autocompletion' to enable shell autocompletion

VERSION:
  ` + ReleaseTag +
	`{{ "\n"}}{{range $key, $value := ExtraInfo}}
{{$key}}:
  {{$value}}
{{end}}`

// Main starts mc application
func Main(args []string) {
	if len(args) > 1 {
		switch args[1] {
		case "mc", filepath.Base(args[0]):
			mainComplete()
			return
		}
	}

	// Enable profiling supported modes are [cpu, mem, block].
	// ``MC_PROFILER`` supported options are [cpu, mem, block].
	switch os.Getenv("MC_PROFILER") {
	case "cpu":
		defer profile.Start(profile.CPUProfile, profile.ProfilePath(mustGetProfileDir())).Stop()
	case "mem":
		defer profile.Start(profile.MemProfile, profile.ProfilePath(mustGetProfileDir())).Stop()
	case "block":
		defer profile.Start(profile.BlockProfile, profile.ProfilePath(mustGetProfileDir())).Stop()
	}

	probe.Init() // Set project's root source path.
	probe.SetAppInfo("Release-Tag", ReleaseTag)
	probe.SetAppInfo("Commit", ShortCommitID)

	// Fetch terminal size, if not available, automatically
	// set globalQuiet to true.
	if w, e := pb.GetTerminalWidth(); e != nil {
		globalQuiet = true
	} else {
		globalTermWidth = w
	}

	// Set the mc app name.
	appName := filepath.Base(args[0])
	if runtime.GOOS == "windows" && strings.HasSuffix(strings.ToLower(appName), ".exe") {
		// Trim ".exe" from Windows executable.
		appName = appName[:strings.LastIndex(appName, ".")]
	}

	// Monitor OS exit signals and cancel the global context in such case
	go trapSignals(os.Interrupt, syscall.SIGTERM, syscall.SIGKILL)

	// Run the app - exit on error.
	if err := registerApp(appName).Run(args); err != nil {
		os.Exit(1)
	}
}

// Function invoked when invalid command is passed.
func commandNotFound(ctx *cli.Context, command string) {
	msg := fmt.Sprintf("`%s` is not a mc command. See `mc --help`.", command)
	closestCommands := findClosestCommands(command)
	if len(closestCommands) > 0 {
		msg += "\n\nDid you mean one of these?\n"
		if len(closestCommands) == 1 {
			cmd := closestCommands[0]
			msg += fmt.Sprintf("        `%s`", cmd)
		} else {
			for _, cmd := range closestCommands {
				msg += fmt.Sprintf("        `%s`\n", cmd)
			}
		}
	}
	fatalIf(errDummy().Trace(), msg)
}

// Check for sane config environment early on and gracefully report.
func checkConfig() {
	// Refresh the config once.
	loadMcConfig = loadMcConfigFactory()
	// Ensures config file is sane.
	config, err := loadMcConfig()
	// Verify if the path is accesible before validating the config
	fatalIf(err.Trace(mustGetMcConfigPath()), "Unable to access configuration file.")

	// Validate and print error messges
	ok, errMsgs := validateConfigFile(config)
	if !ok {
		var errorMsg bytes.Buffer
		for index, errMsg := range errMsgs {
			// Print atmost 10 errors
			if index > 10 {
				break
			}
			errorMsg.WriteString(errMsg + "\n")
		}
		console.Fatal(errorMsg.String())
	}
}

func migrate() {
	// Fix broken config files if any.
	fixConfig()

	// Migrate config files if any.
	migrateConfig()

	// Migrate session files if any.
	migrateSession()

	// Migrate shared urls if any.
	migrateShare()
}

// Get os/arch/platform specific information.
// Returns a map of current os/arch/platform/memstats.
func getSystemData() map[string]string {
	host, e := os.Hostname()
	fatalIf(probe.NewError(e), "Unable to determine the hostname.")

	memstats := &runtime.MemStats{}
	runtime.ReadMemStats(memstats)
	mem := fmt.Sprintf("Used: %s | Allocated: %s | UsedHeap: %s | AllocatedHeap: %s",
		pb.Format(int64(memstats.Alloc)).To(pb.U_BYTES),
		pb.Format(int64(memstats.TotalAlloc)).To(pb.U_BYTES),
		pb.Format(int64(memstats.HeapAlloc)).To(pb.U_BYTES),
		pb.Format(int64(memstats.HeapSys)).To(pb.U_BYTES))
	platform := fmt.Sprintf("Host: %s | OS: %s | Arch: %s", host, runtime.GOOS, runtime.GOARCH)
	goruntime := fmt.Sprintf("Version: %s | CPUs: %s", runtime.Version(), strconv.Itoa(runtime.NumCPU()))
	return map[string]string{
		"PLATFORM": platform,
		"RUNTIME":  goruntime,
		"MEM":      mem,
	}
}

// initMC - initialize 'mc'.
func initMC() {
	// Check if mc config exists.
	if !isMcConfigExists() {
		err := saveMcConfig(newMcConfig())
		fatalIf(err.Trace(), "Unable to save new mc config.")

		if !globalQuiet && !globalJSON {
			console.Infoln("Configuration written to `" + mustGetMcConfigPath() + "`. Please update your access credentials.")
		}
	}

	// Check if mc session directory exists.
	if !isSessionDirExists() {
		fatalIf(createSessionDir().Trace(), "Unable to create session config directory.")
	}

	// Check if mc share directory exists.
	if !isShareDirExists() {
		initShareConfig()
	}

	// Check if certs dir exists
	if !isCertsDirExists() {
		fatalIf(createCertsDir().Trace(), "Unable to create `CAs` directory.")
	}

	// Check if CAs dir exists
	if !isCAsDirExists() {
		fatalIf(createCAsDir().Trace(), "Unable to create `CAs` directory.")
	}

	// Load all authority certificates present in CAs dir
	loadRootCAs()

}

func installAutoCompletion() {
	if runtime.GOOS == "windows" {
		console.Infoln("autocompletion feature is not available for this operating system")
		return
	}

	if completeinstall.IsInstalled(filepath.Base(os.Args[0])) || completeinstall.IsInstalled("mc") {
		console.Infoln("autocompletion is already enabled in your '$SHELLRC'")
		return
	}

	err := completeinstall.Install(filepath.Base(os.Args[0]))
	if err != nil {
		fatalIf(probe.NewError(err), "Unable to install auto-completion.")
	} else {
		console.Infoln("enabled autocompletion in '$SHELLRC'. Please restart your shell.")
	}
}

func registerBefore(ctx *cli.Context) error {
	// Set the config directory.
	setMcConfigDir(ctx.GlobalString("config-dir"))

	// Migrate any old version of config / state files to newer format.
	migrate()

	// Set global flags.
	setGlobalsFromContext(ctx)

	// Initialize default config files.
	initMC()

	// Check if config can be read.
	checkConfig()

	return nil
}

// findClosestCommands to match a given string with commands trie tree.
func findClosestCommands(command string) []string {
	var closestCommands []string
	for _, value := range commandsTree.PrefixMatch(command) {
		closestCommands = append(closestCommands, value.(string))
	}
	sort.Strings(closestCommands)
	// Suggest other close commands - allow missed, wrongly added and even transposed characters
	for _, value := range commandsTree.Walk(commandsTree.Root()) {
		if sort.SearchStrings(closestCommands, value.(string)) < len(closestCommands) {
			continue
		}
		// 2 is arbitrary and represents the max allowed number of typed errors
		if words.DamerauLevenshteinDistance(command, value.(string)) < 2 {
			closestCommands = append(closestCommands, value.(string))
		}
	}
	return closestCommands
}

// Check for updates and print a notification message
func checkUpdate(ctx *cli.Context) {
	// Do not print update messages, if quiet flag is set.
	if ctx.Bool("quiet") || ctx.GlobalBool("quiet") {
		// Its OK to ignore any errors during doUpdate() here.
		if updateMsg, _, currentReleaseTime, latestReleaseTime, err := getUpdateInfo(2 * time.Second); err == nil {
			printMsg(updateMessage{
				Status:  "success",
				Message: updateMsg,
			})
		} else {
			printMsg(updateMessage{
				Status:  "success",
				Message: prepareUpdateMessage("Run `mc update`", latestReleaseTime.Sub(currentReleaseTime)),
			})
		}
	}
}

var appCmds = []cli.Command{
	lsCmd,
	mbCmd,
	rbCmd,
	cpCmd,
	mirrorCmd,
	catCmd,
	headCmd,
	pipeCmd,
	shareCmd,
	findCmd,
	sqlCmd,
	statCmd,
	mvCmd,
	treeCmd,
	duCmd,
	lockCmd,
	retentionCmd,
	legalHoldCmd,
	diffCmd,
	rmCmd,
	eventCmd,
	ilmCmd,
	watchCmd,
	policyCmd,
	tagCmd,
	adminCmd,
	configCmd,
	updateCmd,
}

func registerApp(name string) *cli.App {
	for _, cmd := range appCmds {
		registerCmd(cmd)
	}

	cli.HelpFlag = cli.BoolFlag{
		Name:  "help, h",
		Usage: "show help",
	}

	app := cli.NewApp()
	app.Name = name
	app.Action = func(ctx *cli.Context) error {
		if strings.HasPrefix(ReleaseTag, "RELEASE.") {
			// Check for new updates from dl.min.io.
			checkUpdate(ctx)
		}

		if ctx.Bool("autocompletion") || ctx.GlobalBool("autocompletion") {
			// Install shell completions
			installAutoCompletion()
			return nil
		}

		cli.ShowAppHelp(ctx)
		return exitStatus(globalErrorExitStatus)
	}

	app.Before = registerBefore
	app.ExtraInfo = func() map[string]string {
		if globalDebug {
			return getSystemData()
		}
		return make(map[string]string)
	}

	app.HideHelpCommand = true
	app.Usage = "MinIO Client for cloud storage and filesystems."
	app.Commands = commands
	app.Author = "MinIO, Inc."
	app.Version = ReleaseTag
	app.Flags = append(mcFlags, globalFlags...)
	app.CustomAppHelpTemplate = mcHelpTemplate
	app.CommandNotFound = commandNotFound // handler function declared above.
	app.EnableBashCompletion = true

	return app
}

// mustGetProfilePath must get location that the profile will be written to.
func mustGetProfileDir() string {
	return filepath.Join(mustGetMcConfigDir(), globalProfileDir)
}
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// apidoc 是一个 RESTful API 文档生成工具。
package main

import (
	"bytes"
	"context"
	"flag"
	"os"
	"path/filepath"
	"runtime"

	"github.com/issue9/term/colors"
	"golang.org/x/text/language"

	"github.com/caixw/apidoc"
	"github.com/caixw/apidoc/doc"
	"github.com/caixw/apidoc/errors"
	"github.com/caixw/apidoc/internal/lang"
	"github.com/caixw/apidoc/internal/locale"
	"github.com/caixw/apidoc/internal/output"
	"github.com/caixw/apidoc/internal/vars"
)

// 控制台的输出颜色
const (
	infoColor = colors.Green
	warnColor = colors.Cyan
	erroColor = colors.Red
)

// 确保第一时间初始化本地化信息
func init() {
	if err := apidoc.InitLocale(language.Und); err != nil {
		printWarn(err)
		return
	}
}

func main() {
	h := flag.Bool("h", false, locale.Sprintf(locale.FlagHUsage))
	v := flag.Bool("v", false, locale.Sprintf(locale.FlagVUsage))
	g := flag.Bool("g", false, locale.Sprintf(locale.FlagGUsage))
	wd := flag.String("wd", "./", locale.Sprintf(locale.FlagWDUsage))
	languages := flag.Bool("l", false, locale.Sprintf(locale.FlagLanguagesUsage))
	flag.Usage = usage
	flag.Parse()

	switch {
	case *h:
		flag.Usage()
		return
	case *v:
		printVersion()
		return
	case *languages:
		locale.Printf(locale.FlagSupportedLanguages, lang.Langs())
		return
	case *g:
		genConfigFile(*wd)
		return
	}

	parse(*wd)
}

func parse(wd string) {
	cfg, err := loadConfig(wd)
	if err != nil {
		printError(err)
		return
	}

	h := errors.NewHandler(newConsoleHandlerFunc())
	doc, err := doc.Parse(context.Background(), h, cfg.Inputs...)
	if err != nil {
		if ferr, ok := err.(*errors.Error); ok {
			ferr.File = configFilename
			ferr.Field = "inputs." + ferr.Field
		}
		printError(err)
		return
	}

	if err = output.Render(doc, cfg.Output); err != nil {
		if ferr, ok := err.(*errors.Error); ok {
			ferr.File = configFilename
			ferr.Field = "output." + ferr.Field
		}
		printError(err)
	}

	printInfo(locale.Sprintf(locale.Complete, cfg.Output.Path, doc.Elapsed))
}

func usage() {
	buf := new(bytes.Buffer)
	flag.CommandLine.SetOutput(buf)
	flag.PrintDefaults()

	locale.Printf(locale.FlagUsage, vars.Name, buf.String(), vars.RepoURL, vars.OfficialURL)
}

// 根据 wd 所在目录的内容生成一个配置文件，并写入到 wd 目录下的 .apidoc.yaml 中
func genConfigFile(wd string) {
	path := filepath.Join(wd, configFilename)
	if err := generateConfig(wd, path); err != nil {
		printError(err)
		return
	}

	printInfo(locale.Sprintf(locale.FlagConfigWritedSuccess, path))
}

func printVersion() {
	locale.Printf(locale.FlagVersionBuildWith, vars.Name, vars.Version(), runtime.Version())
	locale.Printf(locale.FlagVersionCommitHash, vars.CommitHash())
}

func newConsoleHandlerFunc() errors.HandlerFunc {
	return func(err *errors.Error) {
		switch err.Type {
		case errors.SyntaxError:
			printError(err)
		case errors.SyntaxWarn:
			printWarn(err)
		default:
			printError(err)
		}
	}
}

func print(out *os.File, prefix string, color colors.Color, val interface{}) {
	if out != os.Stderr && out != os.Stdout {
		panic("无效的 out 参数")
	}

	colors.Fprint(out, color, colors.Default, prefix)
	colors.Fprintln(out, colors.Default, colors.Default, val)
}

func printWarn(val interface{}) {
	print(os.Stderr, locale.Sprintf(locale.WarnPrefix), warnColor, val)
}

func printError(val interface{}) {
	print(os.Stderr, locale.Sprintf(locale.ErrorPrefix), erroColor, val)
}

func printInfo(val interface{}) {
	print(os.Stderr, locale.Sprintf(locale.InfoPrefix), infoColor, val)
}
<commit_msg>修正 err.Field 错误<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

// apidoc 是一个 RESTful API 文档生成工具。
package main

import (
	"bytes"
	"context"
	"flag"
	"os"
	"path/filepath"
	"runtime"

	"github.com/issue9/term/colors"
	"golang.org/x/text/language"

	"github.com/caixw/apidoc"
	"github.com/caixw/apidoc/doc"
	"github.com/caixw/apidoc/errors"
	"github.com/caixw/apidoc/internal/lang"
	"github.com/caixw/apidoc/internal/locale"
	"github.com/caixw/apidoc/internal/output"
	"github.com/caixw/apidoc/internal/vars"
)

// 控制台的输出颜色
const (
	infoColor = colors.Green
	warnColor = colors.Cyan
	erroColor = colors.Red
)

// 确保第一时间初始化本地化信息
func init() {
	if err := apidoc.InitLocale(language.Und); err != nil {
		printWarn(err)
		return
	}
}

func main() {
	h := flag.Bool("h", false, locale.Sprintf(locale.FlagHUsage))
	v := flag.Bool("v", false, locale.Sprintf(locale.FlagVUsage))
	g := flag.Bool("g", false, locale.Sprintf(locale.FlagGUsage))
	wd := flag.String("wd", "./", locale.Sprintf(locale.FlagWDUsage))
	languages := flag.Bool("l", false, locale.Sprintf(locale.FlagLanguagesUsage))
	flag.Usage = usage
	flag.Parse()

	switch {
	case *h:
		flag.Usage()
		return
	case *v:
		printVersion()
		return
	case *languages:
		locale.Printf(locale.FlagSupportedLanguages, lang.Langs())
		return
	case *g:
		genConfigFile(*wd)
		return
	}

	parse(*wd)
}

func parse(wd string) {
	cfg, err := loadConfig(wd)
	if err != nil {
		printError(err)
		return
	}

	h := errors.NewHandler(newConsoleHandlerFunc())
	doc, err := doc.Parse(context.Background(), h, cfg.Inputs...)
	if err != nil {
		if ferr, ok := err.(*errors.Error); ok {
			ferr.File = configFilename
		}
		printError(err)
		return
	}

	if err = output.Render(doc, cfg.Output); err != nil {
		if ferr, ok := err.(*errors.Error); ok {
			ferr.File = configFilename
		}
		printError(err)
		return
	}

	printInfo(locale.Sprintf(locale.Complete, cfg.Output.Path, doc.Elapsed))
}

func usage() {
	buf := new(bytes.Buffer)
	flag.CommandLine.SetOutput(buf)
	flag.PrintDefaults()

	locale.Printf(locale.FlagUsage, vars.Name, buf.String(), vars.RepoURL, vars.OfficialURL)
}

// 根据 wd 所在目录的内容生成一个配置文件，并写入到 wd 目录下的 .apidoc.yaml 中
func genConfigFile(wd string) {
	path := filepath.Join(wd, configFilename)
	if err := generateConfig(wd, path); err != nil {
		printError(err)
		return
	}

	printInfo(locale.Sprintf(locale.FlagConfigWritedSuccess, path))
}

func printVersion() {
	locale.Printf(locale.FlagVersionBuildWith, vars.Name, vars.Version(), runtime.Version())
	locale.Printf(locale.FlagVersionCommitHash, vars.CommitHash())
}

func newConsoleHandlerFunc() errors.HandlerFunc {
	return func(err *errors.Error) {
		switch err.Type {
		case errors.SyntaxError:
			printError(err)
		case errors.SyntaxWarn:
			printWarn(err)
		default:
			printError(err)
		}
	}
}

func print(out *os.File, prefix string, color colors.Color, val interface{}) {
	if out != os.Stderr && out != os.Stdout {
		panic("无效的 out 参数")
	}

	colors.Fprint(out, color, colors.Default, prefix)
	colors.Fprintln(out, colors.Default, colors.Default, val)
}

func printWarn(val interface{}) {
	print(os.Stderr, locale.Sprintf(locale.WarnPrefix), warnColor, val)
}

func printError(val interface{}) {
	print(os.Stderr, locale.Sprintf(locale.ErrorPrefix), erroColor, val)
}

func printInfo(val interface{}) {
	print(os.Stderr, locale.Sprintf(locale.InfoPrefix), infoColor, val)
}
<|endoftext|>


<commit_before>/*
Copyright 2016 Skippbox, Ltd.

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 cmd

import (
	"fmt"
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/bitnami-labs/kubewatch/config"
	c "github.com/bitnami-labs/kubewatch/pkg/client"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var cfgFile string

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
	Use:   "kubewatch",
	Short: "A watcher for Kubernetes",
	Long: `
Kubewath: A watcher for Kubernetes

kubewatch is a Kubernetes watcher that could publishes notification
to Slack/hipchat/mattermost/flock channels. It watches the culster
for resource changes and notifies them through webhooks.

supported webhooks:
 - slack
 - hipchat
 - mattermost
 - flock
 - webhook
`,

	Run: func(cmd *cobra.Command, args []string) {
		config := &config.Config{}
		if err := config.Load(); err != nil {
			logrus.Fatal(err)
		}
		config.CheckMissingResourceEnvvars()
		c.Run(config)
	},
}

// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)

	// Disable Help subcommand
	RootCmd.SetHelpCommand(&cobra.Command{
		Use:    "no-help",
		Hidden: true,
	})
	//RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.kubewatch.yaml)")
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" { // enable ability to specify config file via flag
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".kubewatch.yaml") // name of config file (without extension)
	viper.AddConfigPath("$HOME")           // adding home directory as first search path
	viper.AutomaticEnv()                   // read in environment variables that match

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}
<commit_msg>Fix typo<commit_after>/*
Copyright 2016 Skippbox, Ltd.

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 cmd

import (
	"fmt"
	"os"

	"github.com/Sirupsen/logrus"
	"github.com/bitnami-labs/kubewatch/config"
	c "github.com/bitnami-labs/kubewatch/pkg/client"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var cfgFile string

// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
	Use:   "kubewatch",
	Short: "A watcher for Kubernetes",
	Long: `
Kubewath: A watcher for Kubernetes

kubewatch is a Kubernetes watcher that could publishes notification
to Slack/hipchat/mattermost/flock channels. It watches the cluster
for resource changes and notifies them through webhooks.

supported webhooks:
 - slack
 - hipchat
 - mattermost
 - flock
 - webhook
`,

	Run: func(cmd *cobra.Command, args []string) {
		config := &config.Config{}
		if err := config.Load(); err != nil {
			logrus.Fatal(err)
		}
		config.CheckMissingResourceEnvvars()
		c.Run(config)
	},
}

// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
	if err := RootCmd.Execute(); err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
}

func init() {
	cobra.OnInitialize(initConfig)

	// Disable Help subcommand
	RootCmd.SetHelpCommand(&cobra.Command{
		Use:    "no-help",
		Hidden: true,
	})
	//RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.kubewatch.yaml)")
}

// initConfig reads in config file and ENV variables if set.
func initConfig() {
	if cfgFile != "" { // enable ability to specify config file via flag
		viper.SetConfigFile(cfgFile)
	}

	viper.SetConfigName(".kubewatch.yaml") // name of config file (without extension)
	viper.AddConfigPath("$HOME")           // adding home directory as first search path
	viper.AutomaticEnv()                   // read in environment variables that match

	// If a config file is found, read it in.
	if err := viper.ReadInConfig(); err == nil {
		fmt.Println("Using config file:", viper.ConfigFileUsed())
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/dtan4/valec/aws"
	"github.com/dtan4/valec/secret"
	"github.com/fatih/color"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

var (
	yamlExtRegexp = regexp.MustCompile(`\.[yY][aA]?[mM][lL]$`)
)

// syncCmd represents the sync command
var syncCmd = &cobra.Command{
	Use:   "sync SECRETDIR [NAMESPACE]",
	Short: "Synchronize secrets between local file and DynamoDB",
	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return errors.New("Please specify secret directory.")
		}
		dirname := args[0]

		if err := walkDir(dirname, ""); err != nil {
			return errors.Wrapf(err, "Failed to parse directory. dirname=%s", dirname)
		}

		return nil
	},
}

func walkDir(dirname, parentNamespace string) error {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		return errors.Wrapf(err, "Failed to open directory. dirname=%s", dirname)
	}

	for _, file := range files {
		if file.IsDir() {
			subdirname := filepath.Join(dirname, file.Name())

			if err := walkDir(subdirname, file.Name()); err != nil {
				return errors.Wrapf(err, "Failed to parse directory. dirname=%s", subdirname)
			}

			continue
		}

		if strings.HasPrefix(file.Name(), ".") || !yamlExtRegexp.Match([]byte(file.Name())) {
			continue
		}

		filename := filepath.Join(dirname, file.Name())

		if err := syncFile(filename, parentNamespace); err != nil {
			return errors.Wrapf(err, "Failed to synchronize secrets. filename=%s", filename)
		}
	}

	return nil
}

func syncFile(filename, parentNamespace string) error {
	namespaceBase := yamlExtRegexp.ReplaceAllString(filepath.Base(filename), "")

	var namespace string

	if parentNamespace == "" {
		namespace = namespaceBase
	} else {
		namespace = parentNamespace + "/" + namespaceBase
	}

	if noColor {
		fmt.Println(namespace)
	} else {
		color.New(color.Bold).Println(namespace)
	}

	srcSecrets, err := secret.LoadFromYAML(filename)
	if err != nil {
		return errors.Wrapf(err, "Failed to load secrets. filename=%s", filename)
	}

	dstSecrets, err := aws.DynamoDB.ListSecrets(tableName, namespace)
	if err != nil {
		return errors.Wrapf(err, "Failed to retrieve secrets. namespace=%s", namespace)
	}

	added, updated, deleted := srcSecrets.CompareList(dstSecrets)
	red := color.New(color.FgRed)
	green := color.New(color.FgGreen)
	yellow := color.New(color.FgYellow)

	if len(deleted) > 0 {
		fmt.Printf("%  d secrets will be deleted.\n", len(deleted))
		for _, secret := range deleted {
			if noColor {
				fmt.Printf("    - %s\n", secret.Key)
			} else {
				red.Printf("    - %s\n", secret.Key)
			}
		}

		if !dryRun {
			if err := aws.DynamoDB.Delete(tableName, namespace, deleted); err != nil {
				return errors.Wrapf(err, "Failed to delete secrets. namespace=%s", namespace)
			}

			fmt.Printf("  %d secrets were successfully deleted.\n", len(deleted))
		}
	} else {
		fmt.Println("  No secret will be deleted.")
	}

	if len(updated) > 0 {
		fmt.Printf("  %d secrets will be updated.\n", len(updated))
		for _, secret := range updated {
			if noColor {
				fmt.Printf("    + %s\n", secret.Key)
			} else {
				yellow.Printf("    + %s\n", secret.Key)
			}
		}

		if !dryRun {
			if err := aws.DynamoDB.Insert(tableName, namespace, updated); err != nil {
				return errors.Wrapf(err, "Failed to insert secrets. namespace=%s")
			}

			fmt.Printf("  %d secrets were successfully updated.\n", len(updated))
		}
	} else {
		fmt.Println("  No secret will be updated.")
	}

	if len(added) > 0 {
		fmt.Printf("  %d secrets will be added.\n", len(added))
		for _, secret := range added {
			if noColor {
				fmt.Printf("    + %s\n", secret.Key)
			} else {
				green.Printf("    + %s\n", secret.Key)
			}
		}

		if !dryRun {
			if err := aws.DynamoDB.Insert(tableName, namespace, added); err != nil {
				return errors.Wrapf(err, "Failed to insert secrets. namespace=%s")
			}

			fmt.Printf("  %d secrets were successfully added.\n", len(added))
		}
	} else {
		fmt.Println("  No secret will be added.")
	}

	return nil
}

func init() {
	RootCmd.AddCommand(syncCmd)

	syncCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Dry run")
}
<commit_msg>Delete messages if no changes were detected<commit_after>package cmd

import (
	"fmt"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/dtan4/valec/aws"
	"github.com/dtan4/valec/secret"
	"github.com/fatih/color"
	"github.com/pkg/errors"
	"github.com/spf13/cobra"
)

var (
	yamlExtRegexp = regexp.MustCompile(`\.[yY][aA]?[mM][lL]$`)
)

// syncCmd represents the sync command
var syncCmd = &cobra.Command{
	Use:   "sync SECRETDIR [NAMESPACE]",
	Short: "Synchronize secrets between local file and DynamoDB",
	RunE: func(cmd *cobra.Command, args []string) error {
		if len(args) < 1 {
			return errors.New("Please specify secret directory.")
		}
		dirname := args[0]

		if err := walkDir(dirname, ""); err != nil {
			return errors.Wrapf(err, "Failed to parse directory. dirname=%s", dirname)
		}

		return nil
	},
}

func walkDir(dirname, parentNamespace string) error {
	files, err := ioutil.ReadDir(dirname)
	if err != nil {
		return errors.Wrapf(err, "Failed to open directory. dirname=%s", dirname)
	}

	for _, file := range files {
		if file.IsDir() {
			subdirname := filepath.Join(dirname, file.Name())

			if err := walkDir(subdirname, file.Name()); err != nil {
				return errors.Wrapf(err, "Failed to parse directory. dirname=%s", subdirname)
			}

			continue
		}

		if strings.HasPrefix(file.Name(), ".") || !yamlExtRegexp.Match([]byte(file.Name())) {
			continue
		}

		filename := filepath.Join(dirname, file.Name())

		if err := syncFile(filename, parentNamespace); err != nil {
			return errors.Wrapf(err, "Failed to synchronize secrets. filename=%s", filename)
		}
	}

	return nil
}

func syncFile(filename, parentNamespace string) error {
	namespaceBase := yamlExtRegexp.ReplaceAllString(filepath.Base(filename), "")

	var namespace string

	if parentNamespace == "" {
		namespace = namespaceBase
	} else {
		namespace = parentNamespace + "/" + namespaceBase
	}

	if noColor {
		fmt.Println(namespace)
	} else {
		color.New(color.Bold).Println(namespace)
	}

	srcSecrets, err := secret.LoadFromYAML(filename)
	if err != nil {
		return errors.Wrapf(err, "Failed to load secrets. filename=%s", filename)
	}

	dstSecrets, err := aws.DynamoDB.ListSecrets(tableName, namespace)
	if err != nil {
		return errors.Wrapf(err, "Failed to retrieve secrets. namespace=%s", namespace)
	}

	added, updated, deleted := srcSecrets.CompareList(dstSecrets)
	red := color.New(color.FgRed)
	green := color.New(color.FgGreen)
	yellow := color.New(color.FgYellow)

	if len(deleted) > 0 {
		fmt.Printf("%  d secrets will be deleted.\n", len(deleted))
		for _, secret := range deleted {
			if noColor {
				fmt.Printf("    - %s\n", secret.Key)
			} else {
				red.Printf("    - %s\n", secret.Key)
			}
		}

		if !dryRun {
			if err := aws.DynamoDB.Delete(tableName, namespace, deleted); err != nil {
				return errors.Wrapf(err, "Failed to delete secrets. namespace=%s", namespace)
			}

			fmt.Printf("  %d secrets were successfully deleted.\n", len(deleted))
		}
	}

	if len(updated) > 0 {
		fmt.Printf("  %d secrets will be updated.\n", len(updated))
		for _, secret := range updated {
			if noColor {
				fmt.Printf("    + %s\n", secret.Key)
			} else {
				yellow.Printf("    + %s\n", secret.Key)
			}
		}

		if !dryRun {
			if err := aws.DynamoDB.Insert(tableName, namespace, updated); err != nil {
				return errors.Wrapf(err, "Failed to insert secrets. namespace=%s")
			}

			fmt.Printf("  %d secrets were successfully updated.\n", len(updated))
		}
	}

	if len(added) > 0 {
		fmt.Printf("  %d secrets will be added.\n", len(added))
		for _, secret := range added {
			if noColor {
				fmt.Printf("    + %s\n", secret.Key)
			} else {
				green.Printf("    + %s\n", secret.Key)
			}
		}

		if !dryRun {
			if err := aws.DynamoDB.Insert(tableName, namespace, added); err != nil {
				return errors.Wrapf(err, "Failed to insert secrets. namespace=%s")
			}

			fmt.Printf("  %d secrets were successfully added.\n", len(added))
		}
	}

	return nil
}

func init() {
	RootCmd.AddCommand(syncCmd)

	syncCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Dry run")
}
<|endoftext|>


<commit_before>package cms

import (
	"bytes"
	"encoding/asn1"
	"encoding/hex"
	"testing"
)

type marshalCMSTest struct {
	in  interface{}
	out string // Hex string representing DER encoded value
}

var marshalCMSTests = []marshalCMSTest{
	{SignatureValue{0x01}, "040101"},
	{SignatureValue{0x01, 0x02}, "04020102"},
	{EncryptedContent{0x01}, "040101"},
	{EncryptedContent{0x01, 0x02}, "04020102"},
	{EncryptedKey{0x01}, "040101"},
	{SubjectKeyIdentifier{0x01}, "040101"},
	{Digest{0x01}, "040101"},
	{MessageAuthenticationCode{0x01}, "040101"},
	{CMSVersion(1), "020101"},
	{AttCertVersionV1(0), "020100"},
}

func TestMarshalCMS(t *testing.T) {
	for i, test := range marshalCMSTests {
		data, err := asn1.Marshal(test.in)
		if err != nil {
			t.Errorf("#%d failed: %s", i, err)
		}
		out, _ := hex.DecodeString(test.out)
		if !bytes.Equal(out, data) {
			t.Errorf("Test #%d Failed - got: %x expected: %x\n", i+1, data, out)
		}
	}
}
<commit_msg>added MessageAuthenticationCode typedef and unit test<commit_after>package cms

import (
	"bytes"
	"encoding/asn1"
	"encoding/hex"
	"testing"
)

type marshalCMSTest struct {
	in  interface{}
	out string // Hex string representing DER encoded value
}

var marshalCMSTests = []marshalCMSTest{
	{SignatureValue{0x01}, "040101"},
	{SignatureValue{0x01, 0x02}, "04020102"},
	{EncryptedContent{0x01}, "040101"},
	{EncryptedContent{0x01, 0x02}, "04020102"},
	{EncryptedKey{0x01}, "040101"},
	{SubjectKeyIdentifier{0x01}, "040101"},
	{Digest{0x01}, "040101"},
	{MessageAuthenticationCode{0x01}, "040101"},
	{UserKeyingMaterial{0x01}, "040101"},
	{CMSVersion(1), "020101"},
	{AttCertVersionV1(0), "020100"},
}

func TestMarshalCMS(t *testing.T) {
	for i, test := range marshalCMSTests {
		data, err := asn1.Marshal(test.in)
		if err != nil {
			t.Errorf("#%d failed: %s", i, err)
		}
		out, _ := hex.DecodeString(test.out)
		if !bytes.Equal(out, data) {
			t.Errorf("Test #%d Failed - got: %x expected: %x\n", i+1, data, out)
		}
	}
}
<|endoftext|>