From f42222227fc1bacc3e6de736152f878c9c343aee Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 4 May 2026 12:08:59 +0200 Subject: [PATCH 01/21] update dependencies --- go.mod | 38 +++++++----- go.sum | 75 +++++++++++++---------- internal/utils/tablewriter/tablewriter.go | 4 +- 3 files changed, 69 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index 028463d..db58d74 100644 --- a/go.mod +++ b/go.mod @@ -1,42 +1,50 @@ module github.com/oidc-mytoken/client -go 1.22 +go 1.25.0 require ( github.com/Songmu/prompter v0.5.1 - github.com/gliderlabs/ssh v0.3.7 - github.com/mdp/qrterminal/v3 v3.2.0 + github.com/gliderlabs/ssh v0.3.8 + github.com/mdp/qrterminal/v3 v3.2.1 github.com/oidc-mytoken/api v0.11.1 github.com/oidc-mytoken/lib v0.7.1 github.com/oidc-mytoken/utils v0.1.2 - github.com/olekukonko/tablewriter v0.0.5 + github.com/olekukonko/tablewriter v1.1.4 github.com/pkg/errors v0.9.1 - github.com/sirupsen/logrus v1.9.3 - github.com/urfave/cli/v2 v2.3.1-0.20211205195634-e8d81738896c - golang.org/x/net v0.27.0 - golang.org/x/term v0.22.0 + github.com/sirupsen/logrus v1.9.4 + github.com/urfave/cli/v2 v2.27.7 + golang.org/x/net v0.53.0 + golang.org/x/term v0.42.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/andybalholm/brotli v1.0.4 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/coreos/go-oidc/v3 v3.4.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fatih/color v1.19.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect + github.com/goccy/go-json v0.10.6 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/klauspost/compress v1.15.9 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/russross/blackfriday/v2 v2.0.1 // indirect - github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.22 // indirect + github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect + github.com/olekukonko/errors v1.3.0 // indirect + github.com/olekukonko/ll v0.1.8 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.41.0 // indirect - golang.org/x/crypto v0.25.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/sys v0.43.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect diff --git a/go.sum b/go.sum index 5196f08..559ea94 100644 --- a/go.sum +++ b/go.sum @@ -67,10 +67,16 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -82,8 +88,9 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g= github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= +github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -99,14 +106,18 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go. github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= +github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= +github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE= -github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8= +github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= +github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY= github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -205,25 +216,29 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mdp/qrterminal/v3 v3.2.0 h1:qteQMXO3oyTK4IHwj2mWsKYYRBOp1Pj2WRYFYYNTCdk= -github.com/mdp/qrterminal/v3 v3.2.0/go.mod h1:XGGuua4Lefrl7TLEsSONiD+UEjQXJZ4mPzF+gWYIJkk= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= +github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= +github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= +github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= github.com/oidc-mytoken/api v0.9.1/go.mod h1:DBIlUbaIgGlf607VZx8zFC97VR3WNN0kaMVO1AqyTdE= -github.com/oidc-mytoken/api v0.10.0 h1:+LCMb7et/roU+Baifho9bcOOAoqDzMSOJt4T1EgI5nk= -github.com/oidc-mytoken/api v0.10.0/go.mod h1:DS2/0gUjt84jVq/4jqs7mSoUtv1DDEp3xexF7HW1qe4= github.com/oidc-mytoken/api v0.11.1 h1:znpLzfXruM627lH0bjxoJEMRv2fCWptXmlqFRUjgwQ0= github.com/oidc-mytoken/api v0.11.1/go.mod h1:bd7obYvztiIQW1PoRVBTOg8/clWlauNGwcZEu5mRbwg= -github.com/oidc-mytoken/lib v0.7.0 h1:/uPQ10ColbCZ79wotV6aa3yfU6Lbaucis2+TrfVIXdQ= -github.com/oidc-mytoken/lib v0.7.0/go.mod h1:D0vrMn1szss52mJKwI+sTsSakVCP6qCk0l0kAjd4YtA= github.com/oidc-mytoken/lib v0.7.1 h1:inqnng/iVU42TU8Cuac7wP8I+JraTYmWMKSXdZvShfU= github.com/oidc-mytoken/lib v0.7.1/go.mod h1:LLDCpHCFzyodYLWSf69FDI4cvql/8B2sdKhVjmOBPgI= github.com/oidc-mytoken/utils v0.1.2 h1:nmKIw/k4OkQ/9ww06W7krJ4yTwvDbuCRCjjRCmI9gSE= github.com/oidc-mytoken/utils v0.1.2/go.mod h1:pAUlvF/mYz8CjinCx4iwoWzG514B9EhuCpu7O+afaD8= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= +github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6/go.mod h1:rEKTHC9roVVicUIfZK7DYrdIoM0EOr8mK1Hj5s3JjH0= +github.com/olekukonko/errors v1.3.0 h1:teJvgLGUEqMzBUms+Dj3/3szNqCG/Jdw9iDbum8fR6U= +github.com/olekukonko/errors v1.3.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8= +github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= +github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= +github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -232,13 +247,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -249,10 +264,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= @@ -279,8 +293,8 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -362,8 +376,8 @@ golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -458,13 +472,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= +golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/utils/tablewriter/tablewriter.go b/internal/utils/tablewriter/tablewriter.go index 200787e..2b2aa06 100644 --- a/internal/utils/tablewriter/tablewriter.go +++ b/internal/utils/tablewriter/tablewriter.go @@ -12,8 +12,8 @@ func PrintTable(headers []string, data [][]string) { func FPrintTable(out *os.File, headers []string, data [][]string) { t := tablewriter.NewWriter(out) - t.SetHeader(headers) - t.AppendBulk(data) + t.Header(headers) + t.Bulk(data) t.Render() } From 872f8f9b8026332b4dce90b77b0b1993dd1b0836 Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 4 May 2026 13:17:49 +0200 Subject: [PATCH 02/21] update to urfave/cli v3 and refactor code to align with new API changes --- go.mod | 8 +- go.sum | 15 +--- internal/commands/at.go | 23 +++--- internal/commands/general.go | 13 ++-- internal/commands/grants.go | 21 +++--- internal/commands/info.go | 15 ++-- internal/commands/list.go | 7 +- internal/commands/mt.go | 133 ++++++++++++++-------------------- internal/commands/options.go | 82 ++++++++++----------- internal/commands/revoke.go | 17 ++--- internal/commands/settings.go | 2 +- internal/commands/sshGrant.go | 29 ++++---- 12 files changed, 164 insertions(+), 201 deletions(-) diff --git a/go.mod b/go.mod index db58d74..d0ca9a8 100644 --- a/go.mod +++ b/go.mod @@ -12,9 +12,8 @@ require ( github.com/olekukonko/tablewriter v1.1.4 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 - github.com/urfave/cli/v2 v2.27.7 + github.com/urfave/cli/v3 v3.5.0 golang.org/x/net v0.53.0 - golang.org/x/term v0.42.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -25,7 +24,6 @@ require ( github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect github.com/coreos/go-oidc/v3 v3.4.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fatih/color v1.19.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // indirect @@ -39,16 +37,14 @@ require ( github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.3.0 // indirect github.com/olekukonko/ll v0.1.8 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.41.0 // indirect golang.org/x/crypto v0.50.0 // indirect golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect golang.org/x/sys v0.43.0 // indirect + golang.org/x/term v0.42.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/square/go-jose.v2 v2.6.0 // indirect rsc.io/qr v0.2.0 // indirect ) - -replace github.com/urfave/cli/v2 => github.com/zachmann/cli/v2 v2.3.1-0.20211220102037-d619fd40a704 diff --git a/go.sum b/go.sum index 559ea94..558b4e9 100644 --- a/go.sum +++ b/go.sum @@ -88,9 +88,6 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-oidc/v3 v3.4.0 h1:xz7elHb/LDwm/ERpwHd+5nb7wFHL32rsr6bBOgaeu6g= github.com/coreos/go-oidc/v3 v3.4.0/go.mod h1:eHUXhZtXPQLgEaDrOVTgwbgmz1xGOkJNye6h3zkD2Pw= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= -github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -247,10 +244,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= @@ -265,8 +258,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw= +github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= @@ -277,8 +272,6 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/zachmann/cli/v2 v2.3.1-0.20211220102037-d619fd40a704 h1:dpR/K16zQgc/5xTQ66RevZikjkFzsCt6IeNDFyHltaM= -github.com/zachmann/cli/v2 v2.3.1-0.20211220102037-d619fd40a704/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= diff --git a/internal/commands/at.go b/internal/commands/at.go index d9f052c..db3bea8 100644 --- a/internal/commands/at.go +++ b/internal/commands/at.go @@ -1,11 +1,12 @@ package commands import ( + "context" "os" "github.com/oidc-mytoken/api/v0" mytokenlib "github.com/oidc-mytoken/lib" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" cutils "github.com/oidc-mytoken/client/internal/utils" @@ -13,8 +14,8 @@ import ( var atCommand = struct { MTOptions - Scopes cli.StringSlice - Audiences cli.StringSlice + Scopes []string + Audiences []string Out string }{} @@ -35,42 +36,40 @@ func init() { Usage: "Request the passed scope.", DefaultText: "all scopes allowed for the used mytoken", Destination: &atCommand.Scopes, - Placeholder: "SCOPE", }, &cli.StringSliceFlag{ Name: "aud", Aliases: []string{"audience"}, Usage: "Request the passed audience.", Destination: &atCommand.Audiences, - Placeholder: "AUD", }, &cli.StringFlag{ Name: "out", Aliases: []string{"o"}, - Usage: "The access token will be printed to this output", + Usage: "The access token will be printed to this `FILE`", + TakesFile: true, Value: os.Stdout.Name(), Destination: &atCommand.Out, - Placeholder: "FILE", }, ), }, ) } -func getAT(context *cli.Context) error { +func getAT(ctx context.Context, cmd *cli.Command) error { atc := atCommand var comment string - if context.Args().Len() > 0 { - comment = context.Args().Get(0) + if cmd.Args().Len() > 0 { + comment = cmd.Args().Get(0) } if ssh := atc.SSH(); ssh != "" { - req := mytokenlib.NewAccessTokenRequest("", "", atc.Scopes.Value(), atc.Audiences.Value(), comment) + req := mytokenlib.NewAccessTokenRequest("", "", atc.Scopes, atc.Audiences, comment) return doSSH(ssh, api.SSHRequestAccessToken, req) } mToken := atc.MustGetToken() mytoken := config.Get().Mytoken() atRes, err := mytoken.AccessToken.APIGet( - mToken, "", atc.Scopes.Value(), atc.Audiences.Value(), comment, + mToken, "", atc.Scopes, atc.Audiences, comment, ) if err != nil { return err diff --git a/internal/commands/general.go b/internal/commands/general.go index 10e5519..cab8e93 100644 --- a/internal/commands/general.go +++ b/internal/commands/general.go @@ -11,7 +11,7 @@ import ( "github.com/oidc-mytoken/utils/utils/jwtutils" "github.com/oidc-mytoken/utils/utils/ternary" log "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/utils/wlcgtokendiscovery" @@ -111,15 +111,15 @@ func getMTFlags() []cli.Flag { Destination: &opts.Mytoken, }, &cli.BoolFlag{ - Name: "MT-prompt", - Usage: "If set, you are prompted for a mytoken to be passed", - Destination: &opts.MytokenPrompt, - HideDefaultValue: true, + Name: "MT-prompt", + Usage: "If set, you are prompted for a mytoken to be passed", + Destination: &opts.MytokenPrompt, }, &cli.StringFlag{ Name: "MT-file", Usage: "Read the mytoken that should be used from the first line of the passed `FILE`", TakesFile: true, + Sources: cli.NewValueSourceChain(cli.File("")), Destination: &opts.MytokenFile, }, &cli.StringFlag{ @@ -131,8 +131,7 @@ func getMTFlags() []cli.Flag { &cli.StringFlag{ Name: "ssh", Usage: "Use the ssh protocol instead of a mytoken. " + - "SSH will be passed as the first argument to the ssh client", - Placeholder: "SSH", + "`SSH` will be passed as the first argument to the ssh client", Destination: &opts.SSH, }, } diff --git a/internal/commands/grants.go b/internal/commands/grants.go index d48edcb..a857ead 100644 --- a/internal/commands/grants.go +++ b/internal/commands/grants.go @@ -1,10 +1,11 @@ package commands import ( + "context" "fmt" "github.com/oidc-mytoken/api/v0" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/utils/tablewriter" @@ -18,7 +19,7 @@ func initGrants(parent *cli.Command) { Aliases: []string{"grant-types"}, Usage: "View and manage your user grant types", Flags: grantsFlags, - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "list", Aliases: []string{"view"}, @@ -42,11 +43,11 @@ func initGrants(parent *cli.Command) { }, }, } - parent.Subcommands = append(parent.Subcommands, cmd) + parent.Commands = append(parent.Commands, cmd) initSSHGrant(cmd) } -func listGrants(_ *cli.Context) error { +func listGrants(_ context.Context, _ *cli.Command) error { mytoken := settingsOptions.MustGetToken() res, err := config.Get().Mytoken().UserSettings.Grants.APIGet(mytoken) if err != nil { @@ -85,11 +86,11 @@ func (i tableGrantTypeInfo) TableGetRow() []string { } } -func enableGrant(ctx *cli.Context) error { - if ctx.Args().Len() != 1 { +func enableGrant(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() != 1 { return fmt.Errorf("Must provide exactly one grant to enable") } - grant := ctx.Args().Get(0) + grant := cmd.Args().Get(0) mytoken := settingsOptions.MustGetToken() res, err := config.Get().Mytoken().UserSettings.Grants.APIEnableGrant(mytoken, grant) if err != nil { @@ -102,11 +103,11 @@ func enableGrant(ctx *cli.Context) error { return nil } -func disableGrant(ctx *cli.Context) error { - if ctx.Args().Len() != 1 { +func disableGrant(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() != 1 { return fmt.Errorf("Must provide exactly one grant to disable") } - grant := ctx.Args().Get(0) + grant := cmd.Args().Get(0) mytoken := settingsOptions.MustGetToken() res, err := config.Get().Mytoken().UserSettings.Grants.APIDisableGrant(mytoken, grant) if err != nil { diff --git a/internal/commands/info.go b/internal/commands/info.go index a9cfe8f..d478314 100644 --- a/internal/commands/info.go +++ b/internal/commands/info.go @@ -2,6 +2,7 @@ package commands import ( "bytes" + "context" "encoding/base64" "encoding/json" "fmt" @@ -10,7 +11,7 @@ import ( "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/utils/utils/jwtutils" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/utils/tablewriter" @@ -28,7 +29,7 @@ func init() { Usage: "Get information about a mytoken", Action: info, Flags: cmdFlags, - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "history", Usage: "List the event history for this token", @@ -90,7 +91,7 @@ func prettyPrintJSON(obj interface{}) error { return nil } -func info(_ *cli.Context) error { +func info(_ context.Context, _ *cli.Command) error { mToken := infoOptions.MustGetToken() if !jwtutils.IsJWT(mToken) { return fmt.Errorf("The token is not a JWT.") @@ -103,7 +104,7 @@ func info(_ *cli.Context) error { return prettyPrintJSON(decodedPayload) } -func introspect(_ *cli.Context) error { +func introspect(_ context.Context, _ *cli.Command) error { if ssh := infoOptions.SSH(); ssh != "" { res, err := doSSHReturnOutput(ssh, api.SSHRequestTokenInfoIntrospect, nil) if err != nil { @@ -120,7 +121,7 @@ func introspect(_ *cli.Context) error { return prettyPrintJSON(res) } -func history(_ *cli.Context) (err error) { +func history(_ context.Context, _ *cli.Command) (err error) { var res api.TokeninfoHistoryResponse if ssh := infoOptions.SSH(); ssh != "" { var resStr string @@ -173,7 +174,7 @@ func (e tableEventEntry) TableGetRow() []string { } } -func subTree(_ *cli.Context) (err error) { +func subTree(_ context.Context, _ *cli.Command) (err error) { var res api.TokeninfoSubtokensResponse if ssh := infoOptions.SSH(); ssh != "" { var resStr string @@ -199,7 +200,7 @@ func subTree(_ *cli.Context) (err error) { return prettyPrintJSON(res.Tokens) } -func listMytokens(_ *cli.Context) (err error) { +func listMytokens(_ context.Context, _ *cli.Command) (err error) { var res api.TokeninfoListResponse if ssh := infoOptions.SSH(); ssh != "" { var resStr string diff --git a/internal/commands/list.go b/internal/commands/list.go index 2c0132d..5574b61 100644 --- a/internal/commands/list.go +++ b/internal/commands/list.go @@ -1,10 +1,11 @@ package commands import ( + "context" "fmt" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" ) @@ -14,7 +15,7 @@ func init() { &cli.Command{ Name: "list", Usage: "List different information", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "providers", Aliases: []string{"issuers"}, @@ -26,7 +27,7 @@ func init() { app.Commands = append(app.Commands, cmd) } -func listProviders(_ *cli.Context) error { +func listProviders(_ context.Context, _ *cli.Command) error { defaultProvider := config.Get().DefaultProvider instanceProviders := config.Get().Mytoken().ServerMetadata.ProvidersSupported urlMaxLen := 0 diff --git a/internal/commands/mt.go b/internal/commands/mt.go index a49c7e9..b5a4be3 100644 --- a/internal/commands/mt.go +++ b/internal/commands/mt.go @@ -1,6 +1,7 @@ package commands import ( + "context" "encoding/json" "fmt" "os" @@ -12,7 +13,7 @@ import ( "github.com/oidc-mytoken/utils/utils/jsonutils" "github.com/oidc-mytoken/utils/utils/profile" "github.com/oidc-mytoken/utils/utils/timerestriction" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" cutils "github.com/oidc-mytoken/client/internal/utils" @@ -21,13 +22,13 @@ import ( type restrictionOpts struct { Restrictions string - RestrictScopes cli.StringSlice - RestrictAudiences cli.StringSlice + RestrictScopes []string + RestrictAudiences []string RestrictExp string RestrictNbf string - RestrictIP cli.StringSlice - RestrictGeoIPAllow cli.StringSlice - RestrictGeoIPDisallow cli.StringSlice + RestrictIP []string + RestrictGeoIPAllow []string + RestrictGeoIPDisallow []string RestrictUsagesOther int64 RestrictUsagesAT int64 } @@ -121,21 +122,7 @@ func (opts *mtOpts) parseRotationOption() error { return nil } -func parseRestrictionOpts(rOpts restrictionOpts, ctx *cli.Context) (api.Restrictions, error) { - mto := mtOpts{ - profileOpts: profileOpts{ - restrictionOpts: rOpts, - }, - request: &api.GeneralMytokenRequest{}, - } - err := mto.parseRestrictionOpts(ctx) - if err != nil { - return nil, err - } - return mto.request.Restrictions, nil -} - -func (opts *mtOpts) parseRestrictionOpts(ctx *cli.Context) (err error) { +func (opts *mtOpts) parseRestrictionOpts(cmd *cli.Command) (err error) { if opts.Restrictions != "" { rBytes := []byte(opts.Restrictions) if jsonutils.IsJSONObject(rBytes) { @@ -165,16 +152,16 @@ func (opts *mtOpts) parseRestrictionOpts(ctx *cli.Context) (err error) { rr := &api.Restriction{ NotBefore: nbf, ExpiresAt: exp, - Scope: strings.Join(opts.RestrictScopes.Value(), " "), - Audiences: opts.RestrictAudiences.Value(), - Hosts: opts.RestrictIP.Value(), - GeoIPAllow: opts.RestrictGeoIPAllow.Value(), - GeoIPDisallow: opts.RestrictGeoIPDisallow.Value(), + Scope: strings.Join(opts.RestrictScopes, " "), + Audiences: opts.RestrictAudiences, + Hosts: opts.RestrictIP, + GeoIPAllow: opts.RestrictGeoIPAllow, + GeoIPDisallow: opts.RestrictGeoIPDisallow, } - if ctx.IsSet("usages-AT") { + if cmd.IsSet("usages-AT") { rr.UsagesAT = utils.NewInt64(opts.RestrictUsagesAT) } - if ctx.IsSet("usages-other") { + if cmd.IsSet("usages-other") { rr.UsagesOther = utils.NewInt64(opts.RestrictUsagesOther) } if rr.UsagesAT != nil || rr.UsagesOther != nil || rr.NotBefore != 0 || rr.ExpiresAt != 0 || rr.Scope != "" || @@ -184,7 +171,21 @@ func (opts *mtOpts) parseRestrictionOpts(ctx *cli.Context) (err error) { return } -func (opts *mtOpts) Request(ctx *cli.Context) (*api.GeneralMytokenRequest, error) { +func parseRestrictionOpts(rOpts restrictionOpts, cmd *cli.Command) (api.Restrictions, error) { + mto := mtOpts{ + profileOpts: profileOpts{ + restrictionOpts: rOpts, + }, + request: &api.GeneralMytokenRequest{}, + } + err := mto.parseRestrictionOpts(cmd) + if err != nil { + return nil, err + } + return mto.request.Restrictions, nil +} + +func (opts *mtOpts) Request(ctx context.Context, cmd *cli.Command) (*api.GeneralMytokenRequest, error) { if opts.request != nil { return opts.request, nil } @@ -216,7 +217,7 @@ func (opts *mtOpts) Request(ctx *cli.Context) (*api.GeneralMytokenRequest, error if err != nil { return nil, err } - err = opts.parseRestrictionOpts(ctx) + err = opts.parseRestrictionOpts(cmd) if err != nil { return nil, err } @@ -237,7 +238,6 @@ func getCapabilityFlag(c *string) cli.Flag { Usage: "Request the passed capabilities.", DefaultText: "from config file", Destination: c, - Placeholder: "CAPABILITY", } } @@ -248,12 +248,11 @@ func getRestrFlags(opts *restrictionOpts) []cli.Flag { Aliases: []string{"restriction"}, Usage: "The restrictions that restrict the requested mytoken. " + "Can be a json object or array, or a path to a json file.'", - EnvVars: []string{ + Sources: cli.EnvVars( "MYTOKEN_RESTRICTIONS", "MYTOKEN_RESTRICTION", - }, + ), Destination: &opts.Restrictions, - Placeholder: "RESTRICTIONS", }, &cli.StringSliceFlag{ Name: "scope", @@ -264,7 +263,6 @@ func getRestrFlags(opts *restrictionOpts) []cli.Flag { Usage: "Restrict the mytoken so that it can only be used to request ATs with these SCOPES. " + "Can be used multiple times. Overwritten by --restriction.", Destination: &opts.RestrictScopes, - Placeholder: "SCOPE", }, &cli.StringSliceFlag{ Name: "aud", @@ -275,7 +273,6 @@ func getRestrFlags(opts *restrictionOpts) []cli.Flag { Usage: "Restrict the mytoken so that it can only be used to request ATs with these audiences. " + "Can be used multiple times. Overwritten by --restriction.", Destination: &opts.RestrictAudiences, - Placeholder: "AUD", }, &cli.StringFlag{ Name: "exp", @@ -303,21 +300,18 @@ func getRestrFlags(opts *restrictionOpts) []cli.Flag { Usage: "Restrict the mytoken so that it can only be used from these hosts. " + "Can be a network address block, a single ip, or a host name.", Destination: &opts.RestrictIP, - Placeholder: "IP", }, &cli.StringSliceFlag{ Name: "geo-ip-allow", Usage: "Restrict the mytoken so that it can be only used from these COUNTRIES. " + "Must be a short country code, e.g. 'us'.", Destination: &opts.RestrictGeoIPAllow, - Placeholder: "COUNTRY", }, &cli.StringSliceFlag{ Name: "geo-ip-disallow", Usage: "Restrict the mytoken so that it cannot be used from these COUNTRIES. " + "Must be a short country code, e.g. 'us'.", Destination: &opts.RestrictGeoIPDisallow, - Placeholder: "COUNTRY", }, &cli.Int64Flag{ Name: "usages-AT", @@ -325,14 +319,12 @@ func getRestrFlags(opts *restrictionOpts) []cli.Flag { Usage: "Restrict how often the mytoken can be used for requesting an access token.", DefaultText: "infinite", Destination: &opts.RestrictUsagesAT, - Placeholder: "NUM", }, &cli.Int64Flag{ Name: "usages-other", Usage: "Restrict how often the mytoken can be used for actions other than requesting an access token.", DefaultText: "infinite", Destination: &opts.RestrictUsagesOther, - Placeholder: "NUM", }, } } @@ -342,9 +334,8 @@ func getRotationFlags(rotStr *string, rot *api.Rotation) []cli.Flag { Name: "rotation", Aliases: []string{"rotate"}, Usage: "The rotation policy for the requested mytoken. Can be a json object or a path to a json file.'", - EnvVars: []string{"MYTOKEN_ROTATION"}, + Sources: cli.EnvVars("MYTOKEN_ROTATION"), Destination: rotStr, - Placeholder: "ROTATION", }, &cli.BoolFlag{ Name: "rotation-on-AT", @@ -353,30 +344,26 @@ func getRotationFlags(rotStr *string, rot *api.Rotation) []cli.Flag { "rotate-on-at", "rotation-on-at", }, - Usage: "Rotate this mytoken when it is used to obtain access tokens", - Destination: &rot.OnAT, - HideDefaultValue: true, + Usage: "Rotate this mytoken when it is used to obtain access tokens", + Destination: &rot.OnAT, }, &cli.BoolFlag{ - Name: "rotation-on-other", - Aliases: []string{"rotate-on-other"}, - Usage: "Rotate this mytoken when it is used for actions other than obtaining access tokens", - Destination: &rot.OnOther, - HideDefaultValue: true, + Name: "rotation-on-other", + Aliases: []string{"rotate-on-other"}, + Usage: "Rotate this mytoken when it is used for actions other than obtaining access tokens", + Destination: &rot.OnOther, }, &cli.BoolFlag{ Name: "rotation-auto-revoke", Usage: "If set, " + "the mytoken and all it subtokens are automatically revoked when a potential abuse is detected", - Destination: &rot.AutoRevoke, - HideDefaultValue: true, + Destination: &rot.AutoRevoke, }, &cli.Uint64Flag{ Name: "rotation-lifetime", Usage: "Restrict the lifetime of a single rotated mytoken; given in seconds", DefaultText: "infinite", Destination: &rot.Lifetime, - Placeholder: "LIFETIME", }, } } @@ -391,13 +378,10 @@ func init() { append( []cli.Flag{ &cli.StringFlag{ - Name: "profile", - Usage: "A mytoken profile describing the properties of the mytoken to be requested", - EnvVars: []string{ - "MYTOKEN_PROFILE", - }, + Name: "profile", + Usage: "A mytoken profile describing the properties of the mytoken to be requested", + Sources: cli.EnvVars("MYTOKEN_PROFILE"), Destination: &mtCommand.profile, - Placeholder: "PROFILE", }, }, getRestrFlags(&mtCommand.restrictionOpts)..., @@ -405,14 +389,13 @@ func init() { &cli.StringFlag{ Name: "TC", Usage: "Use the passed `TRANSFER_CODE` to exchange it into a mytoken", - EnvVars: []string{"MYTOKEN_TC"}, + Sources: cli.EnvVars("MYTOKEN_TC"), Destination: &mtCommand.TransferCode, }, &cli.BoolFlag{ - Name: "oidc", - Usage: "Use an OpenID Connect flow to create a mytoken", - Destination: &mtCommand.UseOIDCFlow, - HideDefaultValue: true, + Name: "oidc", + Usage: "Use an OpenID Connect flow to create a mytoken", + Destination: &mtCommand.UseOIDCFlow, }, &cli.StringFlag{ Name: "provider", @@ -422,9 +405,8 @@ func init() { }, Usage: "The name or issuer url of the OpenID provider that should be used; only needed if mytoken is" + " obtained through OIDC", - EnvVars: []string{"MYTOKEN_PROVIDER"}, + Sources: cli.EnvVars("MYTOKEN_PROVIDER"), Destination: &mtCommand.provider, - Placeholder: "PROVIDER", }, getCapabilityFlag(&mtCommand.CapabilitiesStr), ) @@ -436,23 +418,20 @@ func init() { Aliases: []string{"n"}, Usage: "A name for the returned mytoken; used for finding the token in a list of mytokens.", Destination: &mtCommand.Name, - Placeholder: "NAME", }, - &cli.ChoiceFlag{ + &cli.StringFlag{ Name: "token-type", Usage: "The type of the returned token.", Value: "token", - Choice: cli.NewStringChoice(api.ResponseTypeToken, cmdArgTokenTypeShort, cmdArgTokenTypeTransfer), Destination: &mtCommand.TokenType, - Placeholder: "TYPE", }, &cli.StringFlag{ Name: "out", Aliases: []string{"o"}, - Usage: "The mytoken will be printed to this output", + Usage: "The mytoken will be printed to this `FILE`", + TakesFile: true, Value: os.Stdout.Name(), Destination: &mtCommand.Out, - Placeholder: "FILE", }, ) app.Commands = append( @@ -469,20 +448,20 @@ func init() { ) } -func obtainMTCmd(context *cli.Context) error { - mt, err := obtainMT(context) +func obtainMTCmd(ctx context.Context, cmd *cli.Command) error { + mt, err := obtainMT(ctx, cmd) if err != nil { return err } return cutils.WriteOutput(mtCommand.Out, mt) } -func obtainMT(context *cli.Context) (string, error) { +func obtainMT(ctx context.Context, cmd *cli.Command) (string, error) { mytoken := config.Get().Mytoken() if mtCommand.TransferCode != "" { return mytoken.Mytoken.FromTransferCode(mtCommand.TransferCode) } - req, err := mtCommand.Request(context) + req, err := mtCommand.Request(ctx, cmd) if err != nil { return "", err } diff --git a/internal/commands/options.go b/internal/commands/options.go index b14991f..1edb15c 100644 --- a/internal/commands/options.go +++ b/internal/commands/options.go @@ -1,94 +1,88 @@ package commands import ( + "context" "os" - "time" log "github.com/sirupsen/logrus" - "github.com/urfave/cli/v2" - "golang.org/x/term" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/model/version" ) -var app = &cli.App{ - Name: "mytoken", - Usage: "Command line client for the mytoken server", - Version: version.VERSION, - Compiled: time.Time{}, - Authors: []*cli.Author{ - { - Name: "Gabriel Zachmann", - Email: "gabriel.zachmann@kit.edu", - }, +var app = &cli.Command{ + Name: "mytoken", + Usage: "Command line client for the mytoken server", + Version: version.VERSION, + Authors: []any{ + "Gabriel Zachmann ", + }, + Copyright: "Karlsruhe Institute of Technology 2020-2022", + Action: func(ctx context.Context, cmd *cli.Command) error { + return cli.ShowAppHelp(cmd) }, - Copyright: "Karlsruhe Institute of Technology 2020-2022", - UseShortOptionHandling: true, } var configFile string func init() { - cli.AppHelpTemplate = `NAME: - {{$v := offset .Name 6}}{{wrap .Name 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}} + cli.RootCommandHelpTemplate = `NAME: + {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Description}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{wrap .Description 3}}{{end}}{{if .VisibleCommands}} + {{template "descriptionTemplate" .}}{{end}} +{{- if len .Authors}} -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} +AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option.String}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} +COMMANDS:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}} -VERSION: - {{.Version}}{{end}}{{end}} +GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}} -DOCUMENTATION: - https://mytoken-docs.data.kit.edu/client/intro +GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} -CONTACT: - m-contact@lists.kit.edu -` +COPYRIGHT: + {{template "copyrightTemplate" .}}{{end}} - termWidth, _, err := term.GetSize(int(os.Stdout.Fd())) - if err == nil { - cli.HelpWrapAt = termWidth - } +DOCUMENTATION: + https://mytoken-docs.data.kit.edu/client/intro + +CONTACT: + m-contact@lists.kit.edu +` app.Flags = append( app.Flags, &cli.StringFlag{ Name: "config", Usage: "Load configuration from `FILE`", - EnvVars: []string{ + Sources: cli.EnvVars( "MYTOKEN_CONFIG", "MYTOKEN_CONF", - }, + ), TakesFile: true, - DefaultText: "", Destination: &configFile, }, ) - app.Before = func(context *cli.Context) error { - if context.IsSet("config") { + app.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) { + if cmd.IsSet("config") { config.Load(configFile) } else { config.LoadDefault() } - return nil + return ctx, nil } } // Parse parses the command line options and calls the specified command func Parse() { - if err := app.Run(os.Args); err != nil { + if err := app.Run(context.Background(), os.Args); err != nil { log.Fatal(err) } } diff --git a/internal/commands/revoke.go b/internal/commands/revoke.go index da1b2f8..f48713c 100644 --- a/internal/commands/revoke.go +++ b/internal/commands/revoke.go @@ -1,10 +1,11 @@ package commands import ( + "context" "fmt" "github.com/oidc-mytoken/api/v0" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" ) @@ -23,11 +24,10 @@ func init() { Action: revoke, Flags: appendMTFlags( &cli.BoolFlag{ - Name: "recursive", - Aliases: []string{"r"}, - Usage: "If set, also all subtokens are revoked", - Destination: &revokeCommand.Recursive, - HideDefaultValue: true, + Name: "recursive", + Aliases: []string{"r"}, + Usage: "If set, also all subtokens are revoked", + Destination: &revokeCommand.Recursive, }, &cli.StringFlag{ Name: "mom-id", @@ -42,16 +42,15 @@ func init() { "This requires that the token linked to the mom id is either a child of the actual mytoken or"+ " the actual mytoken has the %s capability.", api.CapabilityRevokeAnyToken, ), - EnvVars: []string{"MOM_ID"}, + Sources: cli.EnvVars("MOM_ID"), Destination: &revokeCommand.MOMID, - Placeholder: "MOM_ID", }, ), }, ) } -func revoke(_ *cli.Context) error { +func revoke(_ context.Context, _ *cli.Command) error { mToken := revokeCommand.MustGetToken() mytoken := config.Get().Mytoken() var err error diff --git a/internal/commands/settings.go b/internal/commands/settings.go index 05fc6f4..36831fb 100644 --- a/internal/commands/settings.go +++ b/internal/commands/settings.go @@ -1,7 +1,7 @@ package commands import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var settingsOptions MTOptions diff --git a/internal/commands/sshGrant.go b/internal/commands/sshGrant.go index 6135891..c2b4b4d 100644 --- a/internal/commands/sshGrant.go +++ b/internal/commands/sshGrant.go @@ -1,6 +1,7 @@ package commands import ( + "context" "fmt" "os" "strings" @@ -12,7 +13,7 @@ import ( "github.com/oidc-mytoken/utils/utils/fileutil" "github.com/oidc-mytoken/utils/utils/profile" "github.com/pkg/errors" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/utils/tablewriter" @@ -31,7 +32,7 @@ func initSSHGrant(parent *cli.Command) { Aliases: []string{"SSH"}, Usage: "View and manage the ssh grant", Flags: cmdFlags, - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ { Name: "list", Aliases: []string{"view"}, @@ -59,7 +60,7 @@ func initSSHGrant(parent *cli.Command) { Name: "no-write-host-entry", Usage: "If set, " + "the created host entry will not be automatically append to your ~/.ssh/config file.", - EnvVars: []string{"NO_WRITE_HOST_ENTRY"}, + Sources: cli.EnvVars("NO_WRITE_HOST_ENTRY"), Destination: &noWriteHostEntry, }, ), @@ -77,10 +78,10 @@ func initSSHGrant(parent *cli.Command) { }, }, } - parent.Subcommands = append(parent.Subcommands, cmd) + parent.Commands = append(parent.Commands, cmd) } -func listSSH(_ *cli.Context) error { +func listSSH(_ context.Context, _ *cli.Command) error { mytoken := settingsOptions.MustGetToken() res, err := config.Get().Mytoken().UserSettings.Grants.SSH.APIGet(mytoken) if err != nil { @@ -127,14 +128,14 @@ func (i tableSSHKeyInfo) TableGetRow() []string { } } -func addSSHKey(ctx *cli.Context) error { - if ctx.NArg() != 1 { - if ctx.NArg() > 1 { +func addSSHKey(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() != 1 { + if cmd.NArg() > 1 { return fmt.Errorf("Need exactly one argument") } return fmt.Errorf("Required argument SSH_KEY missing") } - keyArg := ctx.Args().Get(0) + keyArg := cmd.Args().Get(0) mytoken := settingsOptions.MustGetToken() key, err := detectKey(keyArg) if err != nil { @@ -166,7 +167,7 @@ func addSSHKey(ctx *cli.Context) error { if err != nil { return err } - restrictions, err := parseRestrictionOpts(optRestrictions, ctx) + restrictions, err := parseRestrictionOpts(optRestrictions, cmd) if err != nil { return err } @@ -201,14 +202,14 @@ func addSSHKey(ctx *cli.Context) error { return nil } -func deleteSSHKey(ctx *cli.Context) error { - if ctx.NArg() != 1 { - if ctx.NArg() > 1 { +func deleteSSHKey(ctx context.Context, cmd *cli.Command) error { + if cmd.NArg() != 1 { + if cmd.NArg() > 1 { return fmt.Errorf("Need exactly one argument") } return fmt.Errorf("Required argument SSH_KEY missing") } - keyArg := ctx.Args().Get(0) + keyArg := cmd.Args().Get(0) mytoken := settingsOptions.MustGetToken() var keyFP string var key string From 336c6decee72e8d40123ea448d4a1c07bfb5bbfd Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 4 May 2026 13:22:41 +0200 Subject: [PATCH 03/21] add support for specifying a custom mytoken server URL via CLI flag or environment variable --- internal/commands/options.go | 23 ++++++++++++++++++----- internal/config/config.go | 5 +++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/internal/commands/options.go b/internal/commands/options.go index 1edb15c..b8c6849 100644 --- a/internal/commands/options.go +++ b/internal/commands/options.go @@ -25,19 +25,20 @@ var app = &cli.Command{ } var configFile string +var mytokenURL string func init() { cli.RootCommandHelpTemplate = `NAME: - {{template "helpNameTemplate" .}} + {{template "helpNameTemplate" .}} USAGE: - {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + {{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.FullName}} {{if .VisibleFlags}}[global options]{{end}}{{if .VisibleCommands}} [command [command options]]{{end}}{{if .ArgsUsage}} {{.ArgsUsage}}{{else}}{{if .Arguments}} [arguments...]{{end}}{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} + {{.Version}}{{end}}{{end}}{{if .Description}} DESCRIPTION: - {{template "descriptionTemplate" .}}{{end}} + {{template "descriptionTemplate" .}}{{end}} {{- if len .Authors}} AUTHOR{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}} @@ -49,7 +50,7 @@ GLOBAL OPTIONS:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFla GLOBAL OPTIONS:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}} COPYRIGHT: - {{template "copyrightTemplate" .}}{{end}} + {{template "copyrightTemplate" .}}{{end}} DOCUMENTATION: https://mytoken-docs.data.kit.edu/client/intro @@ -69,6 +70,15 @@ CONTACT: TakesFile: true, Destination: &configFile, }, + &cli.StringFlag{ + Name: "url", + Aliases: []string{"mytoken-url"}, + Usage: "Use the given mytoken server `URL` instead of the one from the config file", + Sources: cli.EnvVars( + "MYTOKEN_URL", + ), + Destination: &mytokenURL, + }, ) app.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) { if cmd.IsSet("config") { @@ -76,6 +86,9 @@ CONTACT: } else { config.LoadDefault() } + if cmd.IsSet("url") { + config.SetURL(mytokenURL) + } return ctx, nil } } diff --git a/internal/config/config.go b/internal/config/config.go index 70b6fe4..083e4c9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -61,6 +61,11 @@ func (c *Config) SetMytokenServer(mytoken *mytokenlib.MytokenServer) { c.mytoken = mytoken } +func SetURL(url string) { + conf.URL = url + conf.mytoken = nil +} + func load(name string, locations []string) { data, usedLocation, err := fileutil.ReadConfigFile(name, locations) if err != nil { From ff3f97b61c9fc908fe8f7902476a472a715366a9 Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 4 May 2026 13:40:34 +0200 Subject: [PATCH 04/21] update copyright years in LICENSE and code files --- LICENSE | 2 +- internal/commands/options.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 4966821..8392bc4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020-2024 Gabriel Zachmann +Copyright (c) 2020-2026 Gabriel Zachmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/internal/commands/options.go b/internal/commands/options.go index b8c6849..da545b8 100644 --- a/internal/commands/options.go +++ b/internal/commands/options.go @@ -18,7 +18,7 @@ var app = &cli.Command{ Authors: []any{ "Gabriel Zachmann ", }, - Copyright: "Karlsruhe Institute of Technology 2020-2022", + Copyright: "Karlsruhe Institute of Technology 2020-2026", Action: func(ctx context.Context, cmd *cli.Command) error { return cli.ShowAppHelp(cmd) }, From 60e7350ab15c98bee787b5530e0683bbdca71e9a Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 4 May 2026 16:22:48 +0200 Subject: [PATCH 05/21] add "config" and "capabilities" commands to CLI for retrieving server settings and capability templates --- internal/commands/config.go | 124 ++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 internal/commands/config.go diff --git a/internal/commands/config.go b/internal/commands/config.go new file mode 100644 index 0000000..bec39e1 --- /dev/null +++ b/internal/commands/config.go @@ -0,0 +1,124 @@ +package commands + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "text/tabwriter" + + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" +) + +func init() { + // Config command + configCmd := &cli.Command{ + Name: "config", + Usage: "Get server configuration", + Action: getConfig, + } + app.Commands = append(app.Commands, configCmd) + + // Capabilities command + capabilitiesCmd := &cli.Command{ + Name: "capabilities", + Usage: "List available capability templates", + Action: getCapabilities, + } + app.Commands = append(app.Commands, capabilitiesCmd) +} + +func getConfig(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + serverConfig := mtServer.ServerMetadata + + data, err := json.MarshalIndent(serverConfig, "", " ") + if err != nil { + return err + } + fmt.Println(string(data)) + return nil +} + +type CapabilityEntry struct { + ReadWriteCapability CapabilityInfo `json:"read_write_capability"` + ReadOnlyCapability *CapabilityInfo `json:"read_only_capability,omitempty"` + Children []CapabilityEntry `json:"children,omitempty"` +} + +type CapabilityInfo struct { + Name string `json:"name"` + Description string `json:"description"` + IsReadOnly *bool `json:"is_read_only,omitempty"` + ColorClass string `json:"color_class,omitempty"` + CapabilityLevel string `json:"capability_level,omitempty"` +} + +func getCapabilities(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + // Construct capabilities endpoint URL from server metadata + baseURL := mtServer.ServerMetadata.Issuer + var capsURL string + if baseURL[len(baseURL)-1] == '/' { + capsURL = baseURL + "api/v0/capabilities" + } else { + capsURL = baseURL + "/api/v0/capabilities" + } + + // Make HTTP request to capabilities endpoint + req, err := http.NewRequest("GET", capsURL, nil) + if err != nil { + return err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return fmt.Errorf("failed to fetch capabilities: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + var capabilities []CapabilityEntry + if err := json.Unmarshal(body, &capabilities); err != nil { + return fmt.Errorf("failed to decode capabilities: %w", err) + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "NAME\tDESCRIPTION") + fmt.Fprintln(w, "----\t-----------") + + printCapabilityTree(w, capabilities, "") + + w.Flush() + return nil +} + +func printCapabilityTree(w *tabwriter.Writer, capabilities []CapabilityEntry, indent string) { + for _, capEntry := range capabilities { + name := capEntry.ReadWriteCapability.Name + desc := capEntry.ReadWriteCapability.Description + + // Print read-write capability + fmt.Fprintf(w, "%s%s\t%s\n", indent, name, desc) + + // Print read-only capability if exists + if capEntry.ReadOnlyCapability != nil { + fmt.Fprintf(w, "%sread@%s\t%s\n", indent, name, capEntry.ReadOnlyCapability.Description) + } + + // Print children if any + if len(capEntry.Children) > 0 { + printCapabilityTree(w, capEntry.Children, indent+" ") + } + } +} From 17edd370001d94b4ac451fa9964b4ad6492722f8 Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 10:23:56 +0200 Subject: [PATCH 06/21] add "email," "tags," "profiles," and "mttags" commands to CLI for managing user settings, profiles, and mytoken tags --- go.mod | 4 +- go.sum | 8 +- internal/commands/email.go | 104 +++++++++++++++ internal/commands/general.go | 14 +++ internal/commands/info.go | 126 ++++++++++++++++++- internal/commands/mt.go | 27 ++++ internal/commands/mttags.go | 131 +++++++++++++++++++ internal/commands/profiles.go | 223 +++++++++++++++++++++++++++++++++ internal/commands/settings.go | 2 + internal/commands/tags.go | 191 ++++++++++++++++++++++++++++ internal/model/version/VERSION | 2 +- internal/utils/color/color.go | 116 +++++++++++++++++ 12 files changed, 939 insertions(+), 9 deletions(-) create mode 100644 internal/commands/email.go create mode 100644 internal/commands/mttags.go create mode 100644 internal/commands/profiles.go create mode 100644 internal/commands/tags.go create mode 100644 internal/utils/color/color.go diff --git a/go.mod b/go.mod index d0ca9a8..1f5e80c 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/Songmu/prompter v0.5.1 github.com/gliderlabs/ssh v0.3.8 github.com/mdp/qrterminal/v3 v3.2.1 - github.com/oidc-mytoken/api v0.11.1 + github.com/oidc-mytoken/api v0.12.0 github.com/oidc-mytoken/lib v0.7.1 github.com/oidc-mytoken/utils v0.1.2 github.com/olekukonko/tablewriter v1.1.4 @@ -48,3 +48,5 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect rsc.io/qr v0.2.0 // indirect ) + +replace github.com/oidc-mytoken/lib => ../lib diff --git a/go.sum b/go.sum index 558b4e9..62a7373 100644 --- a/go.sum +++ b/go.sum @@ -222,10 +222,8 @@ github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= github.com/oidc-mytoken/api v0.9.1/go.mod h1:DBIlUbaIgGlf607VZx8zFC97VR3WNN0kaMVO1AqyTdE= -github.com/oidc-mytoken/api v0.11.1 h1:znpLzfXruM627lH0bjxoJEMRv2fCWptXmlqFRUjgwQ0= -github.com/oidc-mytoken/api v0.11.1/go.mod h1:bd7obYvztiIQW1PoRVBTOg8/clWlauNGwcZEu5mRbwg= -github.com/oidc-mytoken/lib v0.7.1 h1:inqnng/iVU42TU8Cuac7wP8I+JraTYmWMKSXdZvShfU= -github.com/oidc-mytoken/lib v0.7.1/go.mod h1:LLDCpHCFzyodYLWSf69FDI4cvql/8B2sdKhVjmOBPgI= +github.com/oidc-mytoken/api v0.12.0 h1:PPK+0MNPHgJ0CcdF5DBhV+df2IrffhHG0nU7GrIh9hA= +github.com/oidc-mytoken/api v0.12.0/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= github.com/oidc-mytoken/utils v0.1.2 h1:nmKIw/k4OkQ/9ww06W7krJ4yTwvDbuCRCjjRCmI9gSE= github.com/oidc-mytoken/utils v0.1.2/go.mod h1:pAUlvF/mYz8CjinCx4iwoWzG514B9EhuCpu7O+afaD8= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= @@ -250,14 +248,12 @@ github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC4 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw= diff --git a/internal/commands/email.go b/internal/commands/email.go new file mode 100644 index 0000000..2be9ad0 --- /dev/null +++ b/internal/commands/email.go @@ -0,0 +1,104 @@ +package commands + +import ( + "context" + "fmt" + + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" +) + +var emailOptions = struct { + MTOptions + EmailAddress string + PreferHTMLMail bool + PreferPlainMail bool +}{} + +func initEmail(parent *cli.Command) { + cmd := &cli.Command{ + Name: "email", + Usage: "View and update your email preferences", + Commands: []*cli.Command{ + { + Name: "get", + Usage: "Get your email information", + Action: getEmail, + Flags: getMTFlags(), + }, + { + Name: "update", + Usage: "Update your email preferences", + Action: updateEmail, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "email", + Usage: "New email address", + Destination: &emailOptions.EmailAddress, + }, + &cli.BoolFlag{ + Name: "html", + Usage: "Prefer HTML mails over plain text", + Destination: &emailOptions.PreferHTMLMail, + }, + &cli.BoolFlag{ + Name: "plain", + Usage: "Prefer plain text mails over HTML", + Destination: &emailOptions.PreferPlainMail, + }, + ), + }, + }, + } + parent.Commands = append(parent.Commands, cmd) +} + +func getEmail(_ context.Context, _ *cli.Command) error { + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + res, err := mtServer.UserSettings.Email.APIGet(mytoken) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Println("Email Information:") + fmt.Printf(" Email Address: %s\n", res.EmailAddress) + fmt.Printf(" Verified: %v\n", res.EmailVerified) + fmt.Printf(" Prefer HTML Mail: %v\n", res.PreferHTMLMail) + return nil +} + +func updateEmail(_ context.Context, _ *cli.Command) error { + mytoken := settingsOptions.MustGetToken() + + if emailOptions.EmailAddress == "" && !emailOptions.PreferHTMLMail && !emailOptions.PreferPlainMail { + return fmt.Errorf("at least one of --email, --html, or --plain must be provided") + } + + var preferHTML *bool + if emailOptions.PreferHTMLMail { + val := true + preferHTML = &val + } else if emailOptions.PreferPlainMail { + val := false + preferHTML = &val + } + + mtServer := config.Get().Mytoken() + res, err := mtServer.UserSettings.Email.APIUpdate(mytoken, emailOptions.EmailAddress, preferHTML) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Println("Email preferences updated successfully") + return nil +} diff --git a/internal/commands/general.go b/internal/commands/general.go index cab8e93..95cb122 100644 --- a/internal/commands/general.go +++ b/internal/commands/general.go @@ -221,3 +221,17 @@ func updateMytoken(updatedToken string) { log.Error(err) } } + +func findCommand(commands []*cli.Command, name string) *cli.Command { + for _, c := range commands { + if c.Name == name { + return c + } + for _, alias := range c.Aliases { + if alias == name { + return c + } + } + } + return nil +} diff --git a/internal/commands/info.go b/internal/commands/info.go index d478314..17e7a58 100644 --- a/internal/commands/info.go +++ b/internal/commands/info.go @@ -14,11 +14,17 @@ import ( "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" + "github.com/oidc-mytoken/client/internal/utils/color" "github.com/oidc-mytoken/client/internal/utils/tablewriter" ) var infoOptions MTOptions +var infoNotificationsOptions = struct { + MTOptions + MOMIDs []string +}{} + func init() { cmdFlags := getMTFlags() subCmdFlags := getMTFlags() @@ -58,6 +64,19 @@ func init() { Action: listMytokens, Flags: subCmdFlags, }, + { + Name: "notifications", + Usage: "Get notifications and calendars for this token", + Action: infoNotifications, + Flags: append( + subCmdFlags, + &cli.StringSliceFlag{ + Name: "mom-id", + Usage: "Request notifications for specific mom_ids (special values: 'this', 'children')", + Destination: &infoNotificationsOptions.MOMIDs, + }, + ), + }, }, } app.Commands = append(app.Commands, cmd) @@ -166,7 +185,7 @@ func (tableEventEntry) TableGetHeader() []string { func (e tableEventEntry) TableGetRow() []string { const timeFmt = "2006-01-02 15:04:05" return []string{ - e.Event, + string(e.Event), e.Comment, time.Unix(e.Time, 0).Format(timeFmt), e.IP, @@ -225,3 +244,108 @@ func listMytokens(_ context.Context, _ *cli.Command) (err error) { } return prettyPrintJSON(res.Tokens) } + +func infoNotifications(_ context.Context, _ *cli.Command) (err error) { + mToken := infoNotificationsOptions.MustGetToken() + mytoken := config.Get().Mytoken() + + var momIDs []string + if len(infoNotificationsOptions.MOMIDs) > 0 { + momIDs = infoNotificationsOptions.MOMIDs + } + + res, err := mytoken.Tokeninfo.APINotifications(mToken, momIDs) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + if len(res.Notifications) > 0 { + fmt.Println("Notifications:") + outputData := make([]tablewriter.TableWriter, len(res.Notifications)) + for i, n := range res.Notifications { + outputData[i] = tableNotificationInfo(n) + } + tablewriter.PrintTableData(outputData) + fmt.Println() + } + + if len(res.Calendars) > 0 { + fmt.Println("Calendars:") + outputData := make([]tablewriter.TableWriter, len(res.Calendars)) + for i, c := range res.Calendars { + outputData[i] = tableCalendarInfo(c) + } + tablewriter.PrintTableData(outputData) + fmt.Println() + } + + if len(res.Notifications) == 0 && len(res.Calendars) == 0 { + fmt.Println("No notifications or calendars found.") + } + + return nil +} + +type tableNotificationInfo api.NotificationInfo + +func (tableNotificationInfo) TableGetHeader() []string { + return []string{ + "Type", + "Management Code", + "Classes", + "User Wide", + "Tags", + } +} + +func (n tableNotificationInfo) TableGetRow() []string { + classes := make([]string, len(n.Classes)) + for i, c := range n.Classes { + classes[i] = c.Name + } + + tags := "" + if len(n.Tags) > 0 { + tagStrs := make([]string, len(n.Tags)) + for i, t := range n.Tags { + tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) + } + tags = strings.Join(tagStrs, ", ") + } + + return []string{ + n.Type, + n.ManagementCode, + strings.Join(classes, ", "), + fmt.Sprintf("%v", n.UserWide), + tags, + } +} + +type tableCalendarInfo api.CalendarInfo + +func (tableCalendarInfo) TableGetHeader() []string { + return []string{ + "Description", + "Tags", + } +} + +func (c tableCalendarInfo) TableGetRow() []string { + tags := "" + if len(c.Tags) > 0 { + tagStrs := make([]string, len(c.Tags)) + for i, t := range c.Tags { + tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) + } + tags = strings.Join(tagStrs, ", ") + } + + return []string{ + c.Description, + tags, + } +} diff --git a/internal/commands/mt.go b/internal/commands/mt.go index b5a4be3..c7a60be 100644 --- a/internal/commands/mt.go +++ b/internal/commands/mt.go @@ -54,6 +54,7 @@ type mtOpts struct { profile string profileOpts + Tags []string request *api.GeneralMytokenRequest Out string @@ -225,6 +226,15 @@ func (opts *mtOpts) Request(ctx context.Context, cmd *cli.Command) (*api.General if err != nil { return nil, err } + if len(opts.Tags) > 0 { + for _, tag := range opts.Tags { + opts.request.Tags = append( + opts.request.Tags, api.CreateMytokenTag{ + Tag: api.Tag(tag), + }, + ) + } + } if len(opts.request.Capabilities) == 0 && len(opts.request.IncludedProfiles) == 0 { opts.request.Capabilities = api.NewCapabilities(config.Get().DefaultTokenCapabilities) } @@ -425,6 +435,23 @@ func init() { Value: "token", Destination: &mtCommand.TokenType, }, + &cli.StringFlag{ + Name: "name", + Aliases: []string{"n"}, + Usage: "A name for the returned mytoken; used for finding the token in a list of mytokens.", + Destination: &mtCommand.Name, + }, + &cli.StringSliceFlag{ + Name: "tags", + Usage: "Tags to assign to the new mytoken (can be used multiple times)", + Destination: &mtCommand.Tags, + }, + &cli.StringFlag{ + Name: "token-type", + Usage: "The type of the returned token.", + Value: "token", + Destination: &mtCommand.TokenType, + }, &cli.StringFlag{ Name: "out", Aliases: []string{"o"}, diff --git a/internal/commands/mttags.go b/internal/commands/mttags.go new file mode 100644 index 0000000..c649277 --- /dev/null +++ b/internal/commands/mttags.go @@ -0,0 +1,131 @@ +package commands + +import ( + "context" + "fmt" + + "github.com/oidc-mytoken/api/v0" + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" +) + +var mtTagsOptions = struct { + MTOptions + TagName string + MOMID string + IncludeChildren bool +}{} + +func init() { + cmd := &cli.Command{ + Name: "tags", + Usage: "Manage tags on mytokens", + Commands: []*cli.Command{ + { + Name: "add", + Usage: "Add a tag to a mytoken", + ArgsUsage: "", + Action: addMTTag, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "mom-id", + Usage: "Target mytoken by mom_id", + Destination: &mtTagsOptions.MOMID, + }, + &cli.BoolFlag{ + Name: "include-children", + Usage: "Apply to children tokens", + Destination: &mtTagsOptions.IncludeChildren, + }, + ), + }, + { + Name: "remove", + Usage: "Remove a tag from a mytoken", + ArgsUsage: "", + Action: removeMTTag, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "mom-id", + Usage: "Target mytoken by mom_id", + Destination: &mtTagsOptions.MOMID, + }, + &cli.BoolFlag{ + Name: "include-children", + Usage: "Apply to children tokens", + Destination: &mtTagsOptions.IncludeChildren, + }, + ), + }, + }, + } + + // Add as subcommand to MT command + mtCmd := findCommand(app.Commands, "MT") + if mtCmd == nil { + mtCmd = findCommand(app.Commands, "mt") + } + if mtCmd == nil { + mtCmd = findCommand(app.Commands, "mytoken") + } + if mtCmd != nil { + mtCmd.Commands = append(mtCmd.Commands, cmd) + } +} + +func addMTTag(_ context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("tag name required") + } + tagName := cmd.Args().Get(0) + mytoken := mtTagsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + req := api.AddTagToMytokenRequest{ + Tag: api.Tag(tagName), + Mytoken: mytoken, + MOMID: mtTagsOptions.MOMID, + IncludeChildren: mtTagsOptions.IncludeChildren, + } + + res, err := mtServer.Mytoken.Tags().APIAdd(req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Tag '%s' added successfully\n", tagName) + return nil +} + +func removeMTTag(_ context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("tag name required") + } + tagName := cmd.Args().Get(0) + mytoken := mtTagsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + req := api.RemoveTagFromMytokenRequest{ + Tag: api.Tag(tagName), + Mytoken: mytoken, + MOMID: mtTagsOptions.MOMID, + IncludeChildren: mtTagsOptions.IncludeChildren, + } + + res, err := mtServer.Mytoken.Tags().APIRemove(req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Tag '%s' removed successfully\n", tagName) + return nil +} diff --git a/internal/commands/profiles.go b/internal/commands/profiles.go new file mode 100644 index 0000000..d29c581 --- /dev/null +++ b/internal/commands/profiles.go @@ -0,0 +1,223 @@ +package commands + +import ( + "context" + "fmt" + "os" + "text/tabwriter" + + mytokenlib "github.com/oidc-mytoken/lib" + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" +) + +var profilesOptions = struct { + MTOptions + Groups []string +}{} + +func init() { + cmd := &cli.Command{ + Name: "profiles", + Usage: "List server-side profiles and templates", + Commands: []*cli.Command{ + { + Name: "list", + Usage: "List all profiles grouped by group", + Action: listProfiles, + Flags: append( + getMTFlags(), + &cli.StringSliceFlag{ + Name: "groups", + Usage: "Filter to specific groups (can be used multiple times)", + Destination: &profilesOptions.Groups, + }, + ), + }, + { + Name: "capabilities", + Usage: "List capability templates grouped by group", + Action: listCapabilities, + Flags: append( + getMTFlags(), + &cli.StringSliceFlag{ + Name: "groups", + Usage: "Filter to specific groups", + Destination: &profilesOptions.Groups, + }, + ), + }, + { + Name: "restrictions", + Usage: "List restriction templates grouped by group", + Action: listRestrictions, + Flags: append( + getMTFlags(), + &cli.StringSliceFlag{ + Name: "groups", + Usage: "Filter to specific groups", + Destination: &profilesOptions.Groups, + }, + ), + }, + { + Name: "rotation", + Usage: "List rotation templates grouped by group", + Action: listRotation, + Flags: append( + getMTFlags(), + &cli.StringSliceFlag{ + Name: "groups", + Usage: "Filter to specific groups", + Destination: &profilesOptions.Groups, + }, + ), + }, + }, + } + app.Commands = append(app.Commands, cmd) +} + +func getGroupsToQuery(requestedGroups []string, mtServer *mytokenlib.MytokenServer) ([]string, error) { + if len(requestedGroups) > 0 { + return requestedGroups, nil + } + + // Fetch all groups + groups, err := mtServer.ProfilesAndTemplates.APIGetGroups() + if err != nil { + return nil, err + } + return groups, nil +} + +func listProfiles(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + groups, err := getGroupsToQuery(profilesOptions.Groups, mtServer) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + + for _, group := range groups { + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + + // Get capabilities + caps, err := mtServer.ProfilesAndTemplates.APIGetCapabilities(group) + if err == nil && len(caps) > 0 { + fmt.Fprintln(w, "Capabilities:") + for _, cap := range caps { + fmt.Fprintf(w, " - %s\n", cap.Name) + } + } + + // Get restrictions + restrs, err := mtServer.ProfilesAndTemplates.APIGetRestrictions(group) + if err == nil && len(restrs) > 0 { + fmt.Fprintln(w, "Restrictions:") + for _, restr := range restrs { + fmt.Fprintf(w, " - %s\n", restr.Name) + } + } + + // Get rotation + rots, err := mtServer.ProfilesAndTemplates.APIGetRotation(group) + if err == nil && len(rots) > 0 { + fmt.Fprintln(w, "Rotation:") + for _, rot := range rots { + fmt.Fprintf(w, " - %s\n", rot.Name) + } + } + } + + w.Flush() + return nil +} + +func listCapabilities(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + groups, err := getGroupsToQuery(profilesOptions.Groups, mtServer) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + + for _, group := range groups { + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + + caps, err := mtServer.ProfilesAndTemplates.APIGetCapabilities(group) + if err != nil { + continue + } + + for _, cap := range caps { + fmt.Fprintf(w, " %s\n", cap.Name) + } + } + + w.Flush() + return nil +} + +func listRestrictions(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + groups, err := getGroupsToQuery(profilesOptions.Groups, mtServer) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + + for _, group := range groups { + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + + restrs, err := mtServer.ProfilesAndTemplates.APIGetRestrictions(group) + if err != nil { + continue + } + + for _, restr := range restrs { + fmt.Fprintf(w, " %s\n", restr.Name) + } + } + + w.Flush() + return nil +} + +func listRotation(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + groups, err := getGroupsToQuery(profilesOptions.Groups, mtServer) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + + for _, group := range groups { + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + + rots, err := mtServer.ProfilesAndTemplates.APIGetRotation(group) + if err != nil { + continue + } + + for _, rot := range rots { + fmt.Fprintf(w, " %s\n", rot.Name) + } + } + + w.Flush() + return nil +} diff --git a/internal/commands/settings.go b/internal/commands/settings.go index 36831fb..12858d2 100644 --- a/internal/commands/settings.go +++ b/internal/commands/settings.go @@ -15,4 +15,6 @@ func init() { } app.Commands = append(app.Commands, cmd) initGrants(cmd) + initEmail(cmd) + initTags(cmd) } diff --git a/internal/commands/tags.go b/internal/commands/tags.go new file mode 100644 index 0000000..741338f --- /dev/null +++ b/internal/commands/tags.go @@ -0,0 +1,191 @@ +package commands + +import ( + "context" + "fmt" + "regexp" + + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" + "github.com/oidc-mytoken/client/internal/utils/color" +) + +var tagsOptions = struct { + MTOptions + TagName string + NewTagName string + Color string +}{} + +func initTags(parent *cli.Command) { + cmd := &cli.Command{ + Name: "tags", + Usage: "View and manage your tags", + Commands: []*cli.Command{ + { + Name: "list", + Usage: "List all your tags", + Action: listTags, + Flags: getMTFlags(), + }, + { + Name: "create", + Usage: "Create a new tag", + ArgsUsage: "", + Action: createTag, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "color", + Usage: "Tag color (hex code or named color like 'red', 'blue')", + Destination: &tagsOptions.Color, + }, + ), + }, + { + Name: "update", + Usage: "Update an existing tag", + ArgsUsage: "", + Action: updateTag, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "new-name", + Usage: "New name for the tag", + Destination: &tagsOptions.NewTagName, + }, + &cli.StringFlag{ + Name: "color", + Usage: "New color for the tag (hex code or named color)", + Destination: &tagsOptions.Color, + }, + ), + }, + { + Name: "delete", + Usage: "Delete a tag", + ArgsUsage: "", + Action: deleteTag, + Flags: getMTFlags(), + }, + }, + } + parent.Commands = append(parent.Commands, cmd) +} + +func listTags(_ context.Context, _ *cli.Command) error { + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + res, err := mtServer.UserSettings.Tags.APIGet(mytoken) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + // Print header + fmt.Println("COLOR TAG Color (Hex)") + fmt.Println("-------- ------------------------- --------------------") + + // ANSI escape code regex to strip for width calculation + ansiRegex := regexp.MustCompile(`\x1b\[[0-9;]*m`) + + // Print tags with color blocks + for _, tag := range res.Tags { + colorBlock := color.TerminalColorBlock(tag.Color) + // Strip ANSI codes to get actual visual width of color block (should be 2 spaces) + visualBlock := ansiRegex.ReplaceAllString(colorBlock, "") + // Calculate padding needed: we want the color block to take 8 chars total (like "--------") + padding := 8 - len(visualBlock) + paddingStr := "" + if padding > 0 { + for i := 0; i < padding; i++ { + paddingStr += " " + } + } + fmt.Printf("%s%s %-24s %s\n", colorBlock, paddingStr, tag.Tag, tag.Color) + } + return nil +} + +func createTag(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("tag name required") + } + tagName := cmd.Args().Get(0) + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + var tagColor string + if tagsOptions.Color != "" { + normalizedColor, err := color.NormalizeColor(tagsOptions.Color) + if err != nil { + return fmt.Errorf("invalid color: %w", err) + } + tagColor = normalizedColor + } + + err := mtServer.UserSettings.Tags.APICreate(mytoken, tagName, tagColor) + if err != nil { + return err + } + + fmt.Printf("Tag '%s' created successfully\n", tagName) + return nil +} + +func updateTag(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("tag name required") + } + tagName := cmd.Args().Get(0) + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + if tagsOptions.NewTagName == "" && tagsOptions.Color == "" { + return fmt.Errorf("at least one of --new-name or --color must be provided") + } + + var newColor string + if tagsOptions.Color != "" { + normalizedColor, err := color.NormalizeColor(tagsOptions.Color) + if err != nil { + return fmt.Errorf("invalid color: %w", err) + } + newColor = normalizedColor + } + + res, err := mtServer.UserSettings.Tags.APIUpdate(mytoken, tagName, tagsOptions.NewTagName, newColor) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Tag '%s' updated successfully\n", tagName) + return nil +} + +func deleteTag(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("tag name required") + } + tagName := cmd.Args().Get(0) + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + res, err := mtServer.UserSettings.Tags.APIDelete(mytoken, tagName) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Tag '%s' deleted successfully\n", tagName) + return nil +} diff --git a/internal/model/version/VERSION b/internal/model/version/VERSION index 7ceb040..bcaffe1 100644 --- a/internal/model/version/VERSION +++ b/internal/model/version/VERSION @@ -1 +1 @@ -0.6.1 \ No newline at end of file +0.7.0 \ No newline at end of file diff --git a/internal/utils/color/color.go b/internal/utils/color/color.go new file mode 100644 index 0000000..3c93fd3 --- /dev/null +++ b/internal/utils/color/color.go @@ -0,0 +1,116 @@ +package color + +import ( + "fmt" + "strings" +) + +// NamedColorMap maps HTML color names to hex codes +var NamedColorMap = map[string]string{ + "red": "#FF0000", + "blue": "#0000FF", + "green": "#008000", + "yellow": "#FFFF00", + "orange": "#FFA500", + "purple": "#800080", + "pink": "#FFC0CB", + "cyan": "#00FFFF", + "magenta": "#FF00FF", + "lime": "#00FF00", + "teal": "#008080", + "navy": "#000080", + "maroon": "#800000", + "olive": "#808000", + "silver": "#C0C0C0", + "gray": "#808080", + "grey": "#808080", + "black": "#000000", + "white": "#FFFFFF", + "darkgray": "#A9A9A9", + "darkgrey": "#A9A9A9", + "lightgray": "#D3D3D3", + "lightgrey": "#D3D3D3", + "gold": "#FFD700", + "brown": "#A52A2A", + "coral": "#FF7F50", + "crimson": "#DC143C", + "indigo": "#4B0082", + "ivory": "#FFFFF0", + "khaki": "#F0E68C", + "lavender": "#E6E6FA", + "salmon": "#FA8072", + "tan": "#D2B48C", + "tomato": "#FF6347", + "violet": "#EE82EE", + "wheat": "#F5DEB3", +} + +// NormalizeColor converts a color string (hex or named) to hex format +func NormalizeColor(color string) (string, error) { + color = strings.ToLower(strings.TrimSpace(color)) + + // Check if it's a named color + if hex, ok := NamedColorMap[color]; ok { + return hex, nil + } + + // Validate hex format (#RRGGBB) + if strings.HasPrefix(color, "#") && len(color) == 7 { + return strings.ToUpper(color), nil + } + + // Validate short hex format (#RGB) + if strings.HasPrefix(color, "#") && len(color) == 4 { + r := string(color[1]) + g := string(color[2]) + b := string(color[3]) + return strings.ToUpper(fmt.Sprintf("#%s%s%s%s%s%s", r, r, g, g, b, b)), nil + } + + return "", fmt.Errorf("invalid color: %s", color) +} + +// HexToRGB extracts RGB values from hex color +func HexToRGB(hex string) (r, g, b int, err error) { + hex = strings.TrimPrefix(hex, "#") + if len(hex) != 6 { + return 0, 0, 0, fmt.Errorf("invalid hex color: %s", hex) + } + + fmt.Sscanf(hex[0:2], "%02x", &r) + fmt.Sscanf(hex[2:4], "%02x", &g) + fmt.Sscanf(hex[4:6], "%02x", &b) + + return r, g, b, nil +} + +// TerminalColorBlock returns ANSI escape sequence for terminal color visualization +func TerminalColorBlock(hex string) string { + r, g, b, err := HexToRGB(hex) + if err != nil { + return "[?]" + } + // Return ANSI true color block (two spaces for better visibility) + return fmt.Sprintf("\x1b[48;2;%d;%d;%dm \x1b[0m", r, g, b) +} + +// PrintColoredTag prints a tag with colored indicator +func PrintColoredTag(tag, hex string) { + colorBlock := TerminalColorBlock(hex) + fmt.Printf("%s %s (%s)\n", colorBlock, tag, hex) +} + +// FormatColoredTag returns a formatted string with colored indicator +func FormatColoredTag(tag, hex string) string { + colorBlock := TerminalColorBlock(hex) + return fmt.Sprintf("%s %s (%s)", colorBlock, tag, hex) +} + +// ColorizeText returns ANSI escape sequence for colored text +func ColorizeText(text, hex string) string { + r, g, b, err := HexToRGB(hex) + if err != nil { + return text + } + return fmt.Sprintf("\x1b[38;2;%d;%d;%dm%s\x1b[0m", r, g, b, text) +} From 0964cea8a454fcefb1ccc4256515ce1d3208e332 Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 10:30:02 +0200 Subject: [PATCH 07/21] add support for enabling/disabling colored output via environment variables and CLI flag --- internal/commands/options.go | 8 ++++++ internal/utils/color/color.go | 6 +++++ internal/utils/color/config.go | 47 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 internal/utils/color/config.go diff --git a/internal/commands/options.go b/internal/commands/options.go index da545b8..388ef53 100644 --- a/internal/commands/options.go +++ b/internal/commands/options.go @@ -9,6 +9,7 @@ import ( "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/model/version" + "github.com/oidc-mytoken/client/internal/utils/color" ) var app = &cli.Command{ @@ -79,6 +80,10 @@ CONTACT: ), Destination: &mytokenURL, }, + &cli.BoolFlag{ + Name: "no-color", + Usage: "Disable colored output", + }, ) app.Before = func(ctx context.Context, cmd *cli.Command) (context.Context, error) { if cmd.IsSet("config") { @@ -89,6 +94,9 @@ CONTACT: if cmd.IsSet("url") { config.SetURL(mytokenURL) } + if cmd.IsSet("no-color") { + color.DisableColors() + } return ctx, nil } } diff --git a/internal/utils/color/color.go b/internal/utils/color/color.go index 3c93fd3..61399c4 100644 --- a/internal/utils/color/color.go +++ b/internal/utils/color/color.go @@ -86,6 +86,9 @@ func HexToRGB(hex string) (r, g, b int, err error) { // TerminalColorBlock returns ANSI escape sequence for terminal color visualization func TerminalColorBlock(hex string) string { + if !ShouldUseColors() { + return "" + } r, g, b, err := HexToRGB(hex) if err != nil { return "[?]" @@ -108,6 +111,9 @@ func FormatColoredTag(tag, hex string) string { // ColorizeText returns ANSI escape sequence for colored text func ColorizeText(text, hex string) string { + if !ShouldUseColors() { + return text + } r, g, b, err := HexToRGB(hex) if err != nil { return text diff --git a/internal/utils/color/config.go b/internal/utils/color/config.go new file mode 100644 index 0000000..6dc1821 --- /dev/null +++ b/internal/utils/color/config.go @@ -0,0 +1,47 @@ +package color + +import ( + "os" + + "github.com/mattn/go-isatty" +) + +var colorsEnabled = true + +func init() { + // Priority 1: CLICOLOR_FORCE=1 forces colors on + if os.Getenv("CLICOLOR_FORCE") == "1" { + colorsEnabled = true + return + } + + // Priority 2: NO_COLOR disables colors (any value) + if os.Getenv("NO_COLOR") != "" { + colorsEnabled = false + return + } + + // Priority 3: CLICOLOR=0 disables colors + if os.Getenv("CLICOLOR") == "0" { + colorsEnabled = false + return + } + + // Priority 4: Not a TTY disables colors + if !isatty.IsTerminal(os.Stdout.Fd()) { + colorsEnabled = false + return + } + + // Default: colors enabled +} + +// ShouldUseColors returns true if colored output should be used +func ShouldUseColors() bool { + return colorsEnabled +} + +// DisableColors disables colored output globally +func DisableColors() { + colorsEnabled = false +} From a0b39c2dda0dc0f5ea214544faa7d68ef7795334 Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 10:51:58 +0200 Subject: [PATCH 08/21] add support for italic and gray text formatting; extend "list-mytokens" with optional MOM-ID column and hierarchical table rendering --- internal/commands/info.go | 130 +++++++++++++++++++++++++++++++++- internal/utils/color/color.go | 16 +++++ 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/internal/commands/info.go b/internal/commands/info.go index 17e7a58..15dfa45 100644 --- a/internal/commands/info.go +++ b/internal/commands/info.go @@ -62,7 +62,13 @@ func init() { Name: "list-mytokens", Usage: "List all mytokens", Action: listMytokens, - Flags: subCmdFlags, + Flags: append( + subCmdFlags, + &cli.BoolFlag{ + Name: "include-mom-id", + Usage: "Include the MOM-ID column in the output", + }, + ), }, { Name: "notifications", @@ -219,7 +225,7 @@ func subTree(_ context.Context, _ *cli.Command) (err error) { return prettyPrintJSON(res.Tokens) } -func listMytokens(_ context.Context, _ *cli.Command) (err error) { +func listMytokens(_ context.Context, cmd *cli.Command) (err error) { var res api.TokeninfoListResponse if ssh := infoOptions.SSH(); ssh != "" { var resStr string @@ -242,7 +248,125 @@ func listMytokens(_ context.Context, _ *cli.Command) (err error) { updateMytoken(res.TokenUpdate.Mytoken) } } - return prettyPrintJSON(res.Tokens) + includeMOMID := cmd.Bool("include-mom-id") + outputData := flattenMytokenEntryTree(res.Tokens, includeMOMID) + tablewriter.PrintTableData(outputData) + return nil +} + +func flattenMytokenEntryTree(tree []api.MytokenEntryTree, includeMOMID bool) []tablewriter.TableWriter { + var result []tablewriter.TableWriter + for _, entry := range tree { + result = append( + result, tableMytokenEntry{ + entry: entry.Token, + depth: 0, + includeMOMID: includeMOMID, + }, + ) + result = append(result, flattenMytokenEntryTreeRecursive(entry.Children, 1, includeMOMID)...) + } + return result +} + +func flattenMytokenEntryTreeRecursive( + tree []api.MytokenEntryTree, depth int, includeMOMID bool, +) []tablewriter.TableWriter { + var result []tablewriter.TableWriter + for _, entry := range tree { + result = append( + result, tableMytokenEntry{ + entry: entry.Token, + depth: depth, + includeMOMID: includeMOMID, + }, + ) + result = append(result, flattenMytokenEntryTreeRecursive(entry.Children, depth+1, includeMOMID)...) + } + return result +} + +type tableMytokenEntry struct { + entry api.MytokenEntry + depth int + includeMOMID bool +} + +func (e tableMytokenEntry) TableGetHeader() []string { + if e.includeMOMID { + return []string{ + "MOM-ID", + "Name", + "Created", + "Expires", + "Tags", + "IP", + } + } + return []string{ + "Name", + "Created", + "Expires", + "Tags", + "IP", + } +} + +func (e tableMytokenEntry) TableGetRow() []string { + const timeFmt = "2006-01-02 15:04:05" + now := time.Now().Unix() + + created := time.Unix(e.entry.CreatedAt, 0).Format(timeFmt) + expires := "" + expired := false + if e.entry.ExpiresAt > 0 { + expires = time.Unix(e.entry.ExpiresAt, 0).Format(timeFmt) + expired = e.entry.ExpiresAt < now + } else { + expires = color.Italic("does not expire") + } + + tags := "" + if len(e.entry.Tags) > 0 { + tagStrs := make([]string, len(e.entry.Tags)) + for i, t := range e.entry.Tags { + tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) + } + tags = strings.Join(tagStrs, ", ") + } + + name := e.entry.Name + if name == "" { + name = color.Italic("unnamed token") + } + if e.depth > 0 { + name = strings.Repeat(" ", e.depth) + "└─ " + name + } + + if expired { + name = color.Gray(name) + created = color.Gray(created) + expires = color.Gray(expires) + tags = color.Gray(tags) + } + + if e.includeMOMID { + return []string{ + e.entry.MOMID, + name, + created, + expires, + tags, + e.entry.IP, + } + } + return []string{ + name, + created, + expires, + tags, + e.entry.IP, + } } func infoNotifications(_ context.Context, _ *cli.Command) (err error) { diff --git a/internal/utils/color/color.go b/internal/utils/color/color.go index 61399c4..3b06b33 100644 --- a/internal/utils/color/color.go +++ b/internal/utils/color/color.go @@ -120,3 +120,19 @@ func ColorizeText(text, hex string) string { } return fmt.Sprintf("\x1b[38;2;%d;%d;%dm%s\x1b[0m", r, g, b, text) } + +// Italic returns ANSI escape sequence for italic text +func Italic(text string) string { + if !ShouldUseColors() { + return text + } + return fmt.Sprintf("\x1b[3m%s\x1b[0m", text) +} + +// Gray returns ANSI escape sequence for gray text +func Gray(text string) string { + if !ShouldUseColors() { + return text + } + return fmt.Sprintf("\x1b[38;2;128;128;128m%s\x1b[0m", text) +} From 4ecd70dad26a03918228450720e0a3b41014617b Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 11:08:34 +0200 Subject: [PATCH 09/21] add "mt list" as an alias for "info list-mytokens" --- internal/commands/info.go | 7 ++++--- internal/commands/mttags.go | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/internal/commands/info.go b/internal/commands/info.go index 15dfa45..3213f09 100644 --- a/internal/commands/info.go +++ b/internal/commands/info.go @@ -59,9 +59,10 @@ func init() { Flags: subCmdFlags, }, { - Name: "list-mytokens", - Usage: "List all mytokens", - Action: listMytokens, + Name: "list-mytokens", + Aliases: []string{"list"}, + Usage: "List all mytokens", + Action: listMytokens, Flags: append( subCmdFlags, &cli.BoolFlag{ diff --git a/internal/commands/mttags.go b/internal/commands/mttags.go index c649277..b6ce7e4 100644 --- a/internal/commands/mttags.go +++ b/internal/commands/mttags.go @@ -72,7 +72,20 @@ func init() { mtCmd = findCommand(app.Commands, "mytoken") } if mtCmd != nil { - mtCmd.Commands = append(mtCmd.Commands, cmd) + listCmd := &cli.Command{ + Name: "list-mytokens", + Aliases: []string{"list"}, + Usage: "List all mytokens", + Action: listMytokens, + Flags: append( + getMTFlags(), + &cli.BoolFlag{ + Name: "include-mom-id", + Usage: "Include the MOM-ID column in the output", + }, + ), + } + mtCmd.Commands = append(mtCmd.Commands, cmd, listCmd) } } From f98321daf9129fc6ac42b281ed09bab794bad287 Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 11:39:58 +0200 Subject: [PATCH 10/21] add additional aliases ("rm," "delete") for tag and SSH key management commands --- internal/commands/mttags.go | 6 +++++- internal/commands/sshGrant.go | 7 +++++-- internal/commands/tags.go | 6 +++++- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/internal/commands/mttags.go b/internal/commands/mttags.go index b6ce7e4..96a849e 100644 --- a/internal/commands/mttags.go +++ b/internal/commands/mttags.go @@ -42,7 +42,11 @@ func init() { ), }, { - Name: "remove", + Name: "remove", + Aliases: []string{ + "rm", + "delete", + }, Usage: "Remove a tag from a mytoken", ArgsUsage: "", Action: removeMTTag, diff --git a/internal/commands/sshGrant.go b/internal/commands/sshGrant.go index c2b4b4d..774c1ec 100644 --- a/internal/commands/sshGrant.go +++ b/internal/commands/sshGrant.go @@ -68,8 +68,11 @@ func initSSHGrant(parent *cli.Command) { ), }, { - Name: "delete", - Aliases: []string{"remove"}, + Name: "delete", + Aliases: []string{ + "remove", + "rm", + }, ArgsUsage: "SSH_KEY", Usage: "Removes an ssh key. You can pass the SHA256 fingerprint (recommended), " + "the whole public key, or a filepath to the public key.", diff --git a/internal/commands/tags.go b/internal/commands/tags.go index 741338f..8783db9 100644 --- a/internal/commands/tags.go +++ b/internal/commands/tags.go @@ -63,7 +63,11 @@ func initTags(parent *cli.Command) { ), }, { - Name: "delete", + Name: "delete", + Aliases: []string{ + "rm", + "remove", + }, Usage: "Delete a tag", ArgsUsage: "", Action: deleteTag, From a25ba60464bbfd9c093eb16fddc7d8af503c3cdc Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 12:19:13 +0200 Subject: [PATCH 11/21] add "list-all" and "groups" commands for listing profiles, templates, and groups --- internal/commands/profiles.go | 167 ++++++++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 40 deletions(-) diff --git a/internal/commands/profiles.go b/internal/commands/profiles.go index d29c581..aaf37de 100644 --- a/internal/commands/profiles.go +++ b/internal/commands/profiles.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "strings" "text/tabwriter" mytokenlib "github.com/oidc-mytoken/lib" @@ -23,9 +24,16 @@ func init() { Usage: "List server-side profiles and templates", Commands: []*cli.Command{ { - Name: "list", - Usage: "List all profiles grouped by group", - Action: listProfiles, + Name: "groups", + Usage: "List all available groups", + Action: listGroups, + Flags: getMTFlags(), + }, + { + Name: "list", + Usage: "List profiles grouped by group", + Aliases: []string{"profiles"}, + Action: listProfiles, Flags: append( getMTFlags(), &cli.StringSliceFlag{ @@ -74,11 +82,39 @@ func init() { }, ), }, + { + Name: "list-all", + Usage: "List all profiles and templates grouped by group", + Action: listAll, + Aliases: []string{"all"}, + Flags: append( + getMTFlags(), + &cli.StringSliceFlag{ + Name: "groups", + Usage: "Filter to specific groups (can be used multiple times)", + Destination: &profilesOptions.Groups, + }, + ), + }, }, } app.Commands = append(app.Commands, cmd) } +func listGroups(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + groups, err := mtServer.ProfilesAndTemplates.APIGetGroups() + if err != nil { + return err + } + + for _, group := range groups { + fmt.Println(group) + } + return nil +} + func getGroupsToQuery(requestedGroups []string, mtServer *mytokenlib.MytokenServer) ([]string, error) { if len(requestedGroups) > 0 { return requestedGroups, nil @@ -103,34 +139,16 @@ func listProfiles(_ context.Context, _ *cli.Command) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) for _, group := range groups { - fmt.Fprintf(w, "\nGroup: %s\n", group) - fmt.Fprintln(w, "----------") - - // Get capabilities - caps, err := mtServer.ProfilesAndTemplates.APIGetCapabilities(group) - if err == nil && len(caps) > 0 { - fmt.Fprintln(w, "Capabilities:") - for _, cap := range caps { - fmt.Fprintf(w, " - %s\n", cap.Name) - } + profiles, err := mtServer.ProfilesAndTemplates.APIGetProfiles(group) + if err != nil || len(profiles) == 0 { + continue } - // Get restrictions - restrs, err := mtServer.ProfilesAndTemplates.APIGetRestrictions(group) - if err == nil && len(restrs) > 0 { - fmt.Fprintln(w, "Restrictions:") - for _, restr := range restrs { - fmt.Fprintf(w, " - %s\n", restr.Name) - } - } + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") - // Get rotation - rots, err := mtServer.ProfilesAndTemplates.APIGetRotation(group) - if err == nil && len(rots) > 0 { - fmt.Fprintln(w, "Rotation:") - for _, rot := range rots { - fmt.Fprintf(w, " - %s\n", rot.Name) - } + for _, profile := range profiles { + fmt.Fprintf(w, " %s\n", profile.Name) } } @@ -149,14 +167,14 @@ func listCapabilities(_ context.Context, _ *cli.Command) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) for _, group := range groups { - fmt.Fprintf(w, "\nGroup: %s\n", group) - fmt.Fprintln(w, "----------") - caps, err := mtServer.ProfilesAndTemplates.APIGetCapabilities(group) - if err != nil { + if err != nil || len(caps) == 0 { continue } + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + for _, cap := range caps { fmt.Fprintf(w, " %s\n", cap.Name) } @@ -177,14 +195,14 @@ func listRestrictions(_ context.Context, _ *cli.Command) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) for _, group := range groups { - fmt.Fprintf(w, "\nGroup: %s\n", group) - fmt.Fprintln(w, "----------") - restrs, err := mtServer.ProfilesAndTemplates.APIGetRestrictions(group) - if err != nil { + if err != nil || len(restrs) == 0 { continue } + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + for _, restr := range restrs { fmt.Fprintf(w, " %s\n", restr.Name) } @@ -205,14 +223,14 @@ func listRotation(_ context.Context, _ *cli.Command) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) for _, group := range groups { - fmt.Fprintf(w, "\nGroup: %s\n", group) - fmt.Fprintln(w, "----------") - rots, err := mtServer.ProfilesAndTemplates.APIGetRotation(group) - if err != nil { + if err != nil || len(rots) == 0 { continue } + fmt.Fprintf(w, "\nGroup: %s\n", group) + fmt.Fprintln(w, "----------") + for _, rot := range rots { fmt.Fprintf(w, " %s\n", rot.Name) } @@ -221,3 +239,72 @@ func listRotation(_ context.Context, _ *cli.Command) error { w.Flush() return nil } + +func listAll(_ context.Context, _ *cli.Command) error { + mtServer := config.Get().Mytoken() + + groups, err := getGroupsToQuery(profilesOptions.Groups, mtServer) + if err != nil { + return err + } + + allProfiles := make(map[string][]string) + allCaps := make(map[string][]string) + allRestrs := make(map[string][]string) + allRots := make(map[string][]string) + + for _, group := range groups { + profiles, err := mtServer.ProfilesAndTemplates.APIGetProfiles(group) + if err == nil && len(profiles) > 0 { + for _, p := range profiles { + allProfiles[group] = append(allProfiles[group], p.Name) + } + } + + caps, err := mtServer.ProfilesAndTemplates.APIGetCapabilities(group) + if err == nil && len(caps) > 0 { + for _, c := range caps { + allCaps[group] = append(allCaps[group], c.Name) + } + } + + restrs, err := mtServer.ProfilesAndTemplates.APIGetRestrictions(group) + if err == nil && len(restrs) > 0 { + for _, r := range restrs { + allRestrs[group] = append(allRestrs[group], r.Name) + } + } + + rots, err := mtServer.ProfilesAndTemplates.APIGetRotation(group) + if err == nil && len(rots) > 0 { + for _, r := range rots { + allRots[group] = append(allRots[group], r.Name) + } + } + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + + printSection := func(title string, data map[string][]string) { + if len(data) == 0 { + return + } + fmt.Fprintf(w, "\n%s:\n", title) + fmt.Fprintln(w, strings.Repeat("=", len(title))) + for group, items := range data { + fmt.Fprintf(w, "\n Group: %s\n", group) + fmt.Fprintln(w, " ----------") + for _, item := range items { + fmt.Fprintf(w, " - %s\n", item) + } + } + } + + printSection("Profiles", allProfiles) + printSection("Capabilities", allCaps) + printSection("Restrictions", allRestrs) + printSection("Rotation", allRots) + + w.Flush() + return nil +} From ae91f5205a4dc8a2cceb2c9bf2f992a2635daa81 Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 6 May 2026 14:46:22 +0200 Subject: [PATCH 12/21] normalize color handling: support hex colors with or without "#" prefix --- internal/utils/color/color.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/internal/utils/color/color.go b/internal/utils/color/color.go index 3b06b33..4578092 100644 --- a/internal/utils/color/color.go +++ b/internal/utils/color/color.go @@ -45,32 +45,43 @@ var NamedColorMap = map[string]string{ "wheat": "#F5DEB3", } -// NormalizeColor converts a color string (hex or named) to hex format +// NormalizeColor converts a color string (hex or named) to hex format (without leading #) func NormalizeColor(color string) (string, error) { color = strings.ToLower(strings.TrimSpace(color)) // Check if it's a named color if hex, ok := NamedColorMap[color]; ok { - return hex, nil + return strings.TrimPrefix(hex, "#"), nil } - // Validate hex format (#RRGGBB) + // Validate hex format (#RRGGBB or RRGGBB) if strings.HasPrefix(color, "#") && len(color) == 7 { + return strings.ToUpper(strings.TrimPrefix(color, "#")), nil + } + // Also accept without # prefix + if !strings.HasPrefix(color, "#") && len(color) == 6 { return strings.ToUpper(color), nil } - // Validate short hex format (#RGB) + // Validate short hex format (#RGB or RGB) if strings.HasPrefix(color, "#") && len(color) == 4 { r := string(color[1]) g := string(color[2]) b := string(color[3]) - return strings.ToUpper(fmt.Sprintf("#%s%s%s%s%s%s", r, r, g, g, b, b)), nil + return strings.ToUpper(fmt.Sprintf("%s%s%s%s%s%s", r, r, g, g, b, b)), nil + } + // Also accept without # prefix + if !strings.HasPrefix(color, "#") && len(color) == 3 { + r := string(color[0]) + g := string(color[1]) + b := string(color[2]) + return strings.ToUpper(fmt.Sprintf("%s%s%s%s%s%s", r, r, g, g, b, b)), nil } return "", fmt.Errorf("invalid color: %s", color) } -// HexToRGB extracts RGB values from hex color +// HexToRGB extracts RGB values from hex color (with or without leading #) func HexToRGB(hex string) (r, g, b int, err error) { hex = strings.TrimPrefix(hex, "#") if len(hex) != 6 { From 03239f8da6d70a065539de735a21cf70da7d3c48 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:05:50 +0200 Subject: [PATCH 13/21] add "calendars" and "notifications" commands to CLI for managing calendars and notifications --- go.mod | 4 +- go.sum | 4 +- internal/commands/calendars.go | 445 ++++++++++++++++++++ internal/commands/info.go | 61 --- internal/commands/notifications.go | 651 +++++++++++++++++++++++++++++ 5 files changed, 1100 insertions(+), 65 deletions(-) create mode 100644 internal/commands/calendars.go create mode 100644 internal/commands/notifications.go diff --git a/go.mod b/go.mod index 1f5e80c..2eac6dd 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.25.0 require ( github.com/Songmu/prompter v0.5.1 github.com/gliderlabs/ssh v0.3.8 + github.com/mattn/go-isatty v0.0.22 github.com/mdp/qrterminal/v3 v3.2.1 - github.com/oidc-mytoken/api v0.12.0 + github.com/oidc-mytoken/api v0.12.1 github.com/oidc-mytoken/lib v0.7.1 github.com/oidc-mytoken/utils v0.1.2 github.com/olekukonko/tablewriter v1.1.4 @@ -32,7 +33,6 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/klauspost/compress v1.15.9 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect github.com/mattn/go-runewidth v0.0.23 // indirect github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 // indirect github.com/olekukonko/errors v1.3.0 // indirect diff --git a/go.sum b/go.sum index 62a7373..e3f5e2d 100644 --- a/go.sum +++ b/go.sum @@ -222,8 +222,8 @@ github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhg github.com/mdp/qrterminal/v3 v3.2.1 h1:6+yQjiiOsSuXT5n9/m60E54vdgFsw0zhADHhHLrFet4= github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFSuQ5kpz/SU= github.com/oidc-mytoken/api v0.9.1/go.mod h1:DBIlUbaIgGlf607VZx8zFC97VR3WNN0kaMVO1AqyTdE= -github.com/oidc-mytoken/api v0.12.0 h1:PPK+0MNPHgJ0CcdF5DBhV+df2IrffhHG0nU7GrIh9hA= -github.com/oidc-mytoken/api v0.12.0/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= +github.com/oidc-mytoken/api v0.12.1 h1:2VFZ2rFBRmWmqdjH1T2Yw1Z/qkwyNwZF0CBk6LGFumI= +github.com/oidc-mytoken/api v0.12.1/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= github.com/oidc-mytoken/utils v0.1.2 h1:nmKIw/k4OkQ/9ww06W7krJ4yTwvDbuCRCjjRCmI9gSE= github.com/oidc-mytoken/utils v0.1.2/go.mod h1:pAUlvF/mYz8CjinCx4iwoWzG514B9EhuCpu7O+afaD8= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= diff --git a/internal/commands/calendars.go b/internal/commands/calendars.go new file mode 100644 index 0000000..6db2d99 --- /dev/null +++ b/internal/commands/calendars.go @@ -0,0 +1,445 @@ +package commands + +import ( + "context" + "fmt" + "strings" + + "github.com/Songmu/prompter" + "github.com/oidc-mytoken/api/v0" + mytokenlib "github.com/oidc-mytoken/lib" + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" + "github.com/oidc-mytoken/client/internal/utils/color" + "github.com/oidc-mytoken/client/internal/utils/tablewriter" +) + +var calendarsOptions = struct { + MTOptions + CalendarID string + Description string + Tags []string + AddTags []string + RemoveTags []string + MomID string + Comment string + Interactive bool + Force bool +}{} + +func init() { + app.Commands = append( + app.Commands, &cli.Command{ + Name: "calendars", + Aliases: []string{"calendar"}, + Usage: "View and manage your calendars", + Flags: getMTFlags(), + Commands: []*cli.Command{ + { + Name: "list", + Usage: "List all your calendars", + Action: listCalendars, + Flags: getMTFlags(), + }, + { + Name: "create", + Aliases: []string{"new"}, + Usage: "Create a new calendar", + Action: createCalendar, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "description", + Usage: "Calendar description", + Destination: &calendarsOptions.Description, + }, + &cli.StringSliceFlag{ + Name: "tags", + Usage: "Tags for the calendar (comma-separated or repeat flag)", + Destination: &calendarsOptions.Tags, + }, + &cli.BoolFlag{ + Name: "interactive", + Aliases: []string{"i"}, + Usage: "Interactive mode with guided prompts", + Destination: &calendarsOptions.Interactive, + }, + ), + }, + { + Name: "update", + Usage: "Update calendar description and/or tags", + ArgsUsage: "", + Action: updateCalendar, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "description", + Usage: "New calendar description", + Destination: &calendarsOptions.Description, + }, + &cli.StringSliceFlag{ + Name: "add-tags", + Usage: "Add these tags", + Destination: &calendarsOptions.AddTags, + }, + &cli.StringSliceFlag{ + Name: "remove-tags", + Usage: "Remove these tags", + Destination: &calendarsOptions.RemoveTags, + }, + ), + }, + { + Name: "delete", + Aliases: []string{ + "rm", + "remove", + }, + Usage: "Delete a calendar", + ArgsUsage: "", + Action: deleteCalendar, + Flags: append( + getMTFlags(), + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Skip confirmation prompt", + Destination: &calendarsOptions.Force, + }, + ), + }, + { + Name: "subscribe", + Aliases: []string{"add"}, + Usage: "Subscribe a mytoken to a calendar", + ArgsUsage: "", + Action: subscribeToCalendar, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "mom-id", + Usage: "MOM-ID of the token to subscribe", + Destination: &calendarsOptions.MomID, + }, + &cli.StringFlag{ + Name: "comment", + Usage: "Comment for the subscription", + Destination: &calendarsOptions.Comment, + }, + ), + }, + { + Name: "unsubscribe", + Aliases: []string{"remove"}, + Usage: "Unsubscribe from a calendar", + ArgsUsage: "", + Action: unsubscribeFromCalendar, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "mom-id", + Usage: "MOM-ID of the token to unsubscribe", + Destination: &calendarsOptions.MomID, + }, + ), + }, + }, + }, + ) +} + +func listCalendars(_ context.Context, _ *cli.Command) error { + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + res, err := mtServer.Calendars.APIList(mytoken) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + outputData := make([]tablewriter.TableWriter, len(res.Calendars)) + for i, c := range res.Calendars { + outputData[i] = tableCalendarInfo(c) + } + tablewriter.PrintTableData(outputData) + return nil +} + +type tableCalendarInfo api.CalendarInfo + +func (tableCalendarInfo) TableGetHeader() []string { + return []string{ + "ID", + "Description", + "Tags", + "ICS Path", + "Subscribed Tokens", + } +} + +func (c tableCalendarInfo) TableGetRow() []string { + tags := "" + if len(c.Tags) > 0 { + tagStrs := make([]string, len(c.Tags)) + for i, t := range c.Tags { + tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) + } + tags = strings.Join(tagStrs, ", ") + } + + subscribedCount := fmt.Sprintf("%d", len(c.SubscribedTokens)) + + return []string{ + c.ID, + c.Description, + tags, + c.ICSPath, + subscribedCount, + } +} + +func createCalendar(_ context.Context, _ *cli.Command) error { + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + if calendarsOptions.Interactive { + if err := interactiveCreateCalendar(mytoken, mtServer); err != nil { + return err + } + return nil + } + + tags := parseStringSlice(calendarsOptions.Tags) + apiTags, err := getOrCreateTags(mytoken, mtServer, tags) + if err != nil { + return err + } + + req := api.CreateCalendarRequest{ + Description: calendarsOptions.Description, + Tags: apiTags, + } + + res, err := mtServer.Calendars.APICreate(mytoken, req) + if err != nil { + return err + } + + fmt.Printf("\n✓ Calendar created successfully! ID: %s\n", res.ID) + return nil +} + +func interactiveCreateCalendar(mytoken string, mtServer *mytokenlib.MytokenServer) error { + fmt.Println("=== Interactive Calendar Creation ===") + fmt.Println() + + // Step 1: Description + calendarsOptions.Description = prompter.Prompt("Enter calendar description", "") + + // Step 2: Tags + existingTags, err := mtServer.UserSettings.Tags.APIGet(mytoken) + if err != nil { + return err + } + if len(existingTags.Tags) > 0 { + fmt.Println("\nAvailable tags:") + for _, tag := range existingTags.Tags { + fmt.Printf(" %s\n", color.ColorizeText(string(tag.Tag), tag.Color)) + } + } + tagsInput := prompter.Prompt("Add tags (comma-separated, or empty to skip)", "") + tags := parseStringSlice(strings.Split(tagsInput, ",")) + apiTags, err := getOrCreateTags(mytoken, mtServer, tags) + if err != nil { + return err + } + + // Step 3: Preview and confirm + fmt.Println("\n=== Summary ===") + fmt.Printf("Description: %s\n", calendarsOptions.Description) + if len(apiTags) > 0 { + tagStrs := make([]string, len(apiTags)) + for i, t := range apiTags { + tagStrs[i] = string(t) + } + fmt.Printf("Tags: %s\n", strings.Join(tagStrs, ", ")) + } + + if !prompter.YN("\nConfirm creation?", true) { + fmt.Println("Cancelled.") + return nil + } + + // Execute creation + req := api.CreateCalendarRequest{ + Description: calendarsOptions.Description, + Tags: apiTags, + } + + res, err := mtServer.Calendars.APICreate(mytoken, req) + if err != nil { + return err + } + + fmt.Printf("\n✓ Calendar created successfully! ID: %s\n", res.ID) + return nil +} + +func updateCalendar(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("calendar-id is required") + } + calendarID := cmd.Args().Get(0) + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + addTags := parseStringSlice(calendarsOptions.AddTags) + removeTags := parseStringSlice(calendarsOptions.RemoveTags) + + if calendarsOptions.Description == "" && len(addTags) == 0 && len(removeTags) == 0 { + return fmt.Errorf("at least one of --description, --add-tags, or --remove-tags must be provided") + } + + // Get current calendar state + listRes, err := mtServer.Calendars.APIList(mytoken) + if err != nil { + return err + } + + var currentCalendar *api.CalendarInfo + for _, c := range listRes.Calendars { + if c.ID == calendarID { + currentCalendar = &c + break + } + } + if currentCalendar == nil { + return fmt.Errorf("calendar with ID '%s' not found", calendarID) + } + + // Build new description + newDescription := calendarsOptions.Description + if newDescription == "" { + newDescription = currentCalendar.Description + } + + // Build new tags list + newTags := make(map[api.Tag]bool) + for _, t := range currentCalendar.Tags { + newTags[t.Tag] = true + } + + // Add tags (auto-create if needed) + apiAddTags, err := getOrCreateTags(mytoken, mtServer, addTags) + if err != nil { + return err + } + for _, t := range apiAddTags { + newTags[t] = true + } + + // Remove tags + for _, tagName := range removeTags { + delete(newTags, api.Tag(tagName)) + } + + // Convert map back to slice + finalTags := make([]api.Tag, 0, len(newTags)) + for t := range newTags { + finalTags = append(finalTags, t) + } + + req := api.CreateCalendarRequest{ + Description: newDescription, + Tags: finalTags, + } + + res, err := mtServer.Calendars.APIUpdate(mytoken, calendarID, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Calendar '%s' updated successfully\n", calendarID) + return nil +} + +func deleteCalendar(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("calendar-id is required") + } + calendarID := cmd.Args().Get(0) + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + if !calendarsOptions.Force { + prompt := fmt.Sprintf("Are you sure you want to delete calendar %s?", calendarID) + if !prompter.YN(prompt, false) { + fmt.Println("Cancelled.") + return nil + } + } + + res, err := mtServer.Calendars.APIDelete(mytoken, calendarID) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Calendar '%s' deleted successfully\n", calendarID) + return nil +} + +func subscribeToCalendar(_ context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("calendar-id is required") + } + calendarID := cmd.Args().Get(0) + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + req := api.AddMytokenToCalendarRequest{ + MomID: calendarsOptions.MomID, + Comment: calendarsOptions.Comment, + } + + res, err := mtServer.Calendars.APISubscribe(mytoken, calendarID, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Successfully subscribed to calendar '%s'\n", calendarID) + return nil +} + +func unsubscribeFromCalendar(_ context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("calendar-id is required") + } + calendarID := cmd.Args().Get(0) + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + res, err := mtServer.Calendars.APIUnsubscribe(mytoken, calendarID, calendarsOptions.MomID) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Successfully unsubscribed from calendar '%s'\n", calendarID) + return nil +} diff --git a/internal/commands/info.go b/internal/commands/info.go index 3213f09..e2d20d1 100644 --- a/internal/commands/info.go +++ b/internal/commands/info.go @@ -413,64 +413,3 @@ func infoNotifications(_ context.Context, _ *cli.Command) (err error) { return nil } - -type tableNotificationInfo api.NotificationInfo - -func (tableNotificationInfo) TableGetHeader() []string { - return []string{ - "Type", - "Management Code", - "Classes", - "User Wide", - "Tags", - } -} - -func (n tableNotificationInfo) TableGetRow() []string { - classes := make([]string, len(n.Classes)) - for i, c := range n.Classes { - classes[i] = c.Name - } - - tags := "" - if len(n.Tags) > 0 { - tagStrs := make([]string, len(n.Tags)) - for i, t := range n.Tags { - tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) - } - tags = strings.Join(tagStrs, ", ") - } - - return []string{ - n.Type, - n.ManagementCode, - strings.Join(classes, ", "), - fmt.Sprintf("%v", n.UserWide), - tags, - } -} - -type tableCalendarInfo api.CalendarInfo - -func (tableCalendarInfo) TableGetHeader() []string { - return []string{ - "Description", - "Tags", - } -} - -func (c tableCalendarInfo) TableGetRow() []string { - tags := "" - if len(c.Tags) > 0 { - tagStrs := make([]string, len(c.Tags)) - for i, t := range c.Tags { - tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) - } - tags = strings.Join(tagStrs, ", ") - } - - return []string{ - c.Description, - tags, - } -} diff --git a/internal/commands/notifications.go b/internal/commands/notifications.go new file mode 100644 index 0000000..3fab439 --- /dev/null +++ b/internal/commands/notifications.go @@ -0,0 +1,651 @@ +package commands + +import ( + "context" + "fmt" + "strings" + + "github.com/Songmu/prompter" + "github.com/oidc-mytoken/api/v0" + mytokenlib "github.com/oidc-mytoken/lib" + "github.com/urfave/cli/v3" + + "github.com/oidc-mytoken/client/internal/config" + "github.com/oidc-mytoken/client/internal/utils/color" + "github.com/oidc-mytoken/client/internal/utils/tablewriter" +) + +var notificationsOptions = struct { + MTOptions + ManagementCode string + MOMID string + IncludeChildren bool + NotificationType string + NotificationClasses []string + UserWide bool + Tags []string + Comment string + AddClasses []string + RemoveClasses []string + AddTags []string + RemoveTags []string + Interactive bool + Force bool +}{} + +func init() { + app.Commands = append( + app.Commands, &cli.Command{ + Name: "notifications", + Aliases: []string{"notify"}, + Usage: "View and manage your notifications", + Flags: getMTFlags(), + Commands: []*cli.Command{ + { + Name: "list", + Usage: "List all your notifications", + Action: listNotifications, + Flags: getMTFlags(), + }, + { + Name: "create", + Aliases: []string{ + "subscribe", + "new", + }, + Usage: "Create a new notification subscription", + Action: createNotification, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "type", + Usage: "Notification type (mail, ics_invite)", + Destination: ¬ificationsOptions.NotificationType, + }, + &cli.StringSliceFlag{ + Name: "classes", + Usage: "Notification classes (comma-separated or repeat flag)", + Destination: ¬ificationsOptions.NotificationClasses, + }, + &cli.BoolFlag{ + Name: "user-wide", + Usage: "Make this notification user-wide", + Destination: ¬ificationsOptions.UserWide, + }, + &cli.StringSliceFlag{ + Name: "tags", + Usage: "Tags for the notification (comma-separated or repeat flag)", + Destination: ¬ificationsOptions.Tags, + }, + &cli.StringFlag{ + Name: "comment", + Usage: "Comment (only for ics_invite type)", + Destination: ¬ificationsOptions.Comment, + }, + &cli.BoolFlag{ + Name: "interactive", + Aliases: []string{"i"}, + Usage: "Interactive mode with guided prompts", + Destination: ¬ificationsOptions.Interactive, + }, + ), + }, + { + Name: "update", + Usage: "Update notification classes and/or tags", + ArgsUsage: "", + Action: updateNotification, + Flags: append( + getMTFlags(), + &cli.StringSliceFlag{ + Name: "add-classes", + Usage: "Add these notification classes", + Destination: ¬ificationsOptions.AddClasses, + }, + &cli.StringSliceFlag{ + Name: "remove-classes", + Usage: "Remove these notification classes", + Destination: ¬ificationsOptions.RemoveClasses, + }, + &cli.StringSliceFlag{ + Name: "add-tags", + Usage: "Add these tags", + Destination: ¬ificationsOptions.AddTags, + }, + &cli.StringSliceFlag{ + Name: "remove-tags", + Usage: "Remove these tags", + Destination: ¬ificationsOptions.RemoveTags, + }, + ), + }, + { + Name: "delete", + Aliases: []string{ + "rm", + "remove", + }, + Usage: "Delete a notification", + ArgsUsage: "", + Action: deleteNotification, + Flags: append( + getMTFlags(), + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Skip confirmation prompt", + Destination: ¬ificationsOptions.Force, + }, + ), + }, + { + Name: "add-token", + Usage: "Add a token to a notification", + ArgsUsage: "", + Action: addTokenToNotification, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "mom-id", + Usage: "MOM-ID of the token to add", + Destination: ¬ificationsOptions.MOMID, + }, + &cli.BoolFlag{ + Name: "include-children", + Usage: "Include child tokens", + Destination: ¬ificationsOptions.IncludeChildren, + }, + ), + }, + { + Name: "remove-token", + Usage: "Remove a token from a notification", + ArgsUsage: "", + Action: removeTokenFromNotification, + Flags: append( + getMTFlags(), + &cli.StringFlag{ + Name: "mom-id", + Usage: "MOM-ID of the token to remove", + Destination: ¬ificationsOptions.MOMID, + }, + ), + }, + }, + }, + ) +} + +func parseStringSlice(input []string) []string { + var result []string + for _, val := range input { + parts := strings.Split(val, ",") + for _, p := range parts { + trimmed := strings.TrimSpace(p) + if trimmed != "" { + result = append(result, trimmed) + } + } + } + return result +} + +func listNotifications(_ context.Context, _ *cli.Command) error { + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + res, err := mtServer.Notifications.APIList(mytoken) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + outputData := make([]tablewriter.TableWriter, len(res.Notifications)) + for i, n := range res.Notifications { + outputData[i] = tableNotificationInfo(n) + } + tablewriter.PrintTableData(outputData) + return nil +} + +type tableNotificationInfo api.NotificationInfo + +func (tableNotificationInfo) TableGetHeader() []string { + return []string{ + "Type", + "Management Code", + "Classes", + "User Wide", + "Tags", + "Subscribed Tokens", + } +} + +func (n tableNotificationInfo) TableGetRow() []string { + classes := make([]string, len(n.Classes)) + for i, c := range n.Classes { + classes[i] = c.Name + } + + tags := "" + if len(n.Tags) > 0 { + tagStrs := make([]string, len(n.Tags)) + for i, t := range n.Tags { + tagStrs[i] = color.ColorizeText(string(t.Tag), t.Color) + } + tags = strings.Join(tagStrs, ", ") + } + + var subscribedCount string + if n.UserWide { + subscribedCount = "all" + } else if n.TotalSubscribedTokens > 0 { + // Use TotalSubscribedTokens from backend (includes direct + tag-based) + subscribedCount = fmt.Sprintf("%d", n.TotalSubscribedTokens) + } else { + // Fallback to direct count if TotalSubscribedTokens not set + subscribedCount = fmt.Sprintf("%d", len(n.SubscribedTokens)) + } + + return []string{ + n.Type, + n.ManagementCode, + strings.Join(classes, ", "), + fmt.Sprintf("%v", n.UserWide), + tags, + subscribedCount, + } +} + +func createNotification(_ context.Context, _ *cli.Command) error { + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + if notificationsOptions.Interactive { + if err := interactiveCreateNotification(mytoken, mtServer); err != nil { + return err + } + return nil + } + + if notificationsOptions.NotificationType == "" { + return fmt.Errorf("--type is required (mail or ics_invite)") + } + + classes := parseStringSlice(notificationsOptions.NotificationClasses) + if len(classes) == 0 { + return fmt.Errorf("--classes is required") + } + + tags := parseStringSlice(notificationsOptions.Tags) + apiTags, err := getOrCreateTags(mytoken, mtServer, tags) + if err != nil { + return err + } + + req := api.SubscribeNotificationRequest{ + Mytoken: mytoken, + NotificationType: notificationsOptions.NotificationType, + NotificationClasses: parseNotificationClasses(classes), + UserWide: notificationsOptions.UserWide, + Tags: apiTags, + Comment: notificationsOptions.Comment, + } + + res, err := mtServer.Notifications.APICreate(mytoken, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + displayManagementCode(res.ManagementCode, "notification") + return nil +} + +func interactiveCreateNotification(mytoken string, mtServer *mytokenlib.MytokenServer) error { + fmt.Println("=== Interactive Notification Creation ===") + fmt.Println() + + // Step 1: Notification type + types := []string{ + "mail", + "ics_invite", + } + notificationsOptions.NotificationType = prompter.Choose( + "Select notification type", + types, + "mail", + ) + + // Step 2: Show available classes and let user select + displayAvailableNotificationClasses() + classesInput := prompter.Prompt( + "Enter notification classes (comma-separated)", + "", + ) + classes := parseStringSlice(strings.Split(classesInput, ",")) + if len(classes) == 0 { + return fmt.Errorf("at least one notification class is required") + } + + // Step 3: User-wide + notificationsOptions.UserWide = prompter.YN("Make this notification user-wide?", false) + + // Step 4: Tags (skip if user-wide) + var apiTags []api.Tag + if !notificationsOptions.UserWide { + existingTags, err := mtServer.UserSettings.Tags.APIGet(mytoken) + if err != nil { + return err + } + if len(existingTags.Tags) > 0 { + fmt.Println("\nAvailable tags:") + for _, tag := range existingTags.Tags { + fmt.Printf(" %s\n", color.ColorizeText(string(tag.Tag), tag.Color)) + } + } + tagsInput := prompter.Prompt("Add tags (comma-separated, or empty to skip)", "") + tags := parseStringSlice(strings.Split(tagsInput, ",")) + apiTags, err = getOrCreateTags(mytoken, mtServer, tags) + if err != nil { + return err + } + } + + // Step 4: Comment for ics_invite + if notificationsOptions.NotificationType == "ics_invite" { + notificationsOptions.Comment = prompter.Prompt("Enter comment for ics_invite", "") + } + + // Step 5: Preview and confirm + fmt.Println("\n=== Summary ===") + fmt.Printf("Type: %s\n", notificationsOptions.NotificationType) + fmt.Printf("Classes: %s\n", strings.Join(classes, ", ")) + fmt.Printf("User-wide: %v\n", notificationsOptions.UserWide) + if !notificationsOptions.UserWide && len(apiTags) > 0 { + tagStrs := make([]string, len(apiTags)) + for i, t := range apiTags { + tagStrs[i] = string(t) + } + fmt.Printf("Tags: %s\n", strings.Join(tagStrs, ", ")) + } + if notificationsOptions.Comment != "" { + fmt.Printf("Comment: %s\n", notificationsOptions.Comment) + } + + if !prompter.YN("\nConfirm creation?", true) { + fmt.Println("Cancelled.") + return nil + } + + // Execute creation + req := api.SubscribeNotificationRequest{ + Mytoken: mytoken, + NotificationType: notificationsOptions.NotificationType, + NotificationClasses: parseNotificationClasses(classes), + UserWide: notificationsOptions.UserWide, + Tags: apiTags, + Comment: notificationsOptions.Comment, + } + + res, err := mtServer.Notifications.APICreate(mytoken, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + displayManagementCode(res.ManagementCode, "notification") + return nil +} + +func displayAvailableNotificationClasses() { + fmt.Println("\nAvailable notification classes:") + allClasses := api.AllNotificationClasses + for _, nc := range allClasses { + children := nc.GetChildren() + if len(children) > 0 { + fmt.Printf(" %s\n", nc.Name) + for _, child := range children { + fmt.Printf(" └─ %s\n", child.Name) + grandchildren := child.GetChildren() + for _, gc := range grandchildren { + fmt.Printf(" └─ %s\n", gc.Name) + } + } + } else { + fmt.Printf(" %s\n", nc.Name) + } + } + fmt.Println() +} + +func parseNotificationClasses(classNames []string) api.NotificationClasses { + var result api.NotificationClasses + for _, name := range classNames { + nc := api.NewNotificationClass(name) + result = append(result, nc) + } + return result +} + +func getOrCreateTags(mytoken string, mtServer *mytokenlib.MytokenServer, tagNames []string) ([]api.Tag, error) { + var result []api.Tag + + // Get existing tags + existingResp, err := mtServer.UserSettings.Tags.APIGet(mytoken) + if err != nil { + return nil, err + } + + existingTagMap := make(map[string]bool) + for _, tag := range existingResp.Tags { + existingTagMap[string(tag.Tag)] = true + } + + // Process requested tags + for _, tagName := range tagNames { + if tagName == "" { + continue + } + + // If tag doesn't exist, create it with default color + if !existingTagMap[tagName] { + err := mtServer.UserSettings.Tags.APICreate(mytoken, tagName, "") + if err != nil { + return nil, fmt.Errorf("failed to create tag '%s': %w", tagName, err) + } + existingTagMap[tagName] = true + } + result = append(result, api.Tag(tagName)) + } + + return result, nil +} + +func displayManagementCode(code, entityType string) { + fmt.Printf("\n✓ %s created successfully! Management code: %s\n", strings.Title(entityType), code) +} + +func updateNotification(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("management-code is required") + } + managementCode := cmd.Args().Get(0) + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + addClasses := parseStringSlice(notificationsOptions.AddClasses) + removeClasses := parseStringSlice(notificationsOptions.RemoveClasses) + addTags := parseStringSlice(notificationsOptions.AddTags) + removeTags := parseStringSlice(notificationsOptions.RemoveTags) + + if len(addClasses) == 0 && len(removeClasses) == 0 && len(addTags) == 0 && len(removeTags) == 0 { + return fmt.Errorf("at least one of --add-classes, --remove-classes, --add-tags, or --remove-tags must be provided") + } + + // First, get current notification state + listRes, err := mtServer.Notifications.APIList(mytoken) + if err != nil { + return err + } + + var currentNotification *api.NotificationInfo + for _, n := range listRes.Notifications { + if n.ManagementCode == managementCode { + currentNotification = &n + break + } + } + if currentNotification == nil { + return fmt.Errorf("notification with management code '%s' not found", managementCode) + } + + // Build new classes list + newClasses := make(map[string]*api.NotificationClass) + for _, c := range currentNotification.Classes { + newClasses[c.Name] = c + } + + // Add classes + for _, className := range addClasses { + newClasses[className] = api.NewNotificationClass(className) + } + + // Remove classes + for _, className := range removeClasses { + delete(newClasses, className) + } + + // Convert map back to slice + finalClasses := make(api.NotificationClasses, 0, len(newClasses)) + for _, nc := range newClasses { + finalClasses = append(finalClasses, nc) + } + + // Build new tags list + newTags := make(map[api.Tag]bool) + for _, t := range currentNotification.Tags { + newTags[t.Tag] = true + } + + // Add tags (auto-create if needed) + apiAddTags, err := getOrCreateTags(mytoken, mtServer, addTags) + if err != nil { + return err + } + for _, t := range apiAddTags { + newTags[t] = true + } + + // Remove tags + for _, tagName := range removeTags { + delete(newTags, api.Tag(tagName)) + } + + // Convert map back to slice + finalTags := make([]api.Tag, 0, len(newTags)) + for t := range newTags { + finalTags = append(finalTags, t) + } + + req := api.NotificationUpdateRequest{ + Classes: &finalClasses, + Tags: &finalTags, + } + + res, err := mtServer.Notifications.APIUpdate(mytoken, managementCode, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Notification '%s' updated successfully\n", managementCode) + return nil +} + +func deleteNotification(ctx context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("management-code is required") + } + managementCode := cmd.Args().Get(0) + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + if !notificationsOptions.Force { + prompt := fmt.Sprintf("Are you sure you want to delete notification %s?", managementCode) + if !prompter.YN(prompt, false) { + fmt.Println("Cancelled.") + return nil + } + } + + res, err := mtServer.Notifications.APIDelete(mytoken, managementCode) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Notification '%s' deleted successfully\n", managementCode) + return nil +} + +func addTokenToNotification(_ context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("management-code is required") + } + managementCode := cmd.Args().Get(0) + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + req := api.NotificationAddTokenRequest{ + Mytoken: mytoken, + MOMID: notificationsOptions.MOMID, + IncludeChildren: notificationsOptions.IncludeChildren, + } + + res, err := mtServer.Notifications.APIAddToken(mytoken, managementCode, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Token added to notification '%s' successfully\n", managementCode) + return nil +} + +func removeTokenFromNotification(_ context.Context, cmd *cli.Command) error { + if cmd.Args().Len() < 1 { + return fmt.Errorf("management-code is required") + } + managementCode := cmd.Args().Get(0) + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + + req := api.NotificationRemoveTokenRequest{ + Mytoken: mytoken, + MOMID: notificationsOptions.MOMID, + } + + res, err := mtServer.Notifications.APIRemoveToken(mytoken, managementCode, req) + if err != nil { + return err + } + if res.TokenUpdate != nil { + updateMytoken(res.TokenUpdate.Mytoken) + } + + fmt.Printf("Token removed from notification '%s' successfully\n", managementCode) + return nil +} From ce9694cc7473c70a3c3bae4c13daf8b02f7c4884 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:08:55 +0200 Subject: [PATCH 14/21] update oidc-mytoken/lib to v0.8.0 and remove local replace directive --- go.mod | 4 +--- go.sum | 2 ++ 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2eac6dd..be5828d 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/mattn/go-isatty v0.0.22 github.com/mdp/qrterminal/v3 v3.2.1 github.com/oidc-mytoken/api v0.12.1 - github.com/oidc-mytoken/lib v0.7.1 + github.com/oidc-mytoken/lib v0.8.0 github.com/oidc-mytoken/utils v0.1.2 github.com/olekukonko/tablewriter v1.1.4 github.com/pkg/errors v0.9.1 @@ -48,5 +48,3 @@ require ( gopkg.in/square/go-jose.v2 v2.6.0 // indirect rsc.io/qr v0.2.0 // indirect ) - -replace github.com/oidc-mytoken/lib => ../lib diff --git a/go.sum b/go.sum index e3f5e2d..c59d6a0 100644 --- a/go.sum +++ b/go.sum @@ -224,6 +224,8 @@ github.com/mdp/qrterminal/v3 v3.2.1/go.mod h1:jOTmXvnBsMy5xqLniO0R++Jmjs2sTm9dFS github.com/oidc-mytoken/api v0.9.1/go.mod h1:DBIlUbaIgGlf607VZx8zFC97VR3WNN0kaMVO1AqyTdE= github.com/oidc-mytoken/api v0.12.1 h1:2VFZ2rFBRmWmqdjH1T2Yw1Z/qkwyNwZF0CBk6LGFumI= github.com/oidc-mytoken/api v0.12.1/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= +github.com/oidc-mytoken/lib v0.8.0 h1:7II+p0VfzgitiABdZfx/jZDpEcHFn+EcfjflxrQJMhM= +github.com/oidc-mytoken/lib v0.8.0/go.mod h1:KhQT/OLSviMWsQEj2C5S6vrq2sh14jpmkVzaMvwXfgU= github.com/oidc-mytoken/utils v0.1.2 h1:nmKIw/k4OkQ/9ww06W7krJ4yTwvDbuCRCjjRCmI9gSE= github.com/oidc-mytoken/utils v0.1.2/go.mod h1:pAUlvF/mYz8CjinCx4iwoWzG514B9EhuCpu7O+afaD8= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= From 2d0dc6be625c840adf14ff49b81afebc7481a8e0 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:13:16 +0200 Subject: [PATCH 15/21] update github.com/urfave/cli dependency to v3.8.0 --- go.mod | 2 +- go.sum | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index be5828d..0445123 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/olekukonko/tablewriter v1.1.4 github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 - github.com/urfave/cli/v3 v3.5.0 + github.com/urfave/cli/v3 v3.8.0 golang.org/x/net v0.53.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index c59d6a0..116ce9a 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,7 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -260,6 +261,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw= github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= +github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasthttp v1.41.0 h1:zeR0Z1my1wDHTRiamBCXVglQdbUwgb9uWG3k1HQz6jY= From 6c158746a3b1e7e859e70fac07251e6ba015091d Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:15:47 +0200 Subject: [PATCH 16/21] bump Go version in CI to 1.25 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1cf3702..36c8cd4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -6,7 +6,7 @@ variables: UPSTREAM_PROJECT: oidc-mytoken/client USE_GORELEASER: true -image: golang:1.24 +image: golang:1.25 stages: - build - test From 3ce50acf91bffea2115e2c042e9f7b891a5a8de2 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:23:34 +0200 Subject: [PATCH 17/21] update Go version in staticcheck job to 1.25 --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 36c8cd4..7809f5b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -38,7 +38,7 @@ test_race: - go test -race -v ./... staticcheck: - image: golang:1.24 + image: golang:1.25 stage: lint before_script: - go install honnef.co/go/tools/cmd/staticcheck@latest From 0f943e38c2cf5cea338873ffc367ab16f8928a43 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:39:21 +0200 Subject: [PATCH 18/21] replace deprecated `strings.Title` with `cases.Title` and update context import --- cmd/mytoken/main.go | 2 +- internal/commands/notifications.go | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/mytoken/main.go b/cmd/mytoken/main.go index 5f41a58..86dcce3 100644 --- a/cmd/mytoken/main.go +++ b/cmd/mytoken/main.go @@ -1,11 +1,11 @@ package main import ( + "context" "fmt" mytokenlib "github.com/oidc-mytoken/lib" "github.com/oidc-mytoken/utils/httpclient" - "golang.org/x/net/context" "github.com/oidc-mytoken/client/internal/commands" "github.com/oidc-mytoken/client/internal/model/version" diff --git a/internal/commands/notifications.go b/internal/commands/notifications.go index 3fab439..21c8e43 100644 --- a/internal/commands/notifications.go +++ b/internal/commands/notifications.go @@ -9,6 +9,8 @@ import ( "github.com/oidc-mytoken/api/v0" mytokenlib "github.com/oidc-mytoken/lib" "github.com/urfave/cli/v3" + "golang.org/x/text/cases" + "golang.org/x/text/language" "github.com/oidc-mytoken/client/internal/config" "github.com/oidc-mytoken/client/internal/utils/color" @@ -469,7 +471,9 @@ func getOrCreateTags(mytoken string, mtServer *mytokenlib.MytokenServer, tagName } func displayManagementCode(code, entityType string) { - fmt.Printf("\n✓ %s created successfully! Management code: %s\n", strings.Title(entityType), code) + fmt.Printf( + "\n✓ %s created successfully! Management code: %s\n", cases.Title(language.English).String(entityType), code, + ) } func updateNotification(ctx context.Context, cmd *cli.Command) error { From f6aa9708c8a80840bd0fef7ba93f251c3f5a089c Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 13:48:22 +0200 Subject: [PATCH 19/21] update `golang.org/x/text` dependency to v0.36.0 and adjust `golang.org/x/net` to indirect --- go.mod | 3 ++- go.sum | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 0445123..1c92a86 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.9.4 github.com/urfave/cli/v3 v3.8.0 - golang.org/x/net v0.53.0 + golang.org/x/text v0.36.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -40,6 +40,7 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.41.0 // indirect golang.org/x/crypto v0.50.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/term v0.42.0 // indirect diff --git a/go.sum b/go.sum index 116ce9a..fca2970 100644 --- a/go.sum +++ b/go.sum @@ -65,7 +65,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -259,8 +258,6 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/urfave/cli/v3 v3.5.0 h1:qCuFMmdayTF3zmjG8TSsoBzrDqszNrklYg2x3g4MSgw= -github.com/urfave/cli/v3 v3.5.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -481,6 +478,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= From 84e908583b54ec1c57580f5b924a90e4ab8c9011 Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 11:51:14 +0000 Subject: [PATCH 20/21] New features Resolved issues in the following files with DeepSource Autofix: 1. internal/commands/at.go 2. internal/commands/calendars.go 3. internal/commands/config.go 4. internal/commands/grants.go 5. internal/commands/mt.go 6. internal/commands/notifications.go 7. internal/commands/options.go 8. internal/commands/sshGrant.go 9. internal/commands/tags.go --- internal/commands/at.go | 2 +- internal/commands/calendars.go | 4 ++-- internal/commands/config.go | 2 +- internal/commands/grants.go | 4 ++-- internal/commands/mt.go | 2 +- internal/commands/notifications.go | 4 ++-- internal/commands/options.go | 2 +- internal/commands/sshGrant.go | 4 ++-- internal/commands/tags.go | 6 +++--- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/internal/commands/at.go b/internal/commands/at.go index db3bea8..83f301b 100644 --- a/internal/commands/at.go +++ b/internal/commands/at.go @@ -56,7 +56,7 @@ func init() { ) } -func getAT(ctx context.Context, cmd *cli.Command) error { +func getAT(_ context.Context, cmd *cli.Command) error { atc := atCommand var comment string if cmd.Args().Len() > 0 { diff --git a/internal/commands/calendars.go b/internal/commands/calendars.go index 6db2d99..7f85e87 100644 --- a/internal/commands/calendars.go +++ b/internal/commands/calendars.go @@ -290,7 +290,7 @@ func interactiveCreateCalendar(mytoken string, mtServer *mytokenlib.MytokenServe return nil } -func updateCalendar(ctx context.Context, cmd *cli.Command) error { +func updateCalendar(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("calendar-id is required") } @@ -371,7 +371,7 @@ func updateCalendar(ctx context.Context, cmd *cli.Command) error { return nil } -func deleteCalendar(ctx context.Context, cmd *cli.Command) error { +func deleteCalendar(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("calendar-id is required") } diff --git a/internal/commands/config.go b/internal/commands/config.go index bec39e1..a79ce37 100644 --- a/internal/commands/config.go +++ b/internal/commands/config.go @@ -71,7 +71,7 @@ func getCapabilities(_ context.Context, _ *cli.Command) error { } // Make HTTP request to capabilities endpoint - req, err := http.NewRequest("GET", capsURL, nil) + req, err := http.NewRequest("GET", capsURL, http.NoBody) if err != nil { return err } diff --git a/internal/commands/grants.go b/internal/commands/grants.go index a857ead..808f546 100644 --- a/internal/commands/grants.go +++ b/internal/commands/grants.go @@ -86,7 +86,7 @@ func (i tableGrantTypeInfo) TableGetRow() []string { } } -func enableGrant(ctx context.Context, cmd *cli.Command) error { +func enableGrant(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 1 { return fmt.Errorf("Must provide exactly one grant to enable") } @@ -103,7 +103,7 @@ func enableGrant(ctx context.Context, cmd *cli.Command) error { return nil } -func disableGrant(ctx context.Context, cmd *cli.Command) error { +func disableGrant(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() != 1 { return fmt.Errorf("Must provide exactly one grant to disable") } diff --git a/internal/commands/mt.go b/internal/commands/mt.go index c7a60be..cdb7315 100644 --- a/internal/commands/mt.go +++ b/internal/commands/mt.go @@ -186,7 +186,7 @@ func parseRestrictionOpts(rOpts restrictionOpts, cmd *cli.Command) (api.Restrict return mto.request.Restrictions, nil } -func (opts *mtOpts) Request(ctx context.Context, cmd *cli.Command) (*api.GeneralMytokenRequest, error) { +func (opts *mtOpts) Request(_ context.Context, cmd *cli.Command) (*api.GeneralMytokenRequest, error) { if opts.request != nil { return opts.request, nil } diff --git a/internal/commands/notifications.go b/internal/commands/notifications.go index 21c8e43..b9115a2 100644 --- a/internal/commands/notifications.go +++ b/internal/commands/notifications.go @@ -476,7 +476,7 @@ func displayManagementCode(code, entityType string) { ) } -func updateNotification(ctx context.Context, cmd *cli.Command) error { +func updateNotification(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("management-code is required") } @@ -575,7 +575,7 @@ func updateNotification(ctx context.Context, cmd *cli.Command) error { return nil } -func deleteNotification(ctx context.Context, cmd *cli.Command) error { +func deleteNotification(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("management-code is required") } diff --git a/internal/commands/options.go b/internal/commands/options.go index 388ef53..f78e342 100644 --- a/internal/commands/options.go +++ b/internal/commands/options.go @@ -20,7 +20,7 @@ var app = &cli.Command{ "Gabriel Zachmann ", }, Copyright: "Karlsruhe Institute of Technology 2020-2026", - Action: func(ctx context.Context, cmd *cli.Command) error { + Action: func(_ context.Context, cmd *cli.Command) error { return cli.ShowAppHelp(cmd) }, } diff --git a/internal/commands/sshGrant.go b/internal/commands/sshGrant.go index 774c1ec..8e408b5 100644 --- a/internal/commands/sshGrant.go +++ b/internal/commands/sshGrant.go @@ -131,7 +131,7 @@ func (i tableSSHKeyInfo) TableGetRow() []string { } } -func addSSHKey(ctx context.Context, cmd *cli.Command) error { +func addSSHKey(_ context.Context, cmd *cli.Command) error { if cmd.NArg() != 1 { if cmd.NArg() > 1 { return fmt.Errorf("Need exactly one argument") @@ -205,7 +205,7 @@ func addSSHKey(ctx context.Context, cmd *cli.Command) error { return nil } -func deleteSSHKey(ctx context.Context, cmd *cli.Command) error { +func deleteSSHKey(_ context.Context, cmd *cli.Command) error { if cmd.NArg() != 1 { if cmd.NArg() > 1 { return fmt.Errorf("Need exactly one argument") diff --git a/internal/commands/tags.go b/internal/commands/tags.go index 8783db9..05fd975 100644 --- a/internal/commands/tags.go +++ b/internal/commands/tags.go @@ -115,7 +115,7 @@ func listTags(_ context.Context, _ *cli.Command) error { return nil } -func createTag(ctx context.Context, cmd *cli.Command) error { +func createTag(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("tag name required") } @@ -141,7 +141,7 @@ func createTag(ctx context.Context, cmd *cli.Command) error { return nil } -func updateTag(ctx context.Context, cmd *cli.Command) error { +func updateTag(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("tag name required") } @@ -174,7 +174,7 @@ func updateTag(ctx context.Context, cmd *cli.Command) error { return nil } -func deleteTag(ctx context.Context, cmd *cli.Command) error { +func deleteTag(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("tag name required") } From 34e274699092a4f4bea82969b65210c65c619e2b Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 7 May 2026 15:08:05 +0200 Subject: [PATCH 21/21] update `oidc-mytoken/lib` dependency to v0.8.1-0.20260507130659-1d2d0a003337 --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 1c92a86..f6c4504 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/mattn/go-isatty v0.0.22 github.com/mdp/qrterminal/v3 v3.2.1 github.com/oidc-mytoken/api v0.12.1 - github.com/oidc-mytoken/lib v0.8.0 + github.com/oidc-mytoken/lib v0.8.1-0.20260507130659-1d2d0a003337 github.com/oidc-mytoken/utils v0.1.2 github.com/olekukonko/tablewriter v1.1.4 github.com/pkg/errors v0.9.1 diff --git a/go.sum b/go.sum index fca2970..716eeb2 100644 --- a/go.sum +++ b/go.sum @@ -226,6 +226,8 @@ github.com/oidc-mytoken/api v0.12.1 h1:2VFZ2rFBRmWmqdjH1T2Yw1Z/qkwyNwZF0CBk6LGFu github.com/oidc-mytoken/api v0.12.1/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= github.com/oidc-mytoken/lib v0.8.0 h1:7II+p0VfzgitiABdZfx/jZDpEcHFn+EcfjflxrQJMhM= github.com/oidc-mytoken/lib v0.8.0/go.mod h1:KhQT/OLSviMWsQEj2C5S6vrq2sh14jpmkVzaMvwXfgU= +github.com/oidc-mytoken/lib v0.8.1-0.20260507130659-1d2d0a003337 h1:bjVCDUP1wPX9hNjhNO5sVOli0vxXK4fTTQUoLGxrdqQ= +github.com/oidc-mytoken/lib v0.8.1-0.20260507130659-1d2d0a003337/go.mod h1:KhQT/OLSviMWsQEj2C5S6vrq2sh14jpmkVzaMvwXfgU= github.com/oidc-mytoken/utils v0.1.2 h1:nmKIw/k4OkQ/9ww06W7krJ4yTwvDbuCRCjjRCmI9gSE= github.com/oidc-mytoken/utils v0.1.2/go.mod h1:pAUlvF/mYz8CjinCx4iwoWzG514B9EhuCpu7O+afaD8= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc=