Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,14 @@ These commands act on an organization (`-o`/`--organization`). Most require the

## Personal access

### SSH keys

Run `tw ssh-keys -h` to view the supported operations: `list`, `add`, `view`, and `delete`. SSH keys belong to your user account. Use `-k -` to read the public key from stdin.

```bash
tw ssh-keys add -n laptop -k ~/.ssh/id_ed25519.pub
```

### Access tokens

Run `tw access-tokens -h` to view the supported operations: `list`, `add`, and `delete`. The token value is only shown when the token is created.
Expand Down
76 changes: 76 additions & 0 deletions conf/reflect-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,12 @@
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.SshKeysCmd",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.StudiosCmd",
"allDeclaredFields":true,
Expand Down Expand Up @@ -2003,6 +2009,52 @@
"queryAllDeclaredMethods":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.AbstractSshKeysCmd",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.AddCmd",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"fields":[{"name":"name"}, {"name":"publicKey"}],
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.DeleteCmd",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"fields":[{"name":"ref"}],
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.ListCmd",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.SshKeyRefOptions",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"fields":[{"name":"sshKey"}],
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.SshKeyRefOptions$SshKeyRef",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"fields":[{"name":"id"}, {"name":"name"}],
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.sshkeys.ViewCmd",
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true,
"fields":[{"name":"ref"}],
"methods":[{"name":"<init>","parameterTypes":[] }]
},
{
"name":"io.seqera.tower.cli.commands.studios.AbstractStudiosCmd",
"allDeclaredFields":true,
Expand Down Expand Up @@ -2977,6 +3029,30 @@
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.sshkeys.SshKeyAdded",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.sshkeys.SshKeyDeleted",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.sshkeys.SshKeyView",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.sshkeys.SshKeysList",
"allDeclaredFields":true,
"queryAllDeclaredMethods":true,
"queryAllDeclaredConstructors":true
},
{
"name":"io.seqera.tower.cli.responses.studios.StudioCheckpointUpdated",
"allDeclaredFields":true,
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/io/seqera/tower/cli/Tower.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.seqera.tower.cli.commands.PipelineSchemasCmd;
import io.seqera.tower.cli.commands.RunsCmd;
import io.seqera.tower.cli.commands.SecretsCmd;
import io.seqera.tower.cli.commands.SshKeysCmd;
import io.seqera.tower.cli.commands.TeamsCmd;
import io.seqera.tower.cli.commands.WorkspacesCmd;
import io.seqera.tower.cli.commands.enums.OutputType;
Expand Down Expand Up @@ -77,6 +78,7 @@
PipelinesCmd.class,
PipelineSchemasCmd.class,
RunsCmd.class,
SshKeysCmd.class,
TeamsCmd.class,
WorkspacesCmd.class,
SecretsCmd.class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.seqera.tower.api.PipelinesApi;
import io.seqera.tower.api.PlatformsApi;
import io.seqera.tower.api.ServiceInfoApi;
import io.seqera.tower.api.SshKeysApi;
import io.seqera.tower.api.StudiosApi;
import io.seqera.tower.api.TeamsApi;
import io.seqera.tower.api.TokensApi;
Expand Down Expand Up @@ -136,6 +137,7 @@ public abstract class AbstractApiCmd extends AbstractCmd {
private PipelineVersionsApi pipelineVersionsApi;
private PlatformsApi platformsApi;
private ServiceInfoApi serviceInfoApi;
private SshKeysApi sshKeysApi;
private StudiosApi studiosApi;
private TeamsApi teamsApi;
private TokensApi tokensApi;
Expand Down Expand Up @@ -269,6 +271,10 @@ protected ServiceInfoApi serviceInfoApi() throws ApiException {
return serviceInfoApi == null ? new ServiceInfoApi(apiClient()) : serviceInfoApi;
}

protected SshKeysApi sshKeysApi() throws ApiException {
return sshKeysApi == null ? new SshKeysApi(apiClient()) : sshKeysApi;
}

protected StudiosApi studiosApi() throws ApiException {
return studiosApi == null ? new StudiosApi(apiClient()) : studiosApi;
}
Expand Down
37 changes: 37 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/SshKeysCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2021-2026, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.seqera.tower.cli.commands;

import io.seqera.tower.cli.commands.sshkeys.AddCmd;
import io.seqera.tower.cli.commands.sshkeys.DeleteCmd;
import io.seqera.tower.cli.commands.sshkeys.ListCmd;
import io.seqera.tower.cli.commands.sshkeys.ViewCmd;
import picocli.CommandLine.Command;

@Command(
name = "ssh-keys",
description = "Manage your SSH public keys",
subcommands = {
ListCmd.class,
AddCmd.class,
ViewCmd.class,
DeleteCmd.class,
}
)
public class SshKeysCmd extends AbstractRootCmd {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2021-2026, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.seqera.tower.cli.commands.sshkeys;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.commands.AbstractApiCmd;
import io.seqera.tower.cli.exceptions.SshKeyNotFoundException;
import io.seqera.tower.model.UserSshPublicKeyDto;
import picocli.CommandLine.Command;

@Command
public abstract class AbstractSshKeysCmd extends AbstractApiCmd {

protected UserSshPublicKeyDto fetchSshKey(SshKeyRefOptions ref) throws ApiException {
if (ref.sshKey.id != null) {
return sshKeysApi().describeSshKey(ref.sshKey.id).getSshKey();
}
return sshKeysApi().listSshKeys().getSshKeys().stream()
.filter(key -> ref.sshKey.name.equals(key.getName()))
.findFirst()
.orElseThrow(() -> new SshKeyNotFoundException(ref.sshKey.name));
}
}
52 changes: 52 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/sshkeys/AddCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright 2021-2026, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.seqera.tower.cli.commands.sshkeys;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.sshkeys.SshKeyAdded;
import io.seqera.tower.cli.utils.FilesHelper;
import io.seqera.tower.model.CreateSshKeyRequest;
import io.seqera.tower.model.UserSshPublicKeyDto;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;

import java.io.IOException;
import java.nio.file.Path;

@Command(
name = "add",
description = "Add an SSH public key"
)
public class AddCmd extends AbstractSshKeysCmd {

@Option(names = {"-n", "--name"}, description = "SSH key name. Must be unique. Names consist of alphanumeric, hyphen, and underscore characters.", required = true)
public String name;

@Option(names = {"-k", "--key"}, description = "Path to the SSH public key file (e.g. ~/.ssh/id_ed25519.pub). Use '-' to read it from stdin. Create a key pair with 'ssh-keygen'.", required = true)
public Path publicKey;

@Override
protected Response exec() throws ApiException, IOException {
CreateSshKeyRequest request = new CreateSshKeyRequest()
.name(name)
.publicKey(FilesHelper.readStringOrStdin(publicKey).strip());
UserSshPublicKeyDto sshKey = sshKeysApi().createSshKey(request).getSshKey();
return new SshKeyAdded(sshKey.getId(), sshKey.getName());
}
}
42 changes: 42 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/sshkeys/DeleteCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2021-2026, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.seqera.tower.cli.commands.sshkeys;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.sshkeys.SshKeyDeleted;
import io.seqera.tower.model.UserSshPublicKeyDto;
import picocli.CommandLine.Command;
import picocli.CommandLine.Mixin;

@Command(
name = "delete",
description = "Delete an SSH public key"
)
public class DeleteCmd extends AbstractSshKeysCmd {

@Mixin
SshKeyRefOptions ref;

@Override
protected Response exec() throws ApiException {
UserSshPublicKeyDto sshKey = fetchSshKey(ref);
sshKeysApi().deleteSshKey(sshKey.getId());
return new SshKeyDeleted(sshKey.getId(), sshKey.getName());
}
}
35 changes: 35 additions & 0 deletions src/main/java/io/seqera/tower/cli/commands/sshkeys/ListCmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2021-2026, Seqera.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package io.seqera.tower.cli.commands.sshkeys;

import io.seqera.tower.ApiException;
import io.seqera.tower.cli.responses.Response;
import io.seqera.tower.cli.responses.sshkeys.SshKeysList;
import picocli.CommandLine.Command;

@Command(
name = "list",
description = "List your SSH public keys"
)
public class ListCmd extends AbstractSshKeysCmd {

@Override
protected Response exec() throws ApiException {
return new SshKeysList(sshKeysApi().listSshKeys().getSshKeys());
}
}
Loading
Loading