github.com/cyverse/go-irodsclient@v0.13.2/irods/fs/ticket.go (about)

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/cyverse/go-irodsclient/irods/common"
    10  	"github.com/cyverse/go-irodsclient/irods/connection"
    11  	"github.com/cyverse/go-irodsclient/irods/message"
    12  	"github.com/cyverse/go-irodsclient/irods/types"
    13  	"github.com/cyverse/go-irodsclient/irods/util"
    14  	"github.com/rs/xid"
    15  	"golang.org/x/xerrors"
    16  )
    17  
    18  // https://github.com/irods/irods_client_s3_ticketbooth/blob/b92e8aaa3127cb56fcb8fef09caa00244bd29ca6/ticket_booth/main.py
    19  // GetTicketForAnonymousAccess returns minimal ticket information for the ticket name string
    20  func GetTicketForAnonymousAccess(conn *connection.IRODSConnection, ticketName string) (*types.IRODSTicketForAnonymousAccess, error) {
    21  	if conn == nil || !conn.IsConnected() {
    22  		return nil, xerrors.Errorf("connection is nil or disconnected")
    23  	}
    24  
    25  	// lock the connection
    26  	conn.Lock()
    27  	defer conn.Unlock()
    28  
    29  	query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, 0, 0, 0)
    30  	query.AddSelect(common.ICAT_COLUMN_TICKET_ID, 1)
    31  	query.AddSelect(common.ICAT_COLUMN_TICKET_TYPE, 1)
    32  	query.AddSelect(common.ICAT_COLUMN_TICKET_COLL_NAME, 1)
    33  	query.AddSelect(common.ICAT_COLUMN_TICKET_EXPIRY_TS, 1)
    34  	// We can't get common.ICAT_COLUMN_TICKET_STRING using query since it's not available for anonymous access
    35  
    36  	condVal := fmt.Sprintf("= '%s'", ticketName)
    37  	query.AddCondition(common.ICAT_COLUMN_TICKET_STRING, condVal)
    38  
    39  	queryResult := message.IRODSMessageQueryResponse{}
    40  	err := conn.Request(query, &queryResult, nil)
    41  	if err != nil {
    42  		return nil, xerrors.Errorf("failed to receive a ticket query result message: %w", err)
    43  	}
    44  
    45  	err = queryResult.CheckError()
    46  	if err != nil {
    47  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
    48  			return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
    49  		}
    50  
    51  		return nil, xerrors.Errorf("received a ticket query error: %w", err)
    52  	}
    53  
    54  	if queryResult.RowCount != 1 {
    55  		// file not found
    56  		return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
    57  	}
    58  
    59  	if queryResult.AttributeCount > len(queryResult.SQLResult) {
    60  		return nil, xerrors.Errorf("failed to receive ticket attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
    61  	}
    62  
    63  	var ticketID int64 = -1
    64  	ticketType := types.TicketTypeRead
    65  	ticketPath := ""
    66  	expirationTime := time.Time{}
    67  
    68  	for idx := 0; idx < queryResult.AttributeCount; idx++ {
    69  		sqlResult := queryResult.SQLResult[idx]
    70  		if len(sqlResult.Values) != queryResult.RowCount {
    71  			return nil, xerrors.Errorf("failed to receive ticket rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
    72  		}
    73  
    74  		value := sqlResult.Values[0]
    75  
    76  		switch sqlResult.AttributeIndex {
    77  		case int(common.ICAT_COLUMN_TICKET_ID):
    78  			cID, err := strconv.ParseInt(value, 10, 64)
    79  			if err != nil {
    80  				return nil, xerrors.Errorf("failed to parse ticket id '%s': %w", value, err)
    81  			}
    82  			ticketID = cID
    83  		case int(common.ICAT_COLUMN_TICKET_TYPE):
    84  			ticketType = types.TicketType(value)
    85  		case int(common.ICAT_COLUMN_TICKET_COLL_NAME):
    86  			ticketPath = value
    87  		case int(common.ICAT_COLUMN_TICKET_EXPIRY_TS):
    88  			if len(strings.TrimSpace(value)) > 0 {
    89  				mT, err := util.GetIRODSDateTime(value)
    90  				if err != nil {
    91  					return nil, xerrors.Errorf("failed to parse expiry time '%s': %w", value, err)
    92  				}
    93  				expirationTime = mT
    94  			}
    95  		default:
    96  			// ignore
    97  		}
    98  	}
    99  
   100  	if ticketID == -1 {
   101  		return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   102  	}
   103  
   104  	return &types.IRODSTicketForAnonymousAccess{
   105  		ID:             ticketID,
   106  		Name:           ticketName,
   107  		Type:           ticketType,
   108  		Path:           ticketPath,
   109  		ExpirationTime: expirationTime,
   110  	}, nil
   111  }
   112  
   113  // GetTicket returns the ticket
   114  func GetTicket(conn *connection.IRODSConnection, ticketName string) (*types.IRODSTicket, error) {
   115  	if conn == nil || !conn.IsConnected() {
   116  		return nil, xerrors.Errorf("connection is nil or disconnected")
   117  	}
   118  
   119  	ticketColl, err := GetTicketForCollections(conn, ticketName)
   120  	if err != nil {
   121  		if !types.IsFileNotFoundError(err) {
   122  			return nil, err
   123  		}
   124  	} else {
   125  		if ticketColl != nil {
   126  			return ticketColl, nil
   127  		}
   128  	}
   129  
   130  	ticketsDataObj, err := GetTicketForDataObjects(conn, ticketName)
   131  	if err != nil {
   132  		if !types.IsFileNotFoundError(err) {
   133  			return nil, err
   134  		}
   135  	} else {
   136  		if ticketsDataObj != nil {
   137  			return ticketsDataObj, nil
   138  		}
   139  	}
   140  
   141  	return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   142  }
   143  
   144  // GetTicketForDataObjects returns ticket information for the ticket name string
   145  func GetTicketForDataObjects(conn *connection.IRODSConnection, ticketName string) (*types.IRODSTicket, error) {
   146  	if conn == nil || !conn.IsConnected() {
   147  		return nil, xerrors.Errorf("connection is nil or disconnected")
   148  	}
   149  
   150  	// lock the connection
   151  	conn.Lock()
   152  	defer conn.Unlock()
   153  
   154  	query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, 0, 0, 0)
   155  	query.AddSelect(common.ICAT_COLUMN_TICKET_ID, 1)
   156  	query.AddSelect(common.ICAT_COLUMN_TICKET_TYPE, 1)
   157  	query.AddSelect(common.ICAT_COLUMN_TICKET_OBJECT_TYPE, 1)
   158  	query.AddSelect(common.ICAT_COLUMN_TICKET_USES_LIMIT, 1)
   159  	query.AddSelect(common.ICAT_COLUMN_TICKET_USES_COUNT, 1)
   160  	query.AddSelect(common.ICAT_COLUMN_TICKET_EXPIRY_TS, 1)
   161  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT, 1)
   162  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT, 1)
   163  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT, 1)
   164  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT, 1)
   165  	query.AddSelect(common.ICAT_COLUMN_TICKET_DATA_NAME, 1)
   166  	query.AddSelect(common.ICAT_COLUMN_TICKET_DATA_COLL_NAME, 1)
   167  	query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_NAME, 1)
   168  	query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_ZONE, 1)
   169  
   170  	condVal := fmt.Sprintf("= '%s'", ticketName)
   171  	query.AddCondition(common.ICAT_COLUMN_TICKET_STRING, condVal)
   172  
   173  	queryResult := message.IRODSMessageQueryResponse{}
   174  	err := conn.Request(query, &queryResult, nil)
   175  	if err != nil {
   176  		return nil, xerrors.Errorf("failed to receive a ticket query result message: %w", err)
   177  	}
   178  
   179  	err = queryResult.CheckError()
   180  	if err != nil {
   181  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   182  			return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   183  		}
   184  
   185  		return nil, xerrors.Errorf("received a ticket query error: %w", err)
   186  	}
   187  
   188  	if queryResult.RowCount != 1 {
   189  		// file not found
   190  		return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   191  	}
   192  
   193  	if queryResult.AttributeCount > len(queryResult.SQLResult) {
   194  		return nil, xerrors.Errorf("failed to receive ticket attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   195  	}
   196  
   197  	ticketID := int64(-1)
   198  	ticketType := types.TicketTypeRead
   199  	owner := ""
   200  	ownerZone := ""
   201  	objectType := types.ObjectTypeDataObject
   202  	ticketPath := ""
   203  	dataCollName := ""
   204  	dataName := ""
   205  	expirationTime := time.Time{}
   206  	usesLimit := int64(0)
   207  	usesCount := int64(0)
   208  	writeFileLimit := int64(0)
   209  	writeFileCount := int64(0)
   210  	writeByteLimit := int64(0)
   211  	writeByteCount := int64(0)
   212  
   213  	for idx := 0; idx < queryResult.AttributeCount; idx++ {
   214  		sqlResult := queryResult.SQLResult[idx]
   215  		if len(sqlResult.Values) != queryResult.RowCount {
   216  			return nil, xerrors.Errorf("failed to receive ticket rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   217  		}
   218  
   219  		value := sqlResult.Values[0]
   220  
   221  		switch sqlResult.AttributeIndex {
   222  		case int(common.ICAT_COLUMN_TICKET_ID):
   223  			cID, err := strconv.ParseInt(value, 10, 64)
   224  			if err != nil {
   225  				return nil, xerrors.Errorf("failed to parse ticket id '%s': %w", value, err)
   226  			}
   227  			ticketID = cID
   228  		case int(common.ICAT_COLUMN_TICKET_TYPE):
   229  			ticketType = types.TicketType(value)
   230  		case int(common.ICAT_COLUMN_TICKET_OBJECT_TYPE):
   231  			objectType = types.ObjectType(value)
   232  		case int(common.ICAT_COLUMN_TICKET_USES_LIMIT):
   233  			limit, err := strconv.ParseInt(value, 10, 64)
   234  			if err != nil {
   235  				return nil, xerrors.Errorf("failed to parse uses limit '%s': %w", value, err)
   236  			}
   237  			usesLimit = limit
   238  		case int(common.ICAT_COLUMN_TICKET_USES_COUNT):
   239  			count, err := strconv.ParseInt(value, 10, 64)
   240  			if err != nil {
   241  				return nil, xerrors.Errorf("failed to parse uses count '%s': %w", value, err)
   242  			}
   243  			usesCount = count
   244  		case int(common.ICAT_COLUMN_TICKET_EXPIRY_TS):
   245  			if len(strings.TrimSpace(value)) > 0 {
   246  				mT, err := util.GetIRODSDateTime(value)
   247  				if err != nil {
   248  					return nil, xerrors.Errorf("failed to parse expiry time '%s': %w", value, err)
   249  				}
   250  				expirationTime = mT
   251  			}
   252  		case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT):
   253  			limit, err := strconv.ParseInt(value, 10, 64)
   254  			if err != nil {
   255  				return nil, xerrors.Errorf("failed to parse write file limit '%s': %w", value, err)
   256  			}
   257  			writeFileLimit = limit
   258  		case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT):
   259  			count, err := strconv.ParseInt(value, 10, 64)
   260  			if err != nil {
   261  				return nil, xerrors.Errorf("failed to parse write file count '%s': %w", value, err)
   262  			}
   263  			writeFileCount = count
   264  		case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT):
   265  			limit, err := strconv.ParseInt(value, 10, 64)
   266  			if err != nil {
   267  				return nil, xerrors.Errorf("failed to parse write byte limit '%s': %w", value, err)
   268  			}
   269  			writeByteLimit = limit
   270  		case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT):
   271  			count, err := strconv.ParseInt(value, 10, 64)
   272  			if err != nil {
   273  				return nil, xerrors.Errorf("failed to parse write byte count '%s': %w", value, err)
   274  			}
   275  			writeByteCount = count
   276  		case int(common.ICAT_COLUMN_TICKET_DATA_NAME):
   277  			dataName = value
   278  			ticketPath = util.MakeIRODSPath(dataCollName, value)
   279  		case int(common.ICAT_COLUMN_TICKET_DATA_COLL_NAME):
   280  			dataCollName = value
   281  			ticketPath = util.MakeIRODSPath(value, dataName)
   282  		case int(common.ICAT_COLUMN_TICKET_OWNER_NAME):
   283  			owner = value
   284  		case int(common.ICAT_COLUMN_TICKET_OWNER_ZONE):
   285  			ownerZone = value
   286  		default:
   287  			// ignore
   288  		}
   289  	}
   290  
   291  	if ticketID == -1 {
   292  		return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   293  	}
   294  
   295  	return &types.IRODSTicket{
   296  		ID:             ticketID,
   297  		Name:           ticketName,
   298  		Type:           ticketType,
   299  		Owner:          owner,
   300  		OwnerZone:      ownerZone,
   301  		ObjectType:     objectType,
   302  		Path:           ticketPath,
   303  		ExpirationTime: expirationTime,
   304  		UsesLimit:      usesLimit,
   305  		UsesCount:      usesCount,
   306  		WriteFileLimit: writeFileLimit,
   307  		WriteFileCount: writeFileCount,
   308  		WriteByteLimit: writeByteLimit,
   309  		WriteByteCount: writeByteCount,
   310  	}, nil
   311  }
   312  
   313  // GetTicketForCollections returns ticket information for the ticket name string
   314  func GetTicketForCollections(conn *connection.IRODSConnection, ticketName string) (*types.IRODSTicket, error) {
   315  	if conn == nil || !conn.IsConnected() {
   316  		return nil, xerrors.Errorf("connection is nil or disconnected")
   317  	}
   318  
   319  	// lock the connection
   320  	conn.Lock()
   321  	defer conn.Unlock()
   322  
   323  	query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, 0, 0, 0)
   324  	query.AddSelect(common.ICAT_COLUMN_TICKET_ID, 1)
   325  	query.AddSelect(common.ICAT_COLUMN_TICKET_TYPE, 1)
   326  	query.AddSelect(common.ICAT_COLUMN_TICKET_OBJECT_TYPE, 1)
   327  	query.AddSelect(common.ICAT_COLUMN_TICKET_USES_LIMIT, 1)
   328  	query.AddSelect(common.ICAT_COLUMN_TICKET_USES_COUNT, 1)
   329  	query.AddSelect(common.ICAT_COLUMN_TICKET_EXPIRY_TS, 1)
   330  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT, 1)
   331  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT, 1)
   332  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT, 1)
   333  	query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT, 1)
   334  	query.AddSelect(common.ICAT_COLUMN_TICKET_COLL_NAME, 1)
   335  	query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_NAME, 1)
   336  	query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_ZONE, 1)
   337  
   338  	condVal := fmt.Sprintf("= '%s'", ticketName)
   339  	query.AddCondition(common.ICAT_COLUMN_TICKET_STRING, condVal)
   340  
   341  	queryResult := message.IRODSMessageQueryResponse{}
   342  	err := conn.Request(query, &queryResult, nil)
   343  	if err != nil {
   344  		return nil, xerrors.Errorf("failed to receive a ticket query result message: %w", err)
   345  	}
   346  
   347  	err = queryResult.CheckError()
   348  	if err != nil {
   349  		if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   350  			return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   351  		}
   352  
   353  		return nil, xerrors.Errorf("received a ticket query error: %w", err)
   354  	}
   355  
   356  	if queryResult.RowCount != 1 {
   357  		// file not found
   358  		return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   359  	}
   360  
   361  	if queryResult.AttributeCount > len(queryResult.SQLResult) {
   362  		return nil, xerrors.Errorf("failed to receive ticket attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   363  	}
   364  
   365  	ticketID := int64(-1)
   366  	ticketType := types.TicketTypeRead
   367  	owner := ""
   368  	ownerZone := ""
   369  	objectType := types.ObjectTypeCollection
   370  	ticketPath := ""
   371  	expirationTime := time.Time{}
   372  	usesLimit := int64(0)
   373  	usesCount := int64(0)
   374  	writeFileLimit := int64(0)
   375  	writeFileCount := int64(0)
   376  	writeByteLimit := int64(0)
   377  	writeByteCount := int64(0)
   378  
   379  	for idx := 0; idx < queryResult.AttributeCount; idx++ {
   380  		sqlResult := queryResult.SQLResult[idx]
   381  		if len(sqlResult.Values) != queryResult.RowCount {
   382  			return nil, xerrors.Errorf("failed to receive ticket rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   383  		}
   384  
   385  		value := sqlResult.Values[0]
   386  
   387  		switch sqlResult.AttributeIndex {
   388  		case int(common.ICAT_COLUMN_TICKET_ID):
   389  			cID, err := strconv.ParseInt(value, 10, 64)
   390  			if err != nil {
   391  				return nil, xerrors.Errorf("failed to parse ticket id '%s': %w", value, err)
   392  			}
   393  			ticketID = cID
   394  		case int(common.ICAT_COLUMN_TICKET_TYPE):
   395  			ticketType = types.TicketType(value)
   396  		case int(common.ICAT_COLUMN_TICKET_OBJECT_TYPE):
   397  			objectType = types.ObjectType(value)
   398  		case int(common.ICAT_COLUMN_TICKET_USES_LIMIT):
   399  			limit, err := strconv.ParseInt(value, 10, 64)
   400  			if err != nil {
   401  				return nil, xerrors.Errorf("failed to parse uses limit '%s': %w", value, err)
   402  			}
   403  			usesLimit = limit
   404  		case int(common.ICAT_COLUMN_TICKET_USES_COUNT):
   405  			count, err := strconv.ParseInt(value, 10, 64)
   406  			if err != nil {
   407  				return nil, xerrors.Errorf("failed to parse uses count '%s': %w", value, err)
   408  			}
   409  			usesCount = count
   410  		case int(common.ICAT_COLUMN_TICKET_EXPIRY_TS):
   411  			if len(strings.TrimSpace(value)) > 0 {
   412  				mT, err := util.GetIRODSDateTime(value)
   413  				if err != nil {
   414  					return nil, xerrors.Errorf("failed to parse expiry time '%s': %w", value, err)
   415  				}
   416  				expirationTime = mT
   417  			}
   418  		case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT):
   419  			limit, err := strconv.ParseInt(value, 10, 64)
   420  			if err != nil {
   421  				return nil, xerrors.Errorf("failed to parse write file limit '%s': %w", value, err)
   422  			}
   423  			writeFileLimit = limit
   424  		case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT):
   425  			count, err := strconv.ParseInt(value, 10, 64)
   426  			if err != nil {
   427  				return nil, xerrors.Errorf("failed to parse write file count '%s': %w", value, err)
   428  			}
   429  			writeFileCount = count
   430  		case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT):
   431  			limit, err := strconv.ParseInt(value, 10, 64)
   432  			if err != nil {
   433  				return nil, xerrors.Errorf("failed to parse write byte limit '%s': %w", value, err)
   434  			}
   435  			writeByteLimit = limit
   436  		case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT):
   437  			count, err := strconv.ParseInt(value, 10, 64)
   438  			if err != nil {
   439  				return nil, xerrors.Errorf("failed to parse write byte count '%s': %w", value, err)
   440  			}
   441  			writeByteCount = count
   442  		case int(common.ICAT_COLUMN_TICKET_COLL_NAME):
   443  			ticketPath = value
   444  		case int(common.ICAT_COLUMN_TICKET_OWNER_NAME):
   445  			owner = value
   446  		case int(common.ICAT_COLUMN_TICKET_OWNER_ZONE):
   447  			ownerZone = value
   448  		default:
   449  			// ignore
   450  		}
   451  	}
   452  
   453  	if ticketID == -1 {
   454  		return nil, xerrors.Errorf("failed to find the ticket for name %s: %w", ticketName, types.NewTicketNotFoundError(ticketName))
   455  	}
   456  
   457  	return &types.IRODSTicket{
   458  		ID:             ticketID,
   459  		Name:           ticketName,
   460  		Type:           ticketType,
   461  		Owner:          owner,
   462  		OwnerZone:      ownerZone,
   463  		ObjectType:     objectType,
   464  		Path:           ticketPath,
   465  		ExpirationTime: expirationTime,
   466  		UsesLimit:      usesLimit,
   467  		UsesCount:      usesCount,
   468  		WriteFileLimit: writeFileLimit,
   469  		WriteFileCount: writeFileCount,
   470  		WriteByteLimit: writeByteLimit,
   471  		WriteByteCount: writeByteCount,
   472  	}, nil
   473  }
   474  
   475  // ListTickets returns tickets
   476  func ListTickets(conn *connection.IRODSConnection) ([]*types.IRODSTicket, error) {
   477  	if conn == nil || !conn.IsConnected() {
   478  		return nil, xerrors.Errorf("connection is nil or disconnected")
   479  	}
   480  
   481  	tickets := []*types.IRODSTicket{}
   482  
   483  	ticketsColl, err := ListTicketsForCollections(conn)
   484  	if err != nil {
   485  		return nil, err
   486  	}
   487  
   488  	tickets = append(tickets, ticketsColl...)
   489  
   490  	ticketsDataObj, err := ListTicketsForDataObjects(conn)
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  
   495  	tickets = append(tickets, ticketsDataObj...)
   496  
   497  	return tickets, nil
   498  }
   499  
   500  // ListTicketsForDataObjects returns tickets for data objects
   501  func ListTicketsForDataObjects(conn *connection.IRODSConnection) ([]*types.IRODSTicket, error) {
   502  	if conn == nil || !conn.IsConnected() {
   503  		return nil, xerrors.Errorf("connection is nil or disconnected")
   504  	}
   505  
   506  	// lock the connection
   507  	conn.Lock()
   508  	defer conn.Unlock()
   509  
   510  	tickets := []*types.IRODSTicket{}
   511  
   512  	continueQuery := true
   513  	continueIndex := 0
   514  	for continueQuery {
   515  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   516  		query.AddSelect(common.ICAT_COLUMN_TICKET_ID, 1)
   517  		query.AddSelect(common.ICAT_COLUMN_TICKET_STRING, 1)
   518  		query.AddSelect(common.ICAT_COLUMN_TICKET_TYPE, 1)
   519  		query.AddSelect(common.ICAT_COLUMN_TICKET_OBJECT_TYPE, 1)
   520  		query.AddSelect(common.ICAT_COLUMN_TICKET_USES_LIMIT, 1)
   521  		query.AddSelect(common.ICAT_COLUMN_TICKET_USES_COUNT, 1)
   522  		query.AddSelect(common.ICAT_COLUMN_TICKET_EXPIRY_TS, 1)
   523  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT, 1)
   524  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT, 1)
   525  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT, 1)
   526  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT, 1)
   527  		query.AddSelect(common.ICAT_COLUMN_TICKET_DATA_NAME, 1)
   528  		query.AddSelect(common.ICAT_COLUMN_TICKET_DATA_COLL_NAME, 1)
   529  		query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_NAME, 1)
   530  		query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_ZONE, 1)
   531  
   532  		queryResult := message.IRODSMessageQueryResponse{}
   533  		err := conn.Request(query, &queryResult, nil)
   534  		if err != nil {
   535  			return nil, xerrors.Errorf("failed to receive a ticket query result message: %w", err)
   536  		}
   537  
   538  		err = queryResult.CheckError()
   539  		if err != nil {
   540  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   541  				// empty
   542  				break
   543  			}
   544  
   545  			return nil, xerrors.Errorf("received a ticket query error: %w", err)
   546  		}
   547  
   548  		if queryResult.RowCount == 0 {
   549  			break
   550  		}
   551  
   552  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   553  			return nil, xerrors.Errorf("failed to receive ticket attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   554  		}
   555  
   556  		pagenatedTickets := make([]*types.IRODSTicket, queryResult.RowCount)
   557  		tempValues := make([]map[string]string, queryResult.RowCount)
   558  
   559  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   560  			sqlResult := queryResult.SQLResult[attr]
   561  			if len(sqlResult.Values) != queryResult.RowCount {
   562  				return nil, xerrors.Errorf("failed to receive ticket rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   563  			}
   564  
   565  			for row := 0; row < queryResult.RowCount; row++ {
   566  				value := sqlResult.Values[row]
   567  
   568  				if pagenatedTickets[row] == nil {
   569  					// create a new
   570  					pagenatedTickets[row] = &types.IRODSTicket{
   571  						ID:             -1,
   572  						Name:           "",
   573  						Type:           types.TicketTypeRead,
   574  						Owner:          "",
   575  						OwnerZone:      "",
   576  						ObjectType:     types.ObjectTypeCollection,
   577  						Path:           "",
   578  						ExpirationTime: time.Time{},
   579  						UsesLimit:      0,
   580  						UsesCount:      0,
   581  						WriteFileLimit: 0,
   582  						WriteFileCount: 0,
   583  						WriteByteLimit: 0,
   584  						WriteByteCount: 0,
   585  					}
   586  				}
   587  
   588  				if tempValues[row] == nil {
   589  					// create a new
   590  					tempValues[row] = map[string]string{}
   591  				}
   592  
   593  				switch sqlResult.AttributeIndex {
   594  				case int(common.ICAT_COLUMN_TICKET_ID):
   595  					tID, err := strconv.ParseInt(value, 10, 64)
   596  					if err != nil {
   597  						return nil, xerrors.Errorf("failed to parse ticket id '%s': %w", value, err)
   598  					}
   599  					pagenatedTickets[row].ID = tID
   600  				case int(common.ICAT_COLUMN_TICKET_STRING):
   601  					pagenatedTickets[row].Name = value
   602  				case int(common.ICAT_COLUMN_TICKET_TYPE):
   603  					pagenatedTickets[row].Type = types.TicketType(value)
   604  				case int(common.ICAT_COLUMN_TICKET_OBJECT_TYPE):
   605  					pagenatedTickets[row].ObjectType = types.ObjectType(value)
   606  				case int(common.ICAT_COLUMN_TICKET_USES_LIMIT):
   607  					limit, err := strconv.ParseInt(value, 10, 64)
   608  					if err != nil {
   609  						return nil, xerrors.Errorf("failed to parse uses limit '%s': %w", value, err)
   610  					}
   611  					pagenatedTickets[row].UsesLimit = limit
   612  				case int(common.ICAT_COLUMN_TICKET_USES_COUNT):
   613  					count, err := strconv.ParseInt(value, 10, 64)
   614  					if err != nil {
   615  						return nil, xerrors.Errorf("failed to parse uses count '%s': %w", value, err)
   616  					}
   617  					pagenatedTickets[row].UsesCount = count
   618  				case int(common.ICAT_COLUMN_TICKET_EXPIRY_TS):
   619  					if len(strings.TrimSpace(value)) > 0 {
   620  						mT, err := util.GetIRODSDateTime(value)
   621  						if err != nil {
   622  							return nil, xerrors.Errorf("failed to parse expiry time '%s': %w", value, err)
   623  						}
   624  						pagenatedTickets[row].ExpirationTime = mT
   625  					}
   626  				case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT):
   627  					limit, err := strconv.ParseInt(value, 10, 64)
   628  					if err != nil {
   629  						return nil, xerrors.Errorf("failed to parse write file limit '%s': %w", value, err)
   630  					}
   631  					pagenatedTickets[row].WriteFileLimit = limit
   632  				case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT):
   633  					count, err := strconv.ParseInt(value, 10, 64)
   634  					if err != nil {
   635  						return nil, xerrors.Errorf("failed to parse write file count '%s': %w", value, err)
   636  					}
   637  					pagenatedTickets[row].WriteFileCount = count
   638  				case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT):
   639  					limit, err := strconv.ParseInt(value, 10, 64)
   640  					if err != nil {
   641  						return nil, xerrors.Errorf("failed to parse write byte limit '%s': %w", value, err)
   642  					}
   643  					pagenatedTickets[row].WriteByteLimit = limit
   644  				case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT):
   645  					count, err := strconv.ParseInt(value, 10, 64)
   646  					if err != nil {
   647  						return nil, xerrors.Errorf("failed to parse write byte count '%s': %w", value, err)
   648  					}
   649  					pagenatedTickets[row].WriteByteCount = count
   650  				case int(common.ICAT_COLUMN_TICKET_DATA_NAME):
   651  					tempValues[row]["data_name"] = value
   652  					if dataCollName, ok := tempValues[row]["data_coll_name"]; ok {
   653  						pagenatedTickets[row].Path = util.MakeIRODSPath(dataCollName, value)
   654  					}
   655  				case int(common.ICAT_COLUMN_TICKET_DATA_COLL_NAME):
   656  					tempValues[row]["data_coll_name"] = value
   657  					if dataName, ok := tempValues[row]["data_name"]; ok {
   658  						pagenatedTickets[row].Path = util.MakeIRODSPath(value, dataName)
   659  					}
   660  				case int(common.ICAT_COLUMN_TICKET_OWNER_NAME):
   661  					pagenatedTickets[row].Owner = value
   662  				case int(common.ICAT_COLUMN_TICKET_OWNER_ZONE):
   663  					pagenatedTickets[row].OwnerZone = value
   664  				default:
   665  					// ignore
   666  				}
   667  			}
   668  		}
   669  
   670  		tickets = append(tickets, pagenatedTickets...)
   671  
   672  		continueIndex = queryResult.ContinueIndex
   673  		if continueIndex == 0 {
   674  			continueQuery = false
   675  		}
   676  	}
   677  
   678  	return tickets, nil
   679  }
   680  
   681  // ListTicketsForCollections returns tickets for collections
   682  func ListTicketsForCollections(conn *connection.IRODSConnection) ([]*types.IRODSTicket, error) {
   683  	if conn == nil || !conn.IsConnected() {
   684  		return nil, xerrors.Errorf("connection is nil or disconnected")
   685  	}
   686  
   687  	// lock the connection
   688  	conn.Lock()
   689  	defer conn.Unlock()
   690  
   691  	tickets := []*types.IRODSTicket{}
   692  
   693  	continueQuery := true
   694  	continueIndex := 0
   695  	for continueQuery {
   696  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   697  		query.AddSelect(common.ICAT_COLUMN_TICKET_ID, 1)
   698  		query.AddSelect(common.ICAT_COLUMN_TICKET_STRING, 1)
   699  		query.AddSelect(common.ICAT_COLUMN_TICKET_TYPE, 1)
   700  		query.AddSelect(common.ICAT_COLUMN_TICKET_OBJECT_TYPE, 1)
   701  		query.AddSelect(common.ICAT_COLUMN_TICKET_USES_LIMIT, 1)
   702  		query.AddSelect(common.ICAT_COLUMN_TICKET_USES_COUNT, 1)
   703  		query.AddSelect(common.ICAT_COLUMN_TICKET_EXPIRY_TS, 1)
   704  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT, 1)
   705  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT, 1)
   706  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT, 1)
   707  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT, 1)
   708  		query.AddSelect(common.ICAT_COLUMN_TICKET_COLL_NAME, 1)
   709  		query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_NAME, 1)
   710  		query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_ZONE, 1)
   711  
   712  		queryResult := message.IRODSMessageQueryResponse{}
   713  		err := conn.Request(query, &queryResult, nil)
   714  		if err != nil {
   715  			return nil, xerrors.Errorf("failed to receive a ticket query result message: %w", err)
   716  		}
   717  
   718  		err = queryResult.CheckError()
   719  		if err != nil {
   720  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   721  				// empty
   722  				break
   723  			}
   724  
   725  			return nil, xerrors.Errorf("received a ticket query error: %w", err)
   726  		}
   727  
   728  		if queryResult.RowCount == 0 {
   729  			break
   730  		}
   731  
   732  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   733  			return nil, xerrors.Errorf("failed to receive ticket attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   734  		}
   735  
   736  		pagenatedTickets := make([]*types.IRODSTicket, queryResult.RowCount)
   737  
   738  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   739  			sqlResult := queryResult.SQLResult[attr]
   740  			if len(sqlResult.Values) != queryResult.RowCount {
   741  				return nil, xerrors.Errorf("failed to receive ticket rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   742  			}
   743  
   744  			for row := 0; row < queryResult.RowCount; row++ {
   745  				value := sqlResult.Values[row]
   746  
   747  				if pagenatedTickets[row] == nil {
   748  					// create a new
   749  					pagenatedTickets[row] = &types.IRODSTicket{
   750  						ID:             -1,
   751  						Name:           "",
   752  						Type:           types.TicketTypeRead,
   753  						Owner:          "",
   754  						OwnerZone:      "",
   755  						ObjectType:     types.ObjectTypeCollection,
   756  						Path:           "",
   757  						ExpirationTime: time.Time{},
   758  						UsesLimit:      0,
   759  						UsesCount:      0,
   760  						WriteFileLimit: 0,
   761  						WriteFileCount: 0,
   762  						WriteByteLimit: 0,
   763  						WriteByteCount: 0,
   764  					}
   765  				}
   766  
   767  				switch sqlResult.AttributeIndex {
   768  				case int(common.ICAT_COLUMN_TICKET_ID):
   769  					tID, err := strconv.ParseInt(value, 10, 64)
   770  					if err != nil {
   771  						return nil, xerrors.Errorf("failed to parse ticket id '%s': %w", value, err)
   772  					}
   773  					pagenatedTickets[row].ID = tID
   774  				case int(common.ICAT_COLUMN_TICKET_STRING):
   775  					pagenatedTickets[row].Name = value
   776  				case int(common.ICAT_COLUMN_TICKET_TYPE):
   777  					pagenatedTickets[row].Type = types.TicketType(value)
   778  				case int(common.ICAT_COLUMN_TICKET_OBJECT_TYPE):
   779  					pagenatedTickets[row].ObjectType = types.ObjectType(value)
   780  				case int(common.ICAT_COLUMN_TICKET_USES_LIMIT):
   781  					limit, err := strconv.ParseInt(value, 10, 64)
   782  					if err != nil {
   783  						return nil, xerrors.Errorf("failed to parse uses limit '%s': %w", value, err)
   784  					}
   785  					pagenatedTickets[row].UsesLimit = limit
   786  				case int(common.ICAT_COLUMN_TICKET_USES_COUNT):
   787  					count, err := strconv.ParseInt(value, 10, 64)
   788  					if err != nil {
   789  						return nil, xerrors.Errorf("failed to parse uses count '%s': %w", value, err)
   790  					}
   791  					pagenatedTickets[row].UsesCount = count
   792  				case int(common.ICAT_COLUMN_TICKET_EXPIRY_TS):
   793  					if len(strings.TrimSpace(value)) > 0 {
   794  						mT, err := util.GetIRODSDateTime(value)
   795  						if err != nil {
   796  							return nil, xerrors.Errorf("failed to parse expiry time '%s': %w", value, err)
   797  						}
   798  						pagenatedTickets[row].ExpirationTime = mT
   799  					}
   800  				case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT):
   801  					limit, err := strconv.ParseInt(value, 10, 64)
   802  					if err != nil {
   803  						return nil, xerrors.Errorf("failed to parse write file limit '%s': %w", value, err)
   804  					}
   805  					pagenatedTickets[row].WriteFileLimit = limit
   806  				case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT):
   807  					count, err := strconv.ParseInt(value, 10, 64)
   808  					if err != nil {
   809  						return nil, xerrors.Errorf("failed to parse write file count '%s': %w", value, err)
   810  					}
   811  					pagenatedTickets[row].WriteFileCount = count
   812  				case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT):
   813  					limit, err := strconv.ParseInt(value, 10, 64)
   814  					if err != nil {
   815  						return nil, xerrors.Errorf("failed to parse write byte limit '%s': %w", value, err)
   816  					}
   817  					pagenatedTickets[row].WriteByteLimit = limit
   818  				case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT):
   819  					count, err := strconv.ParseInt(value, 10, 64)
   820  					if err != nil {
   821  						return nil, xerrors.Errorf("failed to parse write byte count '%s': %w", value, err)
   822  					}
   823  					pagenatedTickets[row].WriteByteCount = count
   824  				case int(common.ICAT_COLUMN_TICKET_COLL_NAME):
   825  					pagenatedTickets[row].Path = value
   826  				case int(common.ICAT_COLUMN_TICKET_OWNER_NAME):
   827  					pagenatedTickets[row].Owner = value
   828  				case int(common.ICAT_COLUMN_TICKET_OWNER_ZONE):
   829  					pagenatedTickets[row].OwnerZone = value
   830  				default:
   831  					// ignore
   832  				}
   833  			}
   834  		}
   835  
   836  		tickets = append(tickets, pagenatedTickets...)
   837  
   838  		continueIndex = queryResult.ContinueIndex
   839  		if continueIndex == 0 {
   840  			continueQuery = false
   841  		}
   842  	}
   843  
   844  	return tickets, nil
   845  }
   846  
   847  // ListTicketsBasic returns tickets with basic info
   848  func ListTicketsBasic(conn *connection.IRODSConnection) ([]*types.IRODSTicket, error) {
   849  	if conn == nil || !conn.IsConnected() {
   850  		return nil, xerrors.Errorf("connection is nil or disconnected")
   851  	}
   852  
   853  	// lock the connection
   854  	conn.Lock()
   855  	defer conn.Unlock()
   856  
   857  	tickets := []*types.IRODSTicket{}
   858  
   859  	continueQuery := true
   860  	continueIndex := 0
   861  	for continueQuery {
   862  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
   863  		query.AddSelect(common.ICAT_COLUMN_TICKET_ID, 1)
   864  		query.AddSelect(common.ICAT_COLUMN_TICKET_STRING, 1)
   865  		query.AddSelect(common.ICAT_COLUMN_TICKET_TYPE, 1)
   866  		query.AddSelect(common.ICAT_COLUMN_TICKET_OBJECT_TYPE, 1)
   867  		query.AddSelect(common.ICAT_COLUMN_TICKET_USES_LIMIT, 1)
   868  		query.AddSelect(common.ICAT_COLUMN_TICKET_USES_COUNT, 1)
   869  		query.AddSelect(common.ICAT_COLUMN_TICKET_EXPIRY_TS, 1)
   870  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT, 1)
   871  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT, 1)
   872  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT, 1)
   873  		query.AddSelect(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT, 1)
   874  		query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_NAME, 1)
   875  		query.AddSelect(common.ICAT_COLUMN_TICKET_OWNER_ZONE, 1)
   876  
   877  		queryResult := message.IRODSMessageQueryResponse{}
   878  		err := conn.Request(query, &queryResult, nil)
   879  		if err != nil {
   880  			return nil, xerrors.Errorf("failed to receive a ticket query result message: %w", err)
   881  		}
   882  
   883  		err = queryResult.CheckError()
   884  		if err != nil {
   885  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
   886  				// empty
   887  				break
   888  			}
   889  
   890  			return nil, xerrors.Errorf("received a ticket query error: %w", err)
   891  		}
   892  
   893  		if queryResult.RowCount == 0 {
   894  			break
   895  		}
   896  
   897  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
   898  			return nil, xerrors.Errorf("failed to receive ticket attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
   899  		}
   900  
   901  		pagenatedTickets := make([]*types.IRODSTicket, queryResult.RowCount)
   902  
   903  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
   904  			sqlResult := queryResult.SQLResult[attr]
   905  			if len(sqlResult.Values) != queryResult.RowCount {
   906  				return nil, xerrors.Errorf("failed to receive ticket rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
   907  			}
   908  
   909  			for row := 0; row < queryResult.RowCount; row++ {
   910  				value := sqlResult.Values[row]
   911  
   912  				if pagenatedTickets[row] == nil {
   913  					// create a new
   914  					pagenatedTickets[row] = &types.IRODSTicket{
   915  						ID:             -1,
   916  						Name:           "",
   917  						Type:           types.TicketTypeRead,
   918  						Owner:          "",
   919  						OwnerZone:      "",
   920  						ObjectType:     types.ObjectTypeCollection,
   921  						Path:           "",
   922  						ExpirationTime: time.Time{},
   923  						UsesLimit:      0,
   924  						UsesCount:      0,
   925  						WriteFileLimit: 0,
   926  						WriteFileCount: 0,
   927  						WriteByteLimit: 0,
   928  						WriteByteCount: 0,
   929  					}
   930  				}
   931  
   932  				switch sqlResult.AttributeIndex {
   933  				case int(common.ICAT_COLUMN_TICKET_ID):
   934  					tID, err := strconv.ParseInt(value, 10, 64)
   935  					if err != nil {
   936  						return nil, xerrors.Errorf("failed to parse ticket id '%s': %w", value, err)
   937  					}
   938  					pagenatedTickets[row].ID = tID
   939  				case int(common.ICAT_COLUMN_TICKET_STRING):
   940  					pagenatedTickets[row].Name = value
   941  				case int(common.ICAT_COLUMN_TICKET_TYPE):
   942  					pagenatedTickets[row].Type = types.TicketType(value)
   943  				case int(common.ICAT_COLUMN_TICKET_OBJECT_TYPE):
   944  					pagenatedTickets[row].ObjectType = types.ObjectType(value)
   945  				case int(common.ICAT_COLUMN_TICKET_USES_LIMIT):
   946  					limit, err := strconv.ParseInt(value, 10, 64)
   947  					if err != nil {
   948  						return nil, xerrors.Errorf("failed to parse uses limit '%s': %w", value, err)
   949  					}
   950  					pagenatedTickets[row].UsesLimit = limit
   951  				case int(common.ICAT_COLUMN_TICKET_USES_COUNT):
   952  					count, err := strconv.ParseInt(value, 10, 64)
   953  					if err != nil {
   954  						return nil, xerrors.Errorf("failed to parse uses count '%s': %w", value, err)
   955  					}
   956  					pagenatedTickets[row].UsesCount = count
   957  				case int(common.ICAT_COLUMN_TICKET_EXPIRY_TS):
   958  					if len(strings.TrimSpace(value)) > 0 {
   959  						mT, err := util.GetIRODSDateTime(value)
   960  						if err != nil {
   961  							return nil, xerrors.Errorf("failed to parse expiry time '%s': %w", value, err)
   962  						}
   963  						pagenatedTickets[row].ExpirationTime = mT
   964  					}
   965  				case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_LIMIT):
   966  					limit, err := strconv.ParseInt(value, 10, 64)
   967  					if err != nil {
   968  						return nil, xerrors.Errorf("failed to parse write file limit '%s': %w", value, err)
   969  					}
   970  					pagenatedTickets[row].WriteFileLimit = limit
   971  				case int(common.ICAT_COLUMN_TICKET_WRITE_FILE_COUNT):
   972  					count, err := strconv.ParseInt(value, 10, 64)
   973  					if err != nil {
   974  						return nil, xerrors.Errorf("failed to parse write file count '%s': %w", value, err)
   975  					}
   976  					pagenatedTickets[row].WriteFileCount = count
   977  				case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_LIMIT):
   978  					limit, err := strconv.ParseInt(value, 10, 64)
   979  					if err != nil {
   980  						return nil, xerrors.Errorf("failed to parse write byte limit '%s': %w", value, err)
   981  					}
   982  					pagenatedTickets[row].WriteByteLimit = limit
   983  				case int(common.ICAT_COLUMN_TICKET_WRITE_BYTE_COUNT):
   984  					count, err := strconv.ParseInt(value, 10, 64)
   985  					if err != nil {
   986  						return nil, xerrors.Errorf("failed to parse write byte count '%s': %w", value, err)
   987  					}
   988  					pagenatedTickets[row].WriteByteCount = count
   989  				case int(common.ICAT_COLUMN_TICKET_OWNER_NAME):
   990  					pagenatedTickets[row].Owner = value
   991  				case int(common.ICAT_COLUMN_TICKET_OWNER_ZONE):
   992  					pagenatedTickets[row].OwnerZone = value
   993  				default:
   994  					// ignore
   995  				}
   996  			}
   997  		}
   998  
   999  		tickets = append(tickets, pagenatedTickets...)
  1000  
  1001  		continueIndex = queryResult.ContinueIndex
  1002  		if continueIndex == 0 {
  1003  			continueQuery = false
  1004  		}
  1005  	}
  1006  
  1007  	return tickets, nil
  1008  }
  1009  
  1010  // ListTicketAllowedHosts returns allowed hosts for the given ticket
  1011  func ListTicketAllowedHosts(conn *connection.IRODSConnection, ticketID int64) ([]string, error) {
  1012  	if conn == nil || !conn.IsConnected() {
  1013  		return nil, xerrors.Errorf("connection is nil or disconnected")
  1014  	}
  1015  
  1016  	// lock the connection
  1017  	conn.Lock()
  1018  	defer conn.Unlock()
  1019  
  1020  	hosts := []string{}
  1021  
  1022  	continueQuery := true
  1023  	continueIndex := 0
  1024  	for continueQuery {
  1025  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
  1026  		query.AddSelect(common.ICAT_COLUMN_TICKET_ALLOWED_HOST, 1)
  1027  
  1028  		collCondVal := fmt.Sprintf("= '%d'", ticketID)
  1029  		query.AddCondition(common.ICAT_COLUMN_TICKET_ALLOWED_HOST_TICKET_ID, collCondVal)
  1030  
  1031  		queryResult := message.IRODSMessageQueryResponse{}
  1032  		err := conn.Request(query, &queryResult, nil)
  1033  		if err != nil {
  1034  			return nil, xerrors.Errorf("failed to receive a ticket restriction query result message: %w", err)
  1035  		}
  1036  
  1037  		err = queryResult.CheckError()
  1038  		if err != nil {
  1039  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
  1040  				// empty
  1041  				break
  1042  			}
  1043  
  1044  			return nil, xerrors.Errorf("received a ticket restriction query error: %w", err)
  1045  		}
  1046  
  1047  		if queryResult.RowCount == 0 {
  1048  			break
  1049  		}
  1050  
  1051  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
  1052  			return nil, xerrors.Errorf("failed to receive ticket restriction attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
  1053  		}
  1054  
  1055  		pagenatedHosts := make([]string, queryResult.RowCount)
  1056  
  1057  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
  1058  			sqlResult := queryResult.SQLResult[attr]
  1059  			if len(sqlResult.Values) != queryResult.RowCount {
  1060  				return nil, xerrors.Errorf("failed to receive ticket restriction rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
  1061  			}
  1062  
  1063  			for row := 0; row < queryResult.RowCount; row++ {
  1064  				value := sqlResult.Values[row]
  1065  
  1066  				switch sqlResult.AttributeIndex {
  1067  				case int(common.ICAT_COLUMN_TICKET_ALLOWED_HOST):
  1068  					pagenatedHosts[row] = value
  1069  				default:
  1070  					// ignore
  1071  				}
  1072  			}
  1073  		}
  1074  
  1075  		hosts = append(hosts, pagenatedHosts...)
  1076  
  1077  		continueIndex = queryResult.ContinueIndex
  1078  		if continueIndex == 0 {
  1079  			continueQuery = false
  1080  		}
  1081  	}
  1082  
  1083  	return hosts, nil
  1084  }
  1085  
  1086  // ListTicketAllowedUserNames returns allowed user names for the given ticket
  1087  func ListTicketAllowedUserNames(conn *connection.IRODSConnection, ticketID int64) ([]string, error) {
  1088  	if conn == nil || !conn.IsConnected() {
  1089  		return nil, xerrors.Errorf("connection is nil or disconnected")
  1090  	}
  1091  
  1092  	// lock the connection
  1093  	conn.Lock()
  1094  	defer conn.Unlock()
  1095  
  1096  	usernames := []string{}
  1097  
  1098  	continueQuery := true
  1099  	continueIndex := 0
  1100  	for continueQuery {
  1101  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
  1102  		query.AddSelect(common.ICAT_COLUMN_TICKET_ALLOWED_USER_NAME, 1)
  1103  
  1104  		collCondVal := fmt.Sprintf("= '%d'", ticketID)
  1105  		query.AddCondition(common.ICAT_COLUMN_TICKET_ALLOWED_USER_TICKET_ID, collCondVal)
  1106  
  1107  		queryResult := message.IRODSMessageQueryResponse{}
  1108  		err := conn.Request(query, &queryResult, nil)
  1109  		if err != nil {
  1110  			return nil, xerrors.Errorf("failed to receive a ticket restriction query result message: %w", err)
  1111  		}
  1112  
  1113  		err = queryResult.CheckError()
  1114  		if err != nil {
  1115  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
  1116  				// empty
  1117  				break
  1118  			}
  1119  
  1120  			return nil, xerrors.Errorf("received a ticket restriction query error: %w", err)
  1121  		}
  1122  
  1123  		if queryResult.RowCount == 0 {
  1124  			break
  1125  		}
  1126  
  1127  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
  1128  			return nil, xerrors.Errorf("failed to receive ticket restriction attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
  1129  		}
  1130  
  1131  		pagenatedUsernames := make([]string, queryResult.RowCount)
  1132  
  1133  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
  1134  			sqlResult := queryResult.SQLResult[attr]
  1135  			if len(sqlResult.Values) != queryResult.RowCount {
  1136  				return nil, xerrors.Errorf("failed to receive ticket restriction rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
  1137  			}
  1138  
  1139  			for row := 0; row < queryResult.RowCount; row++ {
  1140  				value := sqlResult.Values[row]
  1141  
  1142  				switch sqlResult.AttributeIndex {
  1143  				case int(common.ICAT_COLUMN_TICKET_ALLOWED_USER_NAME):
  1144  					pagenatedUsernames[row] = value
  1145  				default:
  1146  					// ignore
  1147  				}
  1148  			}
  1149  		}
  1150  
  1151  		usernames = append(usernames, pagenatedUsernames...)
  1152  
  1153  		continueIndex = queryResult.ContinueIndex
  1154  		if continueIndex == 0 {
  1155  			continueQuery = false
  1156  		}
  1157  	}
  1158  
  1159  	return usernames, nil
  1160  }
  1161  
  1162  // ListTicketAllowedGroupNames returns allowed group names for the given ticket
  1163  func ListTicketAllowedGroupNames(conn *connection.IRODSConnection, ticketID int64) ([]string, error) {
  1164  	if conn == nil || !conn.IsConnected() {
  1165  		return nil, xerrors.Errorf("connection is nil or disconnected")
  1166  	}
  1167  
  1168  	// lock the connection
  1169  	conn.Lock()
  1170  	defer conn.Unlock()
  1171  
  1172  	groupnames := []string{}
  1173  
  1174  	continueQuery := true
  1175  	continueIndex := 0
  1176  	for continueQuery {
  1177  		query := message.NewIRODSMessageQueryRequest(common.MaxQueryRows, continueIndex, 0, 0)
  1178  		query.AddSelect(common.ICAT_COLUMN_TICKET_ALLOWED_GROUP_NAME, 1)
  1179  
  1180  		collCondVal := fmt.Sprintf("= '%d'", ticketID)
  1181  		query.AddCondition(common.ICAT_COLUMN_TICKET_ALLOWED_GROUP_TICKET_ID, collCondVal)
  1182  
  1183  		queryResult := message.IRODSMessageQueryResponse{}
  1184  		err := conn.Request(query, &queryResult, nil)
  1185  		if err != nil {
  1186  			return nil, xerrors.Errorf("failed to receive a ticket restriction query result message: %w", err)
  1187  		}
  1188  
  1189  		err = queryResult.CheckError()
  1190  		if err != nil {
  1191  			if types.GetIRODSErrorCode(err) == common.CAT_NO_ROWS_FOUND {
  1192  				// empty
  1193  				break
  1194  			}
  1195  
  1196  			return nil, xerrors.Errorf("received a ticket restriction query error: %w", err)
  1197  		}
  1198  
  1199  		if queryResult.RowCount == 0 {
  1200  			break
  1201  		}
  1202  
  1203  		if queryResult.AttributeCount > len(queryResult.SQLResult) {
  1204  			return nil, xerrors.Errorf("failed to receive ticket restriction attributes - requires %d, but received %d attributes", queryResult.AttributeCount, len(queryResult.SQLResult))
  1205  		}
  1206  
  1207  		pagenatedGroupnames := make([]string, queryResult.RowCount)
  1208  
  1209  		for attr := 0; attr < queryResult.AttributeCount; attr++ {
  1210  			sqlResult := queryResult.SQLResult[attr]
  1211  			if len(sqlResult.Values) != queryResult.RowCount {
  1212  				return nil, xerrors.Errorf("failed to receive ticket restriction rows - requires %d, but received %d attributes", queryResult.RowCount, len(sqlResult.Values))
  1213  			}
  1214  
  1215  			for row := 0; row < queryResult.RowCount; row++ {
  1216  				value := sqlResult.Values[row]
  1217  
  1218  				switch sqlResult.AttributeIndex {
  1219  				case int(common.ICAT_COLUMN_TICKET_ALLOWED_GROUP_NAME):
  1220  					pagenatedGroupnames[row] = value
  1221  				default:
  1222  					// ignore
  1223  				}
  1224  			}
  1225  		}
  1226  
  1227  		groupnames = append(groupnames, pagenatedGroupnames...)
  1228  
  1229  		continueIndex = queryResult.ContinueIndex
  1230  		if continueIndex == 0 {
  1231  			continueQuery = false
  1232  		}
  1233  	}
  1234  
  1235  	return groupnames, nil
  1236  }
  1237  
  1238  // CreateTicket creates a ticket
  1239  func CreateTicket(conn *connection.IRODSConnection, ticketName string, ticketType types.TicketType, path string) error {
  1240  	// lock the connection
  1241  	conn.Lock()
  1242  	defer conn.Unlock()
  1243  
  1244  	ticketName = strings.TrimSpace(ticketName)
  1245  	if len(ticketName) == 0 {
  1246  		ticketName = xid.New().String()
  1247  	}
  1248  
  1249  	req := message.NewIRODSMessageTicketAdminRequest("create", ticketName, string(ticketType), path, ticketName)
  1250  
  1251  	err := conn.RequestAndCheck(req, &message.IRODSMessageAdminResponse{}, nil)
  1252  	if err != nil {
  1253  		return xerrors.Errorf("received create ticket error: %w", err)
  1254  	}
  1255  	return nil
  1256  }
  1257  
  1258  // DeleteTicket deletes the ticket
  1259  func DeleteTicket(conn *connection.IRODSConnection, ticketName string) error {
  1260  	// lock the connection
  1261  	conn.Lock()
  1262  	defer conn.Unlock()
  1263  
  1264  	req := message.NewIRODSMessageTicketAdminRequest("delete", ticketName)
  1265  
  1266  	err := conn.RequestAndCheck(req, &message.IRODSMessageAdminResponse{}, nil)
  1267  	if err != nil {
  1268  		return xerrors.Errorf("received delete ticket error: %w", err)
  1269  	}
  1270  	return nil
  1271  }
  1272  
  1273  // ModifyTicket modifies the given ticket
  1274  func ModifyTicket(conn *connection.IRODSConnection, ticketName string, args ...string) error {
  1275  	// lock the connection
  1276  	conn.Lock()
  1277  	defer conn.Unlock()
  1278  
  1279  	req := message.NewIRODSMessageTicketAdminRequest("mod", ticketName, args...)
  1280  
  1281  	err := conn.RequestAndCheck(req, &message.IRODSMessageAdminResponse{}, nil)
  1282  	if err != nil {
  1283  		return xerrors.Errorf("received mod ticket error: %w", err)
  1284  	}
  1285  	return nil
  1286  }
  1287  
  1288  // ModifyTicketUseLimit modifies the use limit of the given ticket
  1289  func ModifyTicketUseLimit(conn *connection.IRODSConnection, ticketName string, uses int64) error {
  1290  	return ModifyTicket(conn, ticketName, "uses", fmt.Sprintf("%d", uses))
  1291  }
  1292  
  1293  // ClearTicketUseLimit clears the use limit of the given ticket
  1294  func ClearTicketUseLimit(conn *connection.IRODSConnection, ticketName string) error {
  1295  	return ModifyTicketUseLimit(conn, ticketName, 0)
  1296  }
  1297  
  1298  // ModifyTicketWriteFileLimit modifies the write file limit of the given ticket
  1299  func ModifyTicketWriteFileLimit(conn *connection.IRODSConnection, ticketName string, count int64) error {
  1300  	return ModifyTicket(conn, ticketName, "write-file", fmt.Sprintf("%d", count))
  1301  }
  1302  
  1303  // ClearTicketWriteFileLimit clears the write file limit of the given ticket
  1304  func ClearTicketWriteFileLimit(conn *connection.IRODSConnection, ticketName string) error {
  1305  	return ModifyTicketWriteFileLimit(conn, ticketName, 0)
  1306  }
  1307  
  1308  // ModifyTicketWriteByteLimit modifies the write byte limit of the given ticket
  1309  func ModifyTicketWriteByteLimit(conn *connection.IRODSConnection, ticketName string, bytes int64) error {
  1310  	return ModifyTicket(conn, ticketName, "write-bytes", fmt.Sprintf("%d", bytes))
  1311  }
  1312  
  1313  // ClearTicketWriteByteLimit clears the write byte limit of the given ticket
  1314  func ClearTicketWriteByteLimit(conn *connection.IRODSConnection, ticketName string) error {
  1315  	return ModifyTicketWriteByteLimit(conn, ticketName, 0)
  1316  }
  1317  
  1318  // AddTicketAllowedUser adds a user to the allowed user names list of the given ticket
  1319  func AddTicketAllowedUser(conn *connection.IRODSConnection, ticketName string, userName string) error {
  1320  	return ModifyTicket(conn, ticketName, "add", "user", userName)
  1321  }
  1322  
  1323  // RemoveTicketAllowedUser removes the user from the allowed user names list of the given ticket
  1324  func RemoveTicketAllowedUser(conn *connection.IRODSConnection, ticketName string, userName string) error {
  1325  	return ModifyTicket(conn, ticketName, "remove", "user", userName)
  1326  }
  1327  
  1328  // AddTicketAllowedGroup adds a group to the allowed group names list of the given ticket
  1329  func AddTicketAllowedGroup(conn *connection.IRODSConnection, ticketName string, groupName string) error {
  1330  	return ModifyTicket(conn, ticketName, "add", "group", groupName)
  1331  }
  1332  
  1333  // RemoveTicketAllowedGroup removes the group from the allowed group names list of the given ticket
  1334  func RemoveTicketAllowedGroup(conn *connection.IRODSConnection, ticketName string, groupName string) error {
  1335  	return ModifyTicket(conn, ticketName, "remove", "group", groupName)
  1336  }
  1337  
  1338  // AddTicketAllowedHost adds a host to the allowed hosts list of the given ticket
  1339  func AddTicketAllowedHost(conn *connection.IRODSConnection, ticketName string, host string) error {
  1340  	return ModifyTicket(conn, ticketName, "add", "host", host)
  1341  }
  1342  
  1343  // RemoveTicketAllowedHost removes the host from the allowed hosts list of the given ticket
  1344  func RemoveTicketAllowedHost(conn *connection.IRODSConnection, ticketName string, host string) error {
  1345  	return ModifyTicket(conn, ticketName, "remove", "host", host)
  1346  }
  1347  
  1348  // ModifyTicketExpirationTime modifies the expiration time of the given ticket
  1349  func ModifyTicketExpirationTime(conn *connection.IRODSConnection, ticketName string, expirationTime time.Time) error {
  1350  	expirationTimeString := util.GetIRODSDateTimeStringForTicket(expirationTime)
  1351  
  1352  	return ModifyTicket(conn, ticketName, "expire", expirationTimeString)
  1353  }
  1354  
  1355  // ClearTicketExpirationTime clears the expiration time of the given ticket
  1356  func ClearTicketExpirationTime(conn *connection.IRODSConnection, ticketName string) error {
  1357  	return ModifyTicketExpirationTime(conn, ticketName, time.Time{})
  1358  }
  1359  
  1360  // SupplyTicket supplies a ticket to obtain access
  1361  func SupplyTicket(conn *connection.IRODSConnection, ticketName string) error {
  1362  	// lock the connection
  1363  	conn.Lock()
  1364  	defer conn.Unlock()
  1365  
  1366  	req := message.NewIRODSMessageTicketAdminRequest("session", ticketName)
  1367  	err := conn.RequestAndCheck(req, &message.IRODSMessageAdminResponse{}, nil)
  1368  	if err != nil {
  1369  		return xerrors.Errorf("received supply ticket error: %w", err)
  1370  	}
  1371  	return nil
  1372  }