mirror of
https://github.com/Threnklyn/jira.git
synced 2026-06-06 04:58:30 +02:00
refactor password source, allow for "pass" to be used, update tests to use password-source: pass
This commit is contained in:
+2
-6
@@ -1,6 +1,6 @@
|
|||||||
sudo: true
|
sudo: true
|
||||||
before_install:
|
before_install:
|
||||||
- sudo apt-get update && sudo apt-get install -y libgnome-keyring-dev
|
- sudo apt-get update && sudo apt-get install -y pass gnupg
|
||||||
|
|
||||||
language: go
|
language: go
|
||||||
|
|
||||||
@@ -14,8 +14,4 @@ script:
|
|||||||
- make vet
|
- make vet
|
||||||
- make lint
|
- make lint
|
||||||
- make
|
- make
|
||||||
- JIRACLOUD=1 ./t/100basic.t -w -a 2>&1
|
- export GNUPGHOME=$(pwd)/t/.gnupg && export PASSWORD_STORE_DIR=$(pwd)/t/.password-store && export JIRACLOUD=1 && ./t/100basic.t -w -a 2>&1
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- t/.maven-cache
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kballard/go-shellquote"
|
"github.com/kballard/go-shellquote"
|
||||||
"github.com/tmc/keyring"
|
|
||||||
"gopkg.in/coryb/yaml.v2"
|
"gopkg.in/coryb/yaml.v2"
|
||||||
"gopkg.in/op/go-logging.v1"
|
"gopkg.in/op/go-logging.v1"
|
||||||
)
|
)
|
||||||
@@ -215,11 +214,11 @@ func (c *Cli) makeRequest(req *http.Request) (resp *http.Response, err error) {
|
|||||||
req.Header.Set("Accept", "application/json")
|
req.Header.Set("Accept", "application/json")
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
if val, ok := c.opts["password-keyring"].(bool); ok && val && !strings.HasSuffix(req.URL.Path, "/rest/auth/1/session") {
|
if source, ok := c.opts["password-source"]; ok && !strings.HasSuffix(req.URL.Path, "/rest/auth/1/session") {
|
||||||
user, _ := c.opts["user"].(string)
|
user, _ := c.opts["user"].(string)
|
||||||
password, _ := keyring.Get("go-jira", user)
|
password := c.GetPass(user)
|
||||||
if password == "" {
|
if password == "" {
|
||||||
log.Warning("No password for user %s in keyring, please run the 'login' command first", user)
|
log.Warning("No password for user %s in %s, please run the 'login' command first", user, source)
|
||||||
} else {
|
} else {
|
||||||
req.SetBasicAuth(user, password)
|
req.SetBasicAuth(user, password)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-16
@@ -10,8 +10,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/howeyc/gopass"
|
|
||||||
"github.com/tmc/keyring"
|
|
||||||
"gopkg.in/Netflix-Skunkworks/go-jira.v0/data"
|
"gopkg.in/Netflix-Skunkworks/go-jira.v0/data"
|
||||||
// "github.com/kr/pretty"
|
// "github.com/kr/pretty"
|
||||||
)
|
)
|
||||||
@@ -23,13 +21,7 @@ func (c *Cli) CmdLogin() error {
|
|||||||
req, _ := http.NewRequest("GET", uri, nil)
|
req, _ := http.NewRequest("GET", uri, nil)
|
||||||
user, _ := c.opts["user"].(string)
|
user, _ := c.opts["user"].(string)
|
||||||
|
|
||||||
fmt.Printf("Jira Password [%s]: ", user)
|
passwd := c.GetPass(user)
|
||||||
pw, err := gopass.GetPasswdMasked()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
passwd := string(pw)
|
|
||||||
|
|
||||||
req.SetBasicAuth(user, passwd)
|
req.SetBasicAuth(user, passwd)
|
||||||
|
|
||||||
resp, err := c.makeRequest(req)
|
resp, err := c.makeRequest(req)
|
||||||
@@ -55,13 +47,8 @@ func (c *Cli) CmdLogin() error {
|
|||||||
log.Warning("Authentication Failed: %s", reason)
|
log.Warning("Authentication Failed: %s", reason)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if val, ok := c.opts["password-keyring"].(bool); ok && val {
|
if _, ok := c.opts["password-source"]; ok {
|
||||||
// save password in keychain so that it can be used for subsequent http requests
|
return c.SetPass(user, passwd)
|
||||||
err := keyring.Set("go-jira", user, passwd)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to set password in keyring: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+71
@@ -0,0 +1,71 @@
|
|||||||
|
package jira
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/howeyc/gopass"
|
||||||
|
"github.com/tmc/keyring"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Cli) GetPass(user string) string {
|
||||||
|
passwd := ""
|
||||||
|
if source, ok := c.opts["password-source"].(string); ok {
|
||||||
|
if source == "keyring" {
|
||||||
|
passwd, _ = keyring.Get("go-jira", user)
|
||||||
|
} else if source == "pass" {
|
||||||
|
if bin, err := exec.LookPath("pass"); err == nil {
|
||||||
|
buf := bytes.NewBufferString("")
|
||||||
|
cmd := exec.Command(bin, fmt.Sprintf("GoJira/%s", user))
|
||||||
|
cmd.Stdout = buf
|
||||||
|
cmd.Stderr = buf
|
||||||
|
if err := cmd.Run(); err == nil {
|
||||||
|
passwd = strings.TrimSpace(buf.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Warningf("Unknown password-source: %s", source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if passwd != "" {
|
||||||
|
return passwd
|
||||||
|
}
|
||||||
|
fmt.Printf("Jira Password [%s]: ", user)
|
||||||
|
pw, err := gopass.GetPasswdMasked()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
passwd = string(pw)
|
||||||
|
return passwd
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Cli) SetPass(user, passwd string) error {
|
||||||
|
if source, ok := c.opts["password-source"].(string); ok {
|
||||||
|
if source == "keyring" {
|
||||||
|
// save password in keychain so that it can be used for subsequent http requests
|
||||||
|
err := keyring.Set("go-jira", user, passwd)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to set password in keyring: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if source == "pass" {
|
||||||
|
if bin, err := exec.LookPath("pass"); err == nil {
|
||||||
|
in := bytes.NewBufferString(fmt.Sprintf("%s\n%s\n", passwd, passwd))
|
||||||
|
out := bytes.NewBufferString("")
|
||||||
|
cmd := exec.Command(bin, "insert", fmt.Sprintf("GoJira/%s", user))
|
||||||
|
cmd.Stdin = in
|
||||||
|
cmd.Stdout = out
|
||||||
|
cmd.Stderr = out
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return fmt.Errorf("Failed to insert password: %s", out.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("Unknown password-source: %s", source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,236 @@
|
|||||||
|
# Options for GnuPG
|
||||||
|
# Copyright 1998, 1999, 2000, 2001, 2002, 2003,
|
||||||
|
# 2010 Free Software Foundation, Inc.
|
||||||
|
#
|
||||||
|
# This file is free software; as a special exception the author gives
|
||||||
|
# unlimited permission to copy and/or distribute it, with or without
|
||||||
|
# modifications, as long as this notice is preserved.
|
||||||
|
#
|
||||||
|
# This file is distributed in the hope that it will be useful, but
|
||||||
|
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
|
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
#
|
||||||
|
# Unless you specify which option file to use (with the command line
|
||||||
|
# option "--options filename"), GnuPG uses the file ~/.gnupg/gpg.conf
|
||||||
|
# by default.
|
||||||
|
#
|
||||||
|
# An options file can contain any long options which are available in
|
||||||
|
# GnuPG. If the first non white space character of a line is a '#',
|
||||||
|
# this line is ignored. Empty lines are also ignored.
|
||||||
|
#
|
||||||
|
# See the man page for a list of options.
|
||||||
|
|
||||||
|
# Uncomment the following option to get rid of the copyright notice
|
||||||
|
|
||||||
|
#no-greeting
|
||||||
|
|
||||||
|
# If you have more than 1 secret key in your keyring, you may want to
|
||||||
|
# uncomment the following option and set your preferred keyid.
|
||||||
|
|
||||||
|
#default-key 621CC013
|
||||||
|
|
||||||
|
# If you do not pass a recipient to gpg, it will ask for one. Using
|
||||||
|
# this option you can encrypt to a default key. Key validation will
|
||||||
|
# not be done in this case. The second form uses the default key as
|
||||||
|
# default recipient.
|
||||||
|
|
||||||
|
#default-recipient some-user-id
|
||||||
|
#default-recipient-self
|
||||||
|
|
||||||
|
# Use --encrypt-to to add the specified key as a recipient to all
|
||||||
|
# messages. This is useful, for example, when sending mail through a
|
||||||
|
# mail client that does not automatically encrypt mail to your key.
|
||||||
|
# In the example, this option allows you to read your local copy of
|
||||||
|
# encrypted mail that you've sent to others.
|
||||||
|
|
||||||
|
#encrypt-to some-key-id
|
||||||
|
|
||||||
|
# By default GnuPG creates version 4 signatures for data files as
|
||||||
|
# specified by OpenPGP. Some earlier (PGP 6, PGP 7) versions of PGP
|
||||||
|
# require the older version 3 signatures. Setting this option forces
|
||||||
|
# GnuPG to create version 3 signatures.
|
||||||
|
|
||||||
|
#force-v3-sigs
|
||||||
|
|
||||||
|
# Because some mailers change lines starting with "From " to ">From "
|
||||||
|
# it is good to handle such lines in a special way when creating
|
||||||
|
# cleartext signatures; all other PGP versions do it this way too.
|
||||||
|
|
||||||
|
#no-escape-from-lines
|
||||||
|
|
||||||
|
# If you do not use the Latin-1 (ISO-8859-1) charset, you should tell
|
||||||
|
# GnuPG which is the native character set. Please check the man page
|
||||||
|
# for supported character sets. This character set is only used for
|
||||||
|
# metadata and not for the actual message which does not undergo any
|
||||||
|
# translation. Note that future version of GnuPG will change to UTF-8
|
||||||
|
# as default character set. In most cases this option is not required
|
||||||
|
# as GnuPG is able to figure out the correct charset at runtime.
|
||||||
|
|
||||||
|
#charset utf-8
|
||||||
|
|
||||||
|
# Group names may be defined like this:
|
||||||
|
# group mynames = paige 0x12345678 joe patti
|
||||||
|
#
|
||||||
|
# Any time "mynames" is a recipient (-r or --recipient), it will be
|
||||||
|
# expanded to the names "paige", "joe", and "patti", and the key ID
|
||||||
|
# "0x12345678". Note there is only one level of expansion - you
|
||||||
|
# cannot make an group that points to another group. Note also that
|
||||||
|
# if there are spaces in the recipient name, this will appear as two
|
||||||
|
# recipients. In these cases it is better to use the key ID.
|
||||||
|
|
||||||
|
#group mynames = paige 0x12345678 joe patti
|
||||||
|
|
||||||
|
# Lock the file only once for the lifetime of a process. If you do
|
||||||
|
# not define this, the lock will be obtained and released every time
|
||||||
|
# it is needed, which is usually preferable.
|
||||||
|
|
||||||
|
#lock-once
|
||||||
|
|
||||||
|
# GnuPG can send and receive keys to and from a keyserver. These
|
||||||
|
# servers can be HKP, email, or LDAP (if GnuPG is built with LDAP
|
||||||
|
# support).
|
||||||
|
#
|
||||||
|
# Example HKP keyserver:
|
||||||
|
# hkp://keys.gnupg.net
|
||||||
|
# hkp://subkeys.pgp.net
|
||||||
|
#
|
||||||
|
# Example email keyserver:
|
||||||
|
# mailto:pgp-public-keys@keys.pgp.net
|
||||||
|
#
|
||||||
|
# Example LDAP keyservers:
|
||||||
|
# ldap://keyserver.pgp.com
|
||||||
|
#
|
||||||
|
# Regular URL syntax applies, and you can set an alternate port
|
||||||
|
# through the usual method:
|
||||||
|
# hkp://keyserver.example.net:22742
|
||||||
|
#
|
||||||
|
# Most users just set the name and type of their preferred keyserver.
|
||||||
|
# Note that most servers (with the notable exception of
|
||||||
|
# ldap://keyserver.pgp.com) synchronize changes with each other. Note
|
||||||
|
# also that a single server name may actually point to multiple
|
||||||
|
# servers via DNS round-robin. hkp://keys.gnupg.net is an example of
|
||||||
|
# such a "server", which spreads the load over a number of physical
|
||||||
|
# servers. To see the IP address of the server actually used, you may use
|
||||||
|
# the "--keyserver-options debug".
|
||||||
|
|
||||||
|
keyserver hkp://keys.gnupg.net
|
||||||
|
#keyserver mailto:pgp-public-keys@keys.nl.pgp.net
|
||||||
|
#keyserver ldap://keyserver.pgp.com
|
||||||
|
|
||||||
|
# Common options for keyserver functions:
|
||||||
|
#
|
||||||
|
# include-disabled : when searching, include keys marked as "disabled"
|
||||||
|
# on the keyserver (not all keyservers support this).
|
||||||
|
#
|
||||||
|
# no-include-revoked : when searching, do not include keys marked as
|
||||||
|
# "revoked" on the keyserver.
|
||||||
|
#
|
||||||
|
# verbose : show more information as the keys are fetched.
|
||||||
|
# Can be used more than once to increase the amount
|
||||||
|
# of information shown.
|
||||||
|
#
|
||||||
|
# use-temp-files : use temporary files instead of a pipe to talk to the
|
||||||
|
# keyserver. Some platforms (Win32 for one) always
|
||||||
|
# have this on.
|
||||||
|
#
|
||||||
|
# keep-temp-files : do not delete temporary files after using them
|
||||||
|
# (really only useful for debugging)
|
||||||
|
#
|
||||||
|
# http-proxy="proxy" : set the proxy to use for HTTP and HKP keyservers.
|
||||||
|
# This overrides the "http_proxy" environment variable,
|
||||||
|
# if any.
|
||||||
|
#
|
||||||
|
# auto-key-retrieve : automatically fetch keys as needed from the keyserver
|
||||||
|
# when verifying signatures or when importing keys that
|
||||||
|
# have been revoked by a revocation key that is not
|
||||||
|
# present on the keyring.
|
||||||
|
#
|
||||||
|
# no-include-attributes : do not include attribute IDs (aka "photo IDs")
|
||||||
|
# when sending keys to the keyserver.
|
||||||
|
|
||||||
|
#keyserver-options auto-key-retrieve
|
||||||
|
|
||||||
|
# Display photo user IDs in key listings
|
||||||
|
|
||||||
|
# list-options show-photos
|
||||||
|
|
||||||
|
# Display photo user IDs when a signature from a key with a photo is
|
||||||
|
# verified
|
||||||
|
|
||||||
|
# verify-options show-photos
|
||||||
|
|
||||||
|
# Use this program to display photo user IDs
|
||||||
|
#
|
||||||
|
# %i is expanded to a temporary file that contains the photo.
|
||||||
|
# %I is the same as %i, but the file isn't deleted afterwards by GnuPG.
|
||||||
|
# %k is expanded to the key ID of the key.
|
||||||
|
# %K is expanded to the long OpenPGP key ID of the key.
|
||||||
|
# %t is expanded to the extension of the image (e.g. "jpg").
|
||||||
|
# %T is expanded to the MIME type of the image (e.g. "image/jpeg").
|
||||||
|
# %f is expanded to the fingerprint of the key.
|
||||||
|
# %% is %, of course.
|
||||||
|
#
|
||||||
|
# If %i or %I are not present, then the photo is supplied to the
|
||||||
|
# viewer on standard input. If your platform supports it, standard
|
||||||
|
# input is the best way to do this as it avoids the time and effort in
|
||||||
|
# generating and then cleaning up a secure temp file.
|
||||||
|
#
|
||||||
|
# If no photo-viewer is provided, GnuPG will look for xloadimage, eog,
|
||||||
|
# or display (ImageMagick). On Mac OS X and Windows, the default is
|
||||||
|
# to use your regular JPEG image viewer.
|
||||||
|
#
|
||||||
|
# Some other viewers:
|
||||||
|
# photo-viewer "qiv %i"
|
||||||
|
# photo-viewer "ee %i"
|
||||||
|
#
|
||||||
|
# This one saves a copy of the photo ID in your home directory:
|
||||||
|
# photo-viewer "cat > ~/photoid-for-key-%k.%t"
|
||||||
|
#
|
||||||
|
# Use your MIME handler to view photos:
|
||||||
|
# photo-viewer "metamail -q -d -b -c %T -s 'KeyID 0x%k' -f GnuPG"
|
||||||
|
|
||||||
|
# Passphrase agent
|
||||||
|
#
|
||||||
|
# We support the old experimental passphrase agent protocol as well as
|
||||||
|
# the new Assuan based one (currently available in the "newpg" package
|
||||||
|
# at ftp.gnupg.org/gcrypt/alpha/aegypten/). To make use of the agent,
|
||||||
|
# you have to run an agent as daemon and use the option
|
||||||
|
#
|
||||||
|
# use-agent
|
||||||
|
#
|
||||||
|
# which tries to use the agent but will fallback to the regular mode
|
||||||
|
# if there is a problem connecting to the agent. The normal way to
|
||||||
|
# locate the agent is by looking at the environment variable
|
||||||
|
# GPG_AGENT_INFO which should have been set during gpg-agent startup.
|
||||||
|
# In certain situations the use of this variable is not possible, thus
|
||||||
|
# the option
|
||||||
|
#
|
||||||
|
# --gpg-agent-info=<path>:<pid>:1
|
||||||
|
#
|
||||||
|
# may be used to override it.
|
||||||
|
|
||||||
|
# Automatic key location
|
||||||
|
#
|
||||||
|
# GnuPG can automatically locate and retrieve keys as needed using the
|
||||||
|
# auto-key-locate option. This happens when encrypting to an email
|
||||||
|
# address (in the "user@example.com" form), and there are no
|
||||||
|
# user@example.com keys on the local keyring. This option takes the
|
||||||
|
# following arguments, in the order they are to be tried:
|
||||||
|
#
|
||||||
|
# cert = locate a key using DNS CERT, as specified in RFC-4398.
|
||||||
|
# GnuPG can handle both the PGP (key) and IPGP (URL + fingerprint)
|
||||||
|
# CERT methods.
|
||||||
|
#
|
||||||
|
# pka = locate a key using DNS PKA.
|
||||||
|
#
|
||||||
|
# ldap = locate a key using the PGP Universal method of checking
|
||||||
|
# "ldap://keys.(thedomain)". For example, encrypting to
|
||||||
|
# user@example.com will check ldap://keys.example.com.
|
||||||
|
#
|
||||||
|
# keyserver = locate a key using whatever keyserver is defined using
|
||||||
|
# the keyserver option.
|
||||||
|
#
|
||||||
|
# You may also list arbitrary keyservers here by URL.
|
||||||
|
#
|
||||||
|
# Try CERT, then PKA, then LDAP, then hkp://subkeys.net:
|
||||||
|
#auto-key-locate cert pka ldap hkp://subkeys.pgp.net
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,8 +1,8 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
echo password-source: pass
|
||||||
if [ -z "$JIRACLOUD" ]; then
|
if [ -z "$JIRACLOUD" ]; then
|
||||||
echo endpoint: http://localhost:8080
|
echo endpoint: http://localhost:8080
|
||||||
echo user: gojira
|
echo user: gojira
|
||||||
echo password-keyring: true
|
|
||||||
else
|
else
|
||||||
echo endpoint: https://go-jira.atlassian.net
|
echo endpoint: https://go-jira.atlassian.net
|
||||||
echo user: gojira@example.com
|
echo user: gojira@example.com
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Go Jira <gojira@example.com>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
…(ΡαΆω GΈώ20,ΧΎ„¶ι―’«$Ggu©y1_a-ΟI'ΥΈοΘ}�Ν£4
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
…(Ñá¢ù G¸ÿsNÚÂúƒÊ•±ý9˳¼êÇx¾æ'˜ºô�Ÿ9’Øz?[ôÙÏÀ¬®d¯OÏñbg²Àá.–ý›ß’pþÄÒxòÜp ¸Û›D_c8&Ûƒ ™˜0²í(óó’<¬¿ùéjK;_c0™õ3Í-wý¾g~@üFõ5é«Ãî
|
||||||
Binary file not shown.
Binary file not shown.
+2
-2
@@ -13,7 +13,7 @@ PLAN 86
|
|||||||
|
|
||||||
# reset login
|
# reset login
|
||||||
RUNS $jira logout
|
RUNS $jira logout
|
||||||
echo "gojira123" | RUNS $jira login
|
RUNS $jira login
|
||||||
|
|
||||||
# cleanup from previous failed test executions
|
# cleanup from previous failed test executions
|
||||||
($jira ls | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
|
($jira ls | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
|
||||||
@@ -205,7 +205,7 @@ EOF
|
|||||||
jira="$jira --user mothra"
|
jira="$jira --user mothra"
|
||||||
|
|
||||||
RUNS $jira logout
|
RUNS $jira logout
|
||||||
echo "mothra123" | RUNS $jira login
|
RUNS $jira login
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
## vote for main issue, verify it shows when viewing the issue
|
## vote for main issue, verify it shows when viewing the issue
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ PLAN 8
|
|||||||
|
|
||||||
# reset login
|
# reset login
|
||||||
RUNS $jira logout
|
RUNS $jira logout
|
||||||
echo "gojira123" | RUNS $jira login
|
RUNS $jira login
|
||||||
|
|
||||||
# cleanup from previous failed test executions
|
# cleanup from previous failed test executions
|
||||||
($jira ls | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
|
($jira ls | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
|
||||||
|
|||||||
Reference in New Issue
Block a user