From 3d84fba4ca0c0260cc6c3927c8b76ac192fd6f29 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:06:33 +0000 Subject: [PATCH 01/42] Bump golang.org/x/net from 0.27.0 to 0.30.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.27.0 to 0.30.0. - [Commits](https://github.com/golang/net/compare/v0.27.0...v0.30.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 8 ++++---- go.sum | 22 ++++++++-------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 028463d..cdd6f77 100644 --- a/go.mod +++ b/go.mod @@ -13,8 +13,8 @@ require ( 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 + golang.org/x/net v0.30.0 + golang.org/x/term v0.25.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -34,9 +34,9 @@ require ( github.com/shurcooL/sanitized_anchor_name v1.0.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.28.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.26.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..53e9498 100644 --- a/go.sum +++ b/go.sum @@ -212,12 +212,8 @@ github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m 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/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= @@ -249,8 +245,6 @@ 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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -279,8 +273,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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= 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 +356,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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= 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= @@ -459,12 +453,12 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc 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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= +golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= 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= From 6b0385f69ce0fddf23ffeb47b693ab3a45d48be1 Mon Sep 17 00:00:00 2001 From: Gabriel Zachmann Date: Thu, 21 Aug 2025 14:23:01 +0200 Subject: [PATCH 02/42] [ci] use go 1.24 --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a7dd051..a4d6477 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,7 +4,7 @@ include: variables: UPSTREAM_PROJECT: oidc-mytoken/client -image: golang:1.22 +image: golang:1.24 stages: - build - test @@ -34,7 +34,7 @@ test_race: - go test -race -v ./... staticcheck: - image: golang:1.22 + image: golang:1.24 stage: lint before_script: - go install honnef.co/go/tools/cmd/staticcheck@latest From 07ac1346e6ee02fbaf12a702c114c87ba45daa22 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Aug 2025 15:00:50 +0200 Subject: [PATCH 03/42] add package and publish stages --- .gitlab-ci.yml | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a7dd051..82aaf25 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,6 @@ include: - 'https://codebase.helmholtz.cloud/m-team/tools/ci-voodoo/-/raw/master/ci-include/github-status-sync.yml' + - "https://codebase.helmholtz.cloud/m-team/tools/ci-voodoo/raw/master/ci-include/pipeline-jobs-publish-to-repo.yml" variables: UPSTREAM_PROJECT: oidc-mytoken/client @@ -9,6 +10,8 @@ stages: - build - test - lint + - package + - publish - release default: @@ -51,6 +54,48 @@ build: script: - go build github.com/oidc-mytoken/client/cmd/mytoken +# fixme: consider moving this to a central place (since it's similar in +# mytoken server and oinit) +package: + stage: package + image: + name: goreleaser/goreleaser + entrypoint: [ "" ] + services: + - docker:dind +# only: +# refs: +# - tags +# - prerel + variables: + GIT_STRATEGY: clone + GIT_DEPTH: 0 + REPO_HOST: repo.data.kit.edu + REPO_USER: cicd + DOCKER_REGISTRY: https://index.docker.io/v1/ + before_script: + - echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin "$DOCKER_REGISTRY" + script: + - | + [[ "$CI_COMMIT_BRANCH" == "${PREREL_BRANCH_NAME}" ]] && { + echo "Running set-prerel-version" + .gitlab-ci-scripts/set-prerel-version.sh + } + [[ "$CI_COMMIT_BRANCH" != "${PREREL_BRANCH_NAME}" ]] && { + [[ "$CI_COMMIT_BRANCH" != "${CI_DEFAULT_BRANCH}" ]] && { + # we're on a devel branch + echo "Running set-devel-version" + .gitlab-ci-scripts/set-prerel-version.sh --devstring dev + } + } + .gitlab-ci-scripts/goreleaser.sh + + artifacts: + paths: + - "results/*" + after_script: + - curl -d "repo=github.com/oidc-mytoken/server" https://goreportcard.com/checks + prerelease: stage: release image: From f709e36f010a6a55f7e294a266f7323a26f4059b Mon Sep 17 00:00:00 2001 From: Gabriel Zachmann Date: Thu, 21 Aug 2025 15:42:58 +0200 Subject: [PATCH 04/42] Update VERSION --- internal/model/version/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/model/version/VERSION b/internal/model/version/VERSION index 7ceb040..b616048 100644 --- a/internal/model/version/VERSION +++ b/internal/model/version/VERSION @@ -1 +1 @@ -0.6.1 \ No newline at end of file +0.6.2 From beee843bb19950a407594eb7e0cc06d4b2c839bf Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Aug 2025 15:53:47 +0200 Subject: [PATCH 05/42] fix indent --- .gitlab-ci-scripts/upload.sh | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.gitlab-ci-scripts/upload.sh b/.gitlab-ci-scripts/upload.sh index acd456b..620bab6 100755 --- a/.gitlab-ci-scripts/upload.sh +++ b/.gitlab-ci-scripts/upload.sh @@ -1,20 +1,20 @@ REPO_TARGET="/prerel" if [ -n "$CI_COMMIT_TAG" ] && echo "$CI_COMMIT_TAG" | grep -qv '~'; then - REPO_TARGET="/preprod" + REPO_TARGET="/preprod" fi # ssh-key-script [ -e /tmp/ssh-private-keys/${REPO_USER} ] && { - eval $(ssh-agent -s) - cat /tmp/ssh-private-keys/${REPO_USER} | tr -d '\r' | ssh-add - - test -d ~/.ssh || mkdir -p ~/.ssh - chmod 700 ~/.ssh + eval $(ssh-agent -s) + cat /tmp/ssh-private-keys/${REPO_USER} | tr -d '\r' | ssh-add - + test -d ~/.ssh || mkdir -p ~/.ssh + chmod 700 ~/.ssh } [ -e /tmp/ssh-private-keys/known_hosts ] && { - test -d ~/.ssh || mkdir -p ~/.ssh - cp /tmp/ssh-private-keys/known_hosts ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts + test -d ~/.ssh || mkdir -p ~/.ssh + cp /tmp/ssh-private-keys/known_hosts ~/.ssh/known_hosts + chmod 644 ~/.ssh/known_hosts } ssh-add -l ssh -o StrictHostKeyChecking=no "${REPO_USER}@${REPO_HOST}" "hostname -f" @@ -25,9 +25,9 @@ sign_repos() { } upload_files() { - UPLOAD_DIR=/tmp/package-upload - ssh "${REPO_USER}@${REPO_HOST}" "rm -rf $UPLOAD_DIR && mkdir -p $UPLOAD_DIR" - scp results/* "${REPO_USER}@${REPO_HOST}:${UPLOAD_DIR}" + UPLOAD_DIR=/tmp/package-upload + ssh "${REPO_USER}@${REPO_HOST}" "rm -rf $UPLOAD_DIR && mkdir -p $UPLOAD_DIR" + scp results/* "${REPO_USER}@${REPO_HOST}:${UPLOAD_DIR}" } distribute_files() { From a8e89b235709280e78cdabd269e7052ee2db024e Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Aug 2025 15:54:03 +0200 Subject: [PATCH 06/42] Fix shebang --- .gitlab-ci-scripts/upload.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci-scripts/upload.sh b/.gitlab-ci-scripts/upload.sh index 620bab6..565b47b 100755 --- a/.gitlab-ci-scripts/upload.sh +++ b/.gitlab-ci-scripts/upload.sh @@ -1,3 +1,4 @@ +#!/bin/bash REPO_TARGET="/prerel" if [ -n "$CI_COMMIT_TAG" ] && echo "$CI_COMMIT_TAG" | grep -qv '~'; then From 5051dc798e5f5a83903acf4a33cf7b5eb835d753 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Aug 2025 16:05:05 +0200 Subject: [PATCH 07/42] use PUBLISH_BUILD_RESULTS_REPO --- .gitlab-ci-scripts/upload.sh | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci-scripts/upload.sh b/.gitlab-ci-scripts/upload.sh index 565b47b..a3f6b28 100755 --- a/.gitlab-ci-scripts/upload.sh +++ b/.gitlab-ci-scripts/upload.sh @@ -1,9 +1,14 @@ #!/bin/bash -REPO_TARGET="/prerel" -if [ -n "$CI_COMMIT_TAG" ] && echo "$CI_COMMIT_TAG" | grep -qv '~'; then - REPO_TARGET="/preprod" -fi +# REPO_TARGET="/prerel" +# if [ -n "$CI_COMMIT_TAG" ] && echo "$CI_COMMIT_TAG" | grep -qv '~'; then +# REPO_TARGET="/preprod" +# fi +REPO_TARGET=${PUBLISH_BUILD_RESULTS_REPO} + +echo "REPO_TO_PUBLISH_TO: ${REPO_TO_PUBLISH_TO}" +echo "PUBLISH_BUILD_RESULTS_REPO: ${PUBLISH_BUILD_RESULTS_REPO}" +echo "REPO_TARGET: ${REPO_TARGET}" # ssh-key-script [ -e /tmp/ssh-private-keys/${REPO_USER} ] && { From 9c7bfd2aff1d7c67c1365e314ac0dac633656d4d Mon Sep 17 00:00:00 2001 From: Gabriel Zachmann Date: Thu, 21 Aug 2025 14:23:01 +0200 Subject: [PATCH 08/42] [ci] use go 1.24 --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 82aaf25..c823253 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,7 +5,7 @@ include: variables: UPSTREAM_PROJECT: oidc-mytoken/client -image: golang:1.22 +image: golang:1.24 stages: - build - test @@ -37,7 +37,7 @@ test_race: - go test -race -v ./... staticcheck: - image: golang:1.22 + image: golang:1.24 stage: lint before_script: - go install honnef.co/go/tools/cmd/staticcheck@latest From 4d54f69f0171d75365f688cc3ebf0ff7bdc6c37e Mon Sep 17 00:00:00 2001 From: Gabriel Zachmann Date: Thu, 21 Aug 2025 15:42:58 +0200 Subject: [PATCH 09/42] Update VERSION --- internal/model/version/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/model/version/VERSION b/internal/model/version/VERSION index 7ceb040..b616048 100644 --- a/internal/model/version/VERSION +++ b/internal/model/version/VERSION @@ -1 +1 @@ -0.6.1 \ No newline at end of file +0.6.2 From 7b09638cc1656d458eb43590c7a2c079afbe4eba Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Aug 2025 16:32:36 +0200 Subject: [PATCH 10/42] use goreleaser --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c823253..0d0ca9c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,7 @@ include: variables: UPSTREAM_PROJECT: oidc-mytoken/client + USE_GORELEASER: true image: golang:1.24 stages: From 4863d088d226079f083f78385e591e41f2434549 Mon Sep 17 00:00:00 2001 From: Marcus Date: Thu, 21 Aug 2025 16:48:52 +0200 Subject: [PATCH 11/42] not needed --- .gitlab-ci-scripts/upload.sh | 47 ------------------------------------ 1 file changed, 47 deletions(-) delete mode 100755 .gitlab-ci-scripts/upload.sh diff --git a/.gitlab-ci-scripts/upload.sh b/.gitlab-ci-scripts/upload.sh deleted file mode 100755 index a3f6b28..0000000 --- a/.gitlab-ci-scripts/upload.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash - -# REPO_TARGET="/prerel" -# if [ -n "$CI_COMMIT_TAG" ] && echo "$CI_COMMIT_TAG" | grep -qv '~'; then -# REPO_TARGET="/preprod" -# fi -REPO_TARGET=${PUBLISH_BUILD_RESULTS_REPO} - -echo "REPO_TO_PUBLISH_TO: ${REPO_TO_PUBLISH_TO}" -echo "PUBLISH_BUILD_RESULTS_REPO: ${PUBLISH_BUILD_RESULTS_REPO}" -echo "REPO_TARGET: ${REPO_TARGET}" - -# ssh-key-script -[ -e /tmp/ssh-private-keys/${REPO_USER} ] && { - eval $(ssh-agent -s) - cat /tmp/ssh-private-keys/${REPO_USER} | tr -d '\r' | ssh-add - - test -d ~/.ssh || mkdir -p ~/.ssh - chmod 700 ~/.ssh -} -[ -e /tmp/ssh-private-keys/known_hosts ] && { - test -d ~/.ssh || mkdir -p ~/.ssh - cp /tmp/ssh-private-keys/known_hosts ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts -} -ssh-add -l -ssh -o StrictHostKeyChecking=no "${REPO_USER}@${REPO_HOST}" "hostname -f" - -# sign-repo function -sign_repos() { - ssh "${REPO_USER}@${REPO_HOST}" "~/ci-voodoo/ci-tools/sign-all-repos.sh -t ${REPO_TARGET}" -} - -upload_files() { - UPLOAD_DIR=/tmp/package-upload - ssh "${REPO_USER}@${REPO_HOST}" "rm -rf $UPLOAD_DIR && mkdir -p $UPLOAD_DIR" - scp results/* "${REPO_USER}@${REPO_HOST}:${UPLOAD_DIR}" -} - -distribute_files() { - ssh "${REPO_USER}@${REPO_HOST}" "~/ci-voodoo/ci-tools/distribute-local-packages.sh -t ${REPO_TARGET} -w mytoken" -} - - -# upload and sign -upload_files -distribute_files -sign_repos From 39b847445572c5daf0f2913d3888b8ff49100ead Mon Sep 17 00:00:00 2001 From: Marcus Date: Fri, 22 Aug 2025 09:40:02 +0200 Subject: [PATCH 12/42] remove upload.sh --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0d0ca9c..1cf3702 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -121,6 +121,5 @@ prerelease: script: - if [ -z "$CI_COMMIT_TAG" ]; then .gitlab-ci-scripts/set-prerel-version.sh; fi; - .gitlab-ci-scripts/goreleaser.sh - - .gitlab-ci-scripts/upload.sh after_script: - curl -d "repo=github.com/oidc-mytoken/client" https://goreportcard.com/checks From bdc8152a43633f337afe0b48801e6c612c65f00b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 05:36:21 +0000 Subject: [PATCH 13/42] Bump golang.org/x/term from 0.22.0 to 0.25.0 Bumps [golang.org/x/term](https://github.com/golang/term) from 0.22.0 to 0.25.0. - [Commits](https://github.com/golang/term/compare/v0.22.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 6 +++--- go.sum | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.mod b/go.mod index cdd6f77..2c58c48 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/oidc-mytoken/client -go 1.22 +go 1.24.0 require ( github.com/Songmu/prompter v0.5.1 @@ -14,7 +14,7 @@ require ( github.com/sirupsen/logrus v1.9.3 github.com/urfave/cli/v2 v2.3.1-0.20211205195634-e8d81738896c golang.org/x/net v0.30.0 - golang.org/x/term v0.25.0 + golang.org/x/term v0.36.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -36,7 +36,7 @@ require ( github.com/valyala/fasthttp v1.41.0 // indirect golang.org/x/crypto v0.28.0 // indirect golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect - golang.org/x/sys v0.26.0 // indirect + golang.org/x/sys v0.37.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 53e9498..67bac74 100644 --- a/go.sum +++ b/go.sum @@ -453,12 +453,12 @@ golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBc 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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= -golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= 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.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24= -golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= 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= From f8414de74fa055f9711c40f1fb851df4cb2a4421 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 06:08:27 +0000 Subject: [PATCH 14/42] Bump golang.org/x/net from 0.30.0 to 0.38.0 Bumps [golang.org/x/net](https://github.com/golang/net) from 0.30.0 to 0.38.0. - [Commits](https://github.com/golang/net/compare/v0.30.0...v0.38.0) --- updated-dependencies: - dependency-name: golang.org/x/net dependency-version: 0.38.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 2c58c48..6910a54 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( 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.30.0 + golang.org/x/net v0.38.0 golang.org/x/term v0.36.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -34,7 +34,7 @@ require ( github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.41.0 // indirect - golang.org/x/crypto v0.28.0 // indirect + golang.org/x/crypto v0.36.0 // indirect golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect golang.org/x/sys v0.37.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index 67bac74..ac8894a 100644 --- a/go.sum +++ b/go.sum @@ -273,8 +273,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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= -golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= 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= @@ -356,8 +356,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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= -golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= +golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 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= From b72c6528608490ee69d09bc19aaf96f000627742 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 Oct 2025 07:24:14 +0000 Subject: [PATCH 15/42] Bump golang.org/x/oauth2 Bumps [golang.org/x/oauth2](https://github.com/golang/oauth2) from 0.0.0-20220822191816-0ebed06d0094 to 0.27.0. - [Commits](https://github.com/golang/oauth2/commits/v0.27.0) --- updated-dependencies: - dependency-name: golang.org/x/oauth2 dependency-version: 0.27.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- go.mod | 5 +---- go.sum | 10 ++++------ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 6910a54..c2d4023 100644 --- a/go.mod +++ b/go.mod @@ -26,7 +26,6 @@ require ( github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/go-resty/resty/v2 v2.7.0 // 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 @@ -35,10 +34,8 @@ require ( github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.41.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 // indirect + golang.org/x/oauth2 v0.27.0 // indirect golang.org/x/sys v0.37.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 ) diff --git a/go.sum b/go.sum index ac8894a..fb08e38 100644 --- a/go.sum +++ b/go.sum @@ -138,7 +138,6 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -156,8 +155,9 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -378,8 +378,9 @@ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094 h1:2o1E+E8TpNLklK9nHiPiK1uzIYrIHt+cQx3ynCwq9V8= golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= +golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= +golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -574,7 +575,6 @@ google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -700,8 +700,6 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From f42222227fc1bacc3e6de736152f878c9c343aee Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 4 May 2026 12:08:59 +0200 Subject: [PATCH 16/42] 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 17/42] 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 18/42] 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 19/42] 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 20/42] 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 21/42] 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 22/42] 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 23/42] 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 24/42] 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 25/42] 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 26/42] 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 27/42] 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 28/42] 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 29/42] 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 30/42] 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 31/42] 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 32/42] 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 33/42] 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 34/42] 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 35/42] 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 36/42] 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= From ed407f4208026979639e56dc11fdd2e30fe2b8d4 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 8 May 2026 13:03:16 +0200 Subject: [PATCH 37/42] update README: adjust documentation link, add installation and usage instructions --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e53c926..3d8dcc8 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ [![License](https://img.shields.io/github/license/oidc-mytoken/client.svg)](https://github.com/oidc-mytoken/client/blob/master/LICENSE) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/oidc-mytoken/client) -![GitHub Workflow Status](https://img.shields.io/github/workflow/status/oidc-mytoken/client/Go) [![Go Report](https://goreportcard.com/badge/github.com/oidc-mytoken/client)](https://goreportcard.com/report/github.com/oidc-mytoken/client) [![DeepSource](https://deepsource.io/gh/oidc-mytoken/client.svg/?label=active+issues&show_trend=true)](https://deepsource.io/gh/oidc-mytoken/client/?ref=repository-badge) [![Release date](https://img.shields.io/github/release-date/oidc-mytoken/client.svg)](https://github.com/oidc-mytoken/client/releases/latest) @@ -24,7 +23,49 @@ any device. These `mytokens` can be restricted according to the particular use c A demo instance of the server and webinterface is available at [https://mytoken.data.kit.edu/](https://mytoken.data.kit.edu/). -Documentation is available at [https://mytoken-docs.data.kit.edu/](https://mytoken-docs.data.kit.edu/). +Documentation is available at [https://docs.mytok.eu/](https://docs.mytok.eu/). A go library for interacting with the mytoken server can be found at [https://github.com/oidc-mytoken/lib](https://github.com/oidc-mytoken/lib). + +# Quickstart + +## Installation + +Packaged versions are available from http://repo.data.kit.edu + +The latest version can also be installed via `go`: + +```bash +go install github.com/oidc-mytoken/client/cmd/mytoken@latest +``` + +## Basic Usage + +### Obtain a mytoken + +Get a mytoken: + +```bash +mytoken MT --url -i -o +``` + +The mytoken will be stored in ``. + +### Get an Access Token + +Use your mytoken to obtain an OIDC access token: + +```bash +mytoken AT --MT-file +``` + +## Using Mytokens + +Mytokens can be provided in several ways: + +- **File** (default): Stored in `~/.mytoken/default/mytoken` +- **Environment variable**: `mytoken AT --MT-env MYTOKEN_VAR` +- **From file**: `mytoken AT --MT-file /path/to/token` +- **Interactive prompt**: `mytoken AT --MT-prompt` +- **Direct**: `mytoken AT --MT ` (less secure) From bbc7285d7b48f0161b6720b9eccc3fac366cdf35 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 15:42:34 +0000 Subject: [PATCH 38/42] Bump golang.org/x/text from 0.36.0 to 0.37.0 Bumps [golang.org/x/text](https://github.com/golang/text) from 0.36.0 to 0.37.0. - [Release notes](https://github.com/golang/text/releases) - [Commits](https://github.com/golang/text/compare/v0.36.0...v0.37.0) --- updated-dependencies: - dependency-name: golang.org/x/text dependency-version: 0.37.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index f6c4504..7de7d1d 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/text v0.36.0 + golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 716eeb2..825d08f 100644 --- a/go.sum +++ b/go.sum @@ -224,8 +224,6 @@ 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/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= @@ -480,8 +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/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= 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 a91f5ade25876f307b152936f1447707ac85897a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 21:11:57 +0000 Subject: [PATCH 39/42] Bump github.com/urfave/cli/v3 from 3.8.0 to 3.9.0 Bumps [github.com/urfave/cli/v3](https://github.com/urfave/cli) from 3.8.0 to 3.9.0. - [Release notes](https://github.com/urfave/cli/releases) - [Changelog](https://github.com/urfave/cli/blob/main/docs/CHANGELOG.md) - [Commits](https://github.com/urfave/cli/compare/v3.8.0...v3.9.0) --- updated-dependencies: - dependency-name: github.com/urfave/cli/v3 dependency-version: 3.9.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7de7d1d..0327a13 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.8.0 + github.com/urfave/cli/v3 v3.9.0 golang.org/x/text v0.37.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 825d08f..38a8ebc 100644 --- a/go.sum +++ b/go.sum @@ -258,8 +258,8 @@ 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.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= -github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/urfave/cli/v3 v3.9.0 h1:AV9lIiPv3ukYnxunaCUsHnEozptYmDN2F0+yWqLMn/c= +github.com/urfave/cli/v3 v3.9.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 b007a31a03549eccfb2605853e721364812f3819 Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 18 May 2026 15:51:11 +0200 Subject: [PATCH 40/42] replace custom tag table output logic with reusable `tablewriter` utility --- internal/commands/tags.go | 47 +++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/internal/commands/tags.go b/internal/commands/tags.go index 05fd975..9d4442d 100644 --- a/internal/commands/tags.go +++ b/internal/commands/tags.go @@ -3,12 +3,13 @@ package commands import ( "context" "fmt" - "regexp" + "github.com/oidc-mytoken/api/v0" "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 tagsOptions = struct { @@ -90,31 +91,33 @@ func listTags(_ context.Context, _ *cli.Command) error { 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) + outputData := make([]tablewriter.TableWriter, len(res.Tags)) + for i, t := range res.Tags { + outputData[i] = tableTagInfo(t) } + tablewriter.PrintTableData(outputData) return nil } +type tableTagInfo api.TagInfo + +func (tableTagInfo) TableGetHeader() []string { + return []string{ + "Color Block", + "Tag", + "Color (Hex)", + } +} + +func (t tableTagInfo) TableGetRow() []string { + colorBlock := color.TerminalColorBlock(t.Color) + return []string{ + colorBlock, + string(t.Tag), + t.Color, + } +} + func createTag(_ context.Context, cmd *cli.Command) error { if cmd.Args().Len() < 1 { return fmt.Errorf("tag name required") From 6bd3dd43f79bc8b7d64f02f71f26eccf043a12f0 Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 19 May 2026 13:42:49 +0200 Subject: [PATCH 41/42] add error handling for empty access tokens and mytokens returned by the server --- internal/commands/at.go | 4 ++++ internal/commands/mt.go | 21 +++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/internal/commands/at.go b/internal/commands/at.go index 83f301b..f1981d9 100644 --- a/internal/commands/at.go +++ b/internal/commands/at.go @@ -2,6 +2,7 @@ package commands import ( "context" + "errors" "os" "github.com/oidc-mytoken/api/v0" @@ -74,6 +75,9 @@ func getAT(_ context.Context, cmd *cli.Command) error { if err != nil { return err } + if atRes.AccessToken == "" { + return errors.New("server returned empty access token") + } if atRes.TokenUpdate != nil { updateMytoken(atRes.TokenUpdate.Mytoken) } diff --git a/internal/commands/mt.go b/internal/commands/mt.go index cdb7315..d8145c5 100644 --- a/internal/commands/mt.go +++ b/internal/commands/mt.go @@ -3,6 +3,7 @@ package commands import ( "context" "encoding/json" + "errors" "fmt" "os" "strings" @@ -486,7 +487,14 @@ func obtainMTCmd(ctx context.Context, cmd *cli.Command) error { func obtainMT(ctx context.Context, cmd *cli.Command) (string, error) { mytoken := config.Get().Mytoken() if mtCommand.TransferCode != "" { - return mytoken.Mytoken.FromTransferCode(mtCommand.TransferCode) + mt, err := mytoken.Mytoken.FromTransferCode(mtCommand.TransferCode) + if err != nil { + return "", err + } + if mt == "" { + return "", errors.New("server returned empty mytoken") + } + return mt, nil } req, err := mtCommand.Request(ctx, cmd) if err != nil { @@ -518,6 +526,9 @@ func obtainMT(ctx context.Context, cmd *cli.Command) (string, error) { if err != nil { return "", err } + if mtRes.Mytoken == "" { + return "", errors.New("server returned empty mytoken") + } if mtRes.TokenUpdate != nil { updateMytoken(mtRes.TokenUpdate.Mytoken) } @@ -557,5 +568,11 @@ func obtainMT(ctx context.Context, cmd *cli.Command) (string, error) { }, } resp, err := mytoken.Mytoken.APIFromAuthorizationFlowReq(*req, callbacks) - return resp.Mytoken, err + if err != nil { + return "", err + } + if resp.Mytoken == "" { + return "", errors.New("server returned empty mytoken") + } + return resp.Mytoken, nil } From 48b6e759e39e1a93345dbc03bcb6535cc702cc2e Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 2 Jun 2026 09:06:13 +0200 Subject: [PATCH 42/42] add ssh support for calendars, notifications, tags, email, and token management commands --- go.mod | 2 +- go.sum | 4 +- internal/commands/calendars.go | 127 +++++++++++++++++++++++++- internal/commands/email.go | 23 ++++- internal/commands/general.go | 40 +++++++++ internal/commands/info.go | 77 ++++++++-------- internal/commands/mttags.go | 28 ++++++ internal/commands/notifications.go | 138 ++++++++++++++++++++++++++++- internal/commands/revoke.go | 10 +++ internal/commands/ssh.go | 18 +++- internal/commands/ssh_requests.go | 55 ++++++++++++ internal/commands/tags.go | 61 ++++++++++++- 12 files changed, 530 insertions(+), 53 deletions(-) create mode 100644 internal/commands/ssh_requests.go diff --git a/go.mod b/go.mod index 0327a13..1990420 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( 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.1 + github.com/oidc-mytoken/api v0.12.2-0.20260529132908-2e1359c93a95 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 diff --git a/go.sum b/go.sum index 38a8ebc..257cdfb 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.1 h1:2VFZ2rFBRmWmqdjH1T2Yw1Z/qkwyNwZF0CBk6LGFumI= -github.com/oidc-mytoken/api v0.12.1/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= +github.com/oidc-mytoken/api v0.12.2-0.20260529132908-2e1359c93a95 h1:e+iQlFDdJpjEV2nQrDy5XtH6K6OO7jScm+SHnzgradY= +github.com/oidc-mytoken/api v0.12.2-0.20260529132908-2e1359c93a95/go.mod h1:4QwDXesKKEzbTmH2vENYCVcmdb5/gbDPg3DOm9HQLdg= 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= diff --git a/internal/commands/calendars.go b/internal/commands/calendars.go index 7f85e87..29a0792 100644 --- a/internal/commands/calendars.go +++ b/internal/commands/calendars.go @@ -151,6 +151,18 @@ func init() { } func listCalendars(_ context.Context, _ *cli.Command) error { + if ssh := calendarsOptions.SSH(); ssh != "" { + res, err := doSSHParseJSON[api.CalendarListResponse](ssh, api.SSHRequestCalendars, nil) + if err != nil { + return err + } + outputData := make([]tablewriter.TableWriter, len(res.Calendars)) + for i, c := range res.Calendars { + outputData[i] = tableCalendarInfo(c) + } + tablewriter.PrintTableData(outputData) + return nil + } mytoken := calendarsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -215,6 +227,24 @@ func createCalendar(_ context.Context, _ *cli.Command) error { } tags := parseStringSlice(calendarsOptions.Tags) + + if ssh := calendarsOptions.SSH(); ssh != "" { + req := api.CreateCalendarRequest{ + Description: calendarsOptions.Description, + Tags: stringSliceToTags(tags), + } + createRes, err := doSSHParseJSON[struct { + ID string `json:"id"` + Description string `json:"description"` + ICSPath string `json:"ics_path"` + }](ssh, api.SSHRequestCalendarCreate, &req) + if err != nil { + return err + } + fmt.Printf("\n✓ Calendar created successfully! ID: %s\n", createRes.ID) + return nil + } + apiTags, err := getOrCreateTags(mytoken, mtServer, tags) if err != nil { return err @@ -295,8 +325,6 @@ func updateCalendar(_ context.Context, cmd *cli.Command) error { 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) @@ -305,6 +333,68 @@ func updateCalendar(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("at least one of --description, --add-tags, or --remove-tags must be provided") } + if ssh := calendarsOptions.SSH(); ssh != "" { + listRes, err := doSSHParseJSON[api.CalendarListResponse](ssh, api.SSHRequestCalendars, nil) + 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) + } + + newDescription := calendarsOptions.Description + if newDescription == "" { + newDescription = currentCalendar.Description + } + + newTags := make(map[api.Tag]bool) + for _, t := range currentCalendar.Tags { + newTags[t.Tag] = true + } + + apiAddTags, err := getOrCreateTagsViaSSH(ssh, addTags) + if err != nil { + return err + } + for _, t := range apiAddTags { + newTags[t] = true + } + + for _, tagName := range removeTags { + delete(newTags, api.Tag(tagName)) + } + + finalTags := make([]api.Tag, 0, len(newTags)) + for t := range newTags { + finalTags = append(finalTags, t) + } + + finalTagStrings := make([]string, len(finalTags)) + for i, t := range finalTags { + finalTagStrings[i] = string(t) + } + + req := SSHCalendarUpdateRequest{ + CalendarID: calendarID, + CreateCalendarRequest: api.CreateCalendarRequest{ + Description: newDescription, + Tags: stringSliceToTags(finalTagStrings), + }, + } + return doSSH(ssh, api.SSHRequestCalendarUpdate, &req) + } + + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + // Get current calendar state listRes, err := mtServer.Calendars.APIList(mytoken) if err != nil { @@ -376,8 +466,6 @@ func deleteCalendar(_ context.Context, cmd *cli.Command) error { 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) @@ -387,6 +475,14 @@ func deleteCalendar(_ context.Context, cmd *cli.Command) error { } } + if ssh := calendarsOptions.SSH(); ssh != "" { + req := SSHCalendarIDRequest{CalendarID: calendarID} + return doSSH(ssh, api.SSHRequestCalendarDelete, &req) + } + + mytoken := calendarsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + res, err := mtServer.Calendars.APIDelete(mytoken, calendarID) if err != nil { return err @@ -404,6 +500,18 @@ func subscribeToCalendar(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("calendar-id is required") } calendarID := cmd.Args().Get(0) + + if ssh := calendarsOptions.SSH(); ssh != "" { + req := SSHCalendarSubscriptionRequest{ + CalendarID: calendarID, + AddMytokenToCalendarRequest: api.AddMytokenToCalendarRequest{ + MomID: calendarsOptions.MomID, + Comment: calendarsOptions.Comment, + }, + } + return doSSH(ssh, api.SSHRequestCalendarAddMytoken, &req) + } + mytoken := calendarsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -429,6 +537,17 @@ func unsubscribeFromCalendar(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("calendar-id is required") } calendarID := cmd.Args().Get(0) + + if ssh := calendarsOptions.SSH(); ssh != "" { + req := SSHCalendarSubscriptionRequest{ + CalendarID: calendarID, + AddMytokenToCalendarRequest: api.AddMytokenToCalendarRequest{ + MomID: calendarsOptions.MomID, + }, + } + return doSSH(ssh, api.SSHRequestCalendarRemoveMytoken, &req) + } + mytoken := calendarsOptions.MustGetToken() mtServer := config.Get().Mytoken() diff --git a/internal/commands/email.go b/internal/commands/email.go index 2be9ad0..dd79dbe 100644 --- a/internal/commands/email.go +++ b/internal/commands/email.go @@ -4,6 +4,7 @@ import ( "context" "fmt" + "github.com/oidc-mytoken/api/v0" "github.com/urfave/cli/v3" "github.com/oidc-mytoken/client/internal/config" @@ -56,6 +57,17 @@ func initEmail(parent *cli.Command) { } func getEmail(_ context.Context, _ *cli.Command) error { + if ssh := emailOptions.SSH(); ssh != "" { + res, err := doSSHParseJSON[api.MailSettingsInfoResponse](ssh, api.SSHRequestEmailGet, nil) + if err != nil { + return err + } + 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 + } mytoken := settingsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -75,8 +87,6 @@ func getEmail(_ context.Context, _ *cli.Command) error { } 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") } @@ -90,6 +100,15 @@ func updateEmail(_ context.Context, _ *cli.Command) error { preferHTML = &val } + if ssh := emailOptions.SSH(); ssh != "" { + req := api.UpdateMailSettingsRequest{ + EmailAddress: emailOptions.EmailAddress, + PreferHTMLMail: preferHTML, + } + return doSSH(ssh, api.SSHRequestEmailSet, &req) + } + + mytoken := settingsOptions.MustGetToken() mtServer := config.Get().Mytoken() res, err := mtServer.UserSettings.Email.APIUpdate(mytoken, emailOptions.EmailAddress, preferHTML) if err != nil { diff --git a/internal/commands/general.go b/internal/commands/general.go index 95cb122..063b2ac 100644 --- a/internal/commands/general.go +++ b/internal/commands/general.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/Songmu/prompter" + "github.com/oidc-mytoken/api/v0" mytokenlib "github.com/oidc-mytoken/lib" "github.com/oidc-mytoken/utils/utils/issuerutils" "github.com/oidc-mytoken/utils/utils/jwtutils" @@ -235,3 +236,42 @@ func findCommand(commands []*cli.Command, name string) *cli.Command { } return nil } + +func stringSliceToTags(strings []string) []api.Tag { + result := make([]api.Tag, 0, len(strings)) + for _, s := range strings { + if s != "" { + result = append(result, api.Tag(s)) + } + } + return result +} + +func getOrCreateTagsViaSSH(ssh string, tagNames []string) ([]api.Tag, error) { + res, err := doSSHParseJSON[struct { + Tags []api.TagInfo `json:"tags"` + }](ssh, api.SSHRequestTagsList, nil) + if err != nil { + return nil, err + } + + existingTagMap := make(map[string]bool) + for _, tag := range res.Tags { + existingTagMap[string(tag.Tag)] = true + } + + for _, tagName := range tagNames { + if tagName == "" { + continue + } + if !existingTagMap[tagName] { + createReq := SSHTagCreateRequest{Tag: api.Tag(tagName)} + if err := doSSH(ssh, api.SSHRequestTagCreate, &createReq); err != nil { + return nil, fmt.Errorf("failed to create tag '%s': %w", tagName, err) + } + existingTagMap[tagName] = true + } + } + + return stringSliceToTags(tagNames), nil +} diff --git a/internal/commands/info.go b/internal/commands/info.go index e2d20d1..c2f1b4c 100644 --- a/internal/commands/info.go +++ b/internal/commands/info.go @@ -147,24 +147,21 @@ func introspect(_ context.Context, _ *cli.Command) error { return prettyPrintJSON(res) } -func history(_ context.Context, _ *cli.Command) (err error) { +func history(_ context.Context, _ *cli.Command) error { var res api.TokeninfoHistoryResponse if ssh := infoOptions.SSH(); ssh != "" { - var resStr string - resStr, err = doSSHReturnOutput(ssh, api.SSHRequestTokenInfoHistory, nil) + pRes, err := doSSHParseJSON[api.TokeninfoHistoryResponse](ssh, api.SSHRequestTokenInfoHistory, nil) if err != nil { - return - } - if err = json.Unmarshal([]byte(resStr), &res); err != nil { - err = fmt.Errorf("%s", resStr) - return + return err } - } else { // no ssh + res = *pRes + } else { mToken := infoOptions.MustGetToken() mytoken := config.Get().Mytoken() + var err error res, err = mytoken.Tokeninfo.APIHistory(mToken) if err != nil { - return + return err } if res.TokenUpdate != nil { updateMytoken(res.TokenUpdate.Mytoken) @@ -200,21 +197,18 @@ func (e tableEventEntry) TableGetRow() []string { } } -func subTree(_ context.Context, _ *cli.Command) (err error) { +func subTree(_ context.Context, _ *cli.Command) error { var res api.TokeninfoSubtokensResponse if ssh := infoOptions.SSH(); ssh != "" { - var resStr string - resStr, err = doSSHReturnOutput(ssh, api.SSHRequestTokenInfoSubtokens, nil) + pRes, err := doSSHParseJSON[api.TokeninfoSubtokensResponse](ssh, api.SSHRequestTokenInfoSubtokens, nil) if err != nil { - return - } - if err = json.Unmarshal([]byte(resStr), &res); err != nil { - err = fmt.Errorf("%s", resStr) - return + return err } + res = *pRes } else { mToken := infoOptions.MustGetToken() mytoken := config.Get().Mytoken() + var err error res, err = mytoken.Tokeninfo.APISubtokens(mToken) if err != nil { return err @@ -226,21 +220,18 @@ func subTree(_ context.Context, _ *cli.Command) (err error) { return prettyPrintJSON(res.Tokens) } -func listMytokens(_ context.Context, cmd *cli.Command) (err error) { +func listMytokens(_ context.Context, cmd *cli.Command) error { var res api.TokeninfoListResponse if ssh := infoOptions.SSH(); ssh != "" { - var resStr string - resStr, err = doSSHReturnOutput(ssh, api.SSHRequestTokenInfoListMytokens, nil) + pRes, err := doSSHParseJSON[api.TokeninfoListResponse](ssh, api.SSHRequestTokenInfoListMytokens, nil) if err != nil { - return - } - if err = json.Unmarshal([]byte(resStr), &res); err != nil { - err = fmt.Errorf("%s", resStr) - return + return err } + res = *pRes } else { mToken := infoOptions.MustGetToken() mytoken := config.Get().Mytoken() + var err error res, err = mytoken.Tokeninfo.APIListMytokens(mToken) if err != nil { return err @@ -371,6 +362,19 @@ func (e tableMytokenEntry) TableGetRow() []string { } func infoNotifications(_ context.Context, _ *cli.Command) (err error) { + if ssh := infoNotificationsOptions.SSH(); ssh != "" { + req := api.TokenInfoRequest{} + if len(infoNotificationsOptions.MOMIDs) > 0 { + req.MOMIDs = infoNotificationsOptions.MOMIDs + } + res, err := doSSHParseJSON[api.TokeninfoNotificationsResponse](ssh, api.SSHRequestTokenInfoNotifications, &req) + if err != nil { + return err + } + renderNotificationsCalendars(res.Notifications, res.Calendars) + return nil + } + mToken := infoNotificationsOptions.MustGetToken() mytoken := config.Get().Mytoken() @@ -387,29 +391,32 @@ func infoNotifications(_ context.Context, _ *cli.Command) (err error) { updateMytoken(res.TokenUpdate.Mytoken) } - if len(res.Notifications) > 0 { + renderNotificationsCalendars(res.Notifications, res.Calendars) + return nil +} + +func renderNotificationsCalendars(notifications []api.NotificationInfo, calendars []api.CalendarInfo) { + if len(notifications) > 0 { fmt.Println("Notifications:") - outputData := make([]tablewriter.TableWriter, len(res.Notifications)) - for i, n := range res.Notifications { + outputData := make([]tablewriter.TableWriter, len(notifications)) + for i, n := range notifications { outputData[i] = tableNotificationInfo(n) } tablewriter.PrintTableData(outputData) fmt.Println() } - if len(res.Calendars) > 0 { + if len(calendars) > 0 { fmt.Println("Calendars:") - outputData := make([]tablewriter.TableWriter, len(res.Calendars)) - for i, c := range res.Calendars { + outputData := make([]tablewriter.TableWriter, len(calendars)) + for i, c := range calendars { outputData[i] = tableCalendarInfo(c) } tablewriter.PrintTableData(outputData) fmt.Println() } - if len(res.Notifications) == 0 && len(res.Calendars) == 0 { + if len(notifications) == 0 && len(calendars) == 0 { fmt.Println("No notifications or calendars found.") } - - return nil } diff --git a/internal/commands/mttags.go b/internal/commands/mttags.go index 96a849e..e6b8f3c 100644 --- a/internal/commands/mttags.go +++ b/internal/commands/mttags.go @@ -98,6 +98,20 @@ func addMTTag(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("tag name required") } tagName := cmd.Args().Get(0) + + if ssh := mtTagsOptions.SSH(); ssh != "" { + req := api.AddTagToMytokenRequest{ + Tag: api.Tag(tagName), + MOMID: mtTagsOptions.MOMID, + IncludeChildren: mtTagsOptions.IncludeChildren, + } + if err := doSSH(ssh, api.SSHRequestAddTag, &req); err != nil { + return err + } + fmt.Printf("Tag '%s' added successfully\n", tagName) + return nil + } + mytoken := mtTagsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -125,6 +139,20 @@ func removeMTTag(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("tag name required") } tagName := cmd.Args().Get(0) + + if ssh := mtTagsOptions.SSH(); ssh != "" { + req := api.RemoveTagFromMytokenRequest{ + Tag: api.Tag(tagName), + MOMID: mtTagsOptions.MOMID, + IncludeChildren: mtTagsOptions.IncludeChildren, + } + if err := doSSH(ssh, api.SSHRequestRemoveTag, &req); err != nil { + return err + } + fmt.Printf("Tag '%s' removed successfully\n", tagName) + return nil + } + mytoken := mtTagsOptions.MustGetToken() mtServer := config.Get().Mytoken() diff --git a/internal/commands/notifications.go b/internal/commands/notifications.go index b9115a2..1d73d47 100644 --- a/internal/commands/notifications.go +++ b/internal/commands/notifications.go @@ -193,6 +193,18 @@ func parseStringSlice(input []string) []string { } func listNotifications(_ context.Context, _ *cli.Command) error { + if ssh := notificationsOptions.SSH(); ssh != "" { + res, err := doSSHParseJSON[api.NotificationsListResponse](ssh, api.SSHRequestNotifications, nil) + if err != nil { + return err + } + outputData := make([]tablewriter.TableWriter, len(res.Notifications)) + for i, n := range res.Notifications { + outputData[i] = tableNotificationInfo(n) + } + tablewriter.PrintTableData(outputData) + return nil + } mytoken := notificationsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -282,6 +294,26 @@ func createNotification(_ context.Context, _ *cli.Command) error { } tags := parseStringSlice(notificationsOptions.Tags) + + if ssh := notificationsOptions.SSH(); ssh != "" { + req := api.SubscribeNotificationRequest{ + NotificationType: notificationsOptions.NotificationType, + NotificationClasses: parseNotificationClasses(classes), + UserWide: notificationsOptions.UserWide, + Tags: stringSliceToTags(tags), + Comment: notificationsOptions.Comment, + MOMID: notificationsOptions.MOMID, + } + createRes, err := doSSHParseJSON[struct { + ManagementCode string `json:"management_code"` + }](ssh, api.SSHRequestNotificationCreate, &req) + if err != nil { + return err + } + displayManagementCode(createRes.ManagementCode, "notification") + return nil + } + apiTags, err := getOrCreateTags(mytoken, mtServer, tags) if err != nil { return err @@ -481,8 +513,6 @@ func updateNotification(_ context.Context, cmd *cli.Command) error { 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) @@ -493,6 +523,74 @@ func updateNotification(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("at least one of --add-classes, --remove-classes, --add-tags, or --remove-tags must be provided") } + if ssh := notificationsOptions.SSH(); ssh != "" { + listRes, err := doSSHParseJSON[api.NotificationsListResponse](ssh, api.SSHRequestNotifications, nil) + 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) + } + + newClasses := make(map[string]*api.NotificationClass) + for _, c := range currentNotification.Classes { + newClasses[c.Name] = c + } + for _, className := range addClasses { + newClasses[className] = api.NewNotificationClass(className) + } + for _, className := range removeClasses { + delete(newClasses, className) + } + + finalClasses := make(api.NotificationClasses, 0, len(newClasses)) + for _, nc := range newClasses { + finalClasses = append(finalClasses, nc) + } + + newTags := make(map[api.Tag]bool) + for _, t := range currentNotification.Tags { + newTags[t.Tag] = true + } + + apiAddTags, err := getOrCreateTagsViaSSH(ssh, addTags) + if err != nil { + return err + } + for _, t := range apiAddTags { + newTags[t] = true + } + + for _, tagName := range removeTags { + delete(newTags, api.Tag(tagName)) + } + + finalTags := make([]api.Tag, 0, len(newTags)) + for t := range newTags { + finalTags = append(finalTags, t) + } + + req := SSHNotificationUpdateRequest{ + ManagementCode: managementCode, + NotificationUpdateRequest: api.NotificationUpdateRequest{ + Classes: &finalClasses, + Tags: &finalTags, + }, + } + return doSSH(ssh, api.SSHRequestNotificationUpdate, &req) + } + + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + // First, get current notification state listRes, err := mtServer.Notifications.APIList(mytoken) if err != nil { @@ -580,8 +678,6 @@ func deleteNotification(_ context.Context, cmd *cli.Command) error { 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) @@ -591,6 +687,14 @@ func deleteNotification(_ context.Context, cmd *cli.Command) error { } } + if ssh := notificationsOptions.SSH(); ssh != "" { + req := SSHNotificationManagementCodeRequest{ManagementCode: managementCode} + return doSSH(ssh, api.SSHRequestNotificationDelete, &req) + } + + mytoken := notificationsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + res, err := mtServer.Notifications.APIDelete(mytoken, managementCode) if err != nil { return err @@ -608,6 +712,18 @@ func addTokenToNotification(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("management-code is required") } managementCode := cmd.Args().Get(0) + + if ssh := notificationsOptions.SSH(); ssh != "" { + req := SSHNotificationAddTokenRequest{ + ManagementCode: managementCode, + NotificationAddTokenRequest: api.NotificationAddTokenRequest{ + MOMID: notificationsOptions.MOMID, + IncludeChildren: notificationsOptions.IncludeChildren, + }, + } + return doSSH(ssh, api.SSHRequestNotificationAddToken, &req) + } + mytoken := notificationsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -634,6 +750,20 @@ func removeTokenFromNotification(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("management-code is required") } managementCode := cmd.Args().Get(0) + + if ssh := notificationsOptions.SSH(); ssh != "" { + type request struct { + ManagementCode string `json:"management_code"` + MOMID string `json:"mom_id"` + } + return doSSH( + ssh, api.SSHRequestNotificationRemoveToken, &request{ + ManagementCode: managementCode, + MOMID: notificationsOptions.MOMID, + }, + ) + } + mytoken := notificationsOptions.MustGetToken() mtServer := config.Get().Mytoken() diff --git a/internal/commands/revoke.go b/internal/commands/revoke.go index f48713c..e47ee4b 100644 --- a/internal/commands/revoke.go +++ b/internal/commands/revoke.go @@ -51,6 +51,16 @@ func init() { } func revoke(_ context.Context, _ *cli.Command) error { + if ssh := revokeCommand.SSH(); ssh != "" { + if revokeCommand.MOMID != "" { + req := api.RevocationRequest{ + MOMID: revokeCommand.MOMID, + Recursive: revokeCommand.Recursive, + } + return doSSH(ssh, api.SSHRequestRevoke, &req) + } + return doSSH(ssh, api.SSHRequestRevoke, nil) + } mToken := revokeCommand.MustGetToken() mytoken := config.Get().Mytoken() var err error diff --git a/internal/commands/ssh.go b/internal/commands/ssh.go index f45d631..b0d35a4 100644 --- a/internal/commands/ssh.go +++ b/internal/commands/ssh.go @@ -28,7 +28,11 @@ func fdoSSH(out io.Writer, host, command string, req interface{}) error { } func doSSH(host, command string, req interface{}) error { - return fdoSSH(os.Stdout, host, command, req) + err := fdoSSH(os.Stdout, host, command, req) + if err != nil { + return fmt.Errorf("SSH command failed (%s %s): %w", host, command, err) + } + return err } func doSSHReturnOutput(host, command string, req interface{}) (string, error) { @@ -36,3 +40,15 @@ func doSSHReturnOutput(host, command string, req interface{}) (string, error) { err := fdoSSH(&s, host, command, req) return s.String(), err } + +func doSSHParseJSON[T any](host, command string, req interface{}) (*T, error) { + resStr, err := doSSHReturnOutput(host, command, req) + if err != nil { + return nil, err + } + var res T + if err = json.Unmarshal([]byte(resStr), &res); err != nil { + return nil, fmt.Errorf("failed to parse SSH response: %w (output: %s)", err, resStr) + } + return &res, nil +} diff --git a/internal/commands/ssh_requests.go b/internal/commands/ssh_requests.go new file mode 100644 index 0000000..9f4686f --- /dev/null +++ b/internal/commands/ssh_requests.go @@ -0,0 +1,55 @@ +package commands + +import "github.com/oidc-mytoken/api/v0" + +// SSHCalendarUpdateRequest is the request body for updating a calendar via SSH +type SSHCalendarUpdateRequest struct { + CalendarID string `json:"calendar_id"` + api.CreateCalendarRequest +} + +// SSHCalendarIDRequest is used for calendar operations that only need the calendar ID +type SSHCalendarIDRequest struct { + CalendarID string `json:"calendar_id"` +} + +// SSHCalendarSubscriptionRequest is used for subscribing/unsubscribing to calendars +type SSHCalendarSubscriptionRequest struct { + CalendarID string `json:"calendar_id"` + api.AddMytokenToCalendarRequest +} + +// SSHNotificationUpdateRequest is the request body for updating a notification via SSH +type SSHNotificationUpdateRequest struct { + ManagementCode string `json:"management_code"` + api.NotificationUpdateRequest +} + +// SSHNotificationManagementCodeRequest is used for notification operations that only need the management code +type SSHNotificationManagementCodeRequest struct { + ManagementCode string `json:"management_code"` +} + +// SSHNotificationAddTokenRequest is used for adding a token to a notification via SSH +type SSHNotificationAddTokenRequest struct { + ManagementCode string `json:"management_code"` + api.NotificationAddTokenRequest +} + +// SSHTagCreateRequest is the request body for creating a tag via SSH +type SSHTagCreateRequest struct { + Tag api.Tag `json:"tag"` + Color *string `json:"color,omitempty"` +} + +// SSHTagUpdateRequest is the request body for updating a tag via SSH +type SSHTagUpdateRequest struct { + Tag api.Tag `json:"tag"` + Color *string `json:"color,omitempty"` + Name *string `json:"name,omitempty"` +} + +// SSHTagDeleteRequest is used for deleting a tag +type SSHTagDeleteRequest struct { + Tag api.Tag `json:"tag"` +} diff --git a/internal/commands/tags.go b/internal/commands/tags.go index 9d4442d..979c71b 100644 --- a/internal/commands/tags.go +++ b/internal/commands/tags.go @@ -80,6 +80,20 @@ func initTags(parent *cli.Command) { } func listTags(_ context.Context, _ *cli.Command) error { + if ssh := tagsOptions.SSH(); ssh != "" { + res, err := doSSHParseJSON[struct { + Tags []api.TagInfo `json:"tags"` + }](ssh, api.SSHRequestTagsList, nil) + if err != nil { + return err + } + outputData := make([]tablewriter.TableWriter, len(res.Tags)) + for i, t := range res.Tags { + outputData[i] = tableTagInfo(t) + } + tablewriter.PrintTableData(outputData) + return nil + } mytoken := settingsOptions.MustGetToken() mtServer := config.Get().Mytoken() @@ -123,8 +137,6 @@ func createTag(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("tag name required") } tagName := cmd.Args().Get(0) - mytoken := settingsOptions.MustGetToken() - mtServer := config.Get().Mytoken() var tagColor string if tagsOptions.Color != "" { @@ -135,6 +147,21 @@ func createTag(_ context.Context, cmd *cli.Command) error { tagColor = normalizedColor } + if ssh := tagsOptions.SSH(); ssh != "" { + req := SSHTagCreateRequest{Tag: api.Tag(tagName)} + if tagColor != "" { + req.Color = &tagColor + } + if err := doSSH(ssh, api.SSHRequestTagCreate, &req); err != nil { + return err + } + fmt.Printf("Tag '%s' created successfully\n", tagName) + return nil + } + + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + err := mtServer.UserSettings.Tags.APICreate(mytoken, tagName, tagColor) if err != nil { return err @@ -149,8 +176,6 @@ func updateTag(_ context.Context, cmd *cli.Command) error { 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") @@ -165,6 +190,24 @@ func updateTag(_ context.Context, cmd *cli.Command) error { newColor = normalizedColor } + if ssh := tagsOptions.SSH(); ssh != "" { + req := SSHTagUpdateRequest{Tag: api.Tag(tagName)} + if newColor != "" { + req.Color = &newColor + } + if tagsOptions.NewTagName != "" { + req.Name = &tagsOptions.NewTagName + } + if err := doSSH(ssh, api.SSHRequestTagUpdate, &req); err != nil { + return err + } + fmt.Printf("Tag '%s' updated successfully\n", tagName) + return nil + } + + mytoken := settingsOptions.MustGetToken() + mtServer := config.Get().Mytoken() + res, err := mtServer.UserSettings.Tags.APIUpdate(mytoken, tagName, tagsOptions.NewTagName, newColor) if err != nil { return err @@ -182,6 +225,16 @@ func deleteTag(_ context.Context, cmd *cli.Command) error { return fmt.Errorf("tag name required") } tagName := cmd.Args().Get(0) + + if ssh := tagsOptions.SSH(); ssh != "" { + req := SSHTagDeleteRequest{Tag: api.Tag(tagName)} + if err := doSSH(ssh, api.SSHRequestTagDelete, &req); err != nil { + return err + } + fmt.Printf("Tag '%s' deleted successfully\n", tagName) + return nil + } + mytoken := settingsOptions.MustGetToken() mtServer := config.Get().Mytoken()