Merge component 'engine' from git@github.com:moby/moby master

This commit is contained in:
GordonTheTurtle
2018-05-22 17:07:26 +00:00
67 changed files with 2309 additions and 1667 deletions

View File

@ -13,8 +13,9 @@ Abhinandan Prativadi <abhi@docker.com>
Adrien Gallouët <adrien@gallouet.fr> <angt@users.noreply.github.com>
Ahmed Kamal <email.ahmedkamal@googlemail.com>
Ahmet Alp Balkan <ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
AJ Bowen <aj@gandi.net>
AJ Bowen <aj@gandi.net> <amy@gandi.net>
AJ Bowen <aj@soulshake.net>
AJ Bowen <aj@soulshake.net> <aj@gandi.net>
AJ Bowen <aj@soulshake.net> <amy@gandi.net>
Akihiro Matsushima <amatsusbit@gmail.com> <amatsus@users.noreply.github.com>
Akihiro Suda <suda.akihiro@lab.ntt.co.jp> <suda.kyoto@gmail.com>
Aleksa Sarai <asarai@suse.de>
@ -24,6 +25,7 @@ Aleksandrs Fadins <aleks@s-ko.net>
Alessandro Boch <aboch@tetrationanalytics.com> <aboch@docker.com>
Alex Chen <alexchenunix@gmail.com> <root@localhost.localdomain>
Alex Ellis <alexellis2@gmail.com>
Alex Goodman <wagoodman@gmail.com> <wagoodman@users.noreply.github.com>
Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
Alexander Morozov <lk4d4@docker.com>
Alexander Morozov <lk4d4@docker.com> <lk4d4math@gmail.com>
@ -121,6 +123,7 @@ Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
Doug Tangren <d.tangren@gmail.com>
Elan Ruusamäe <glen@pld-linux.org>
Elan Ruusamäe <glen@pld-linux.org> <glen@delfi.ee>
Elango Sivanandam <elango.siva@docker.com>
Eric G. Noriega <enoriega@vizuri.com> <egnoriega@users.noreply.github.com>
Eric Hanchrow <ehanchrow@ine.com> <eric.hanchrow@gmail.com>
Eric Rosenberg <ehaydenr@gmail.com> <ehaydenr@users.noreply.github.com>
@ -128,12 +131,14 @@ Erica Windisch <erica@windisch.us> <eric@windisch.us>
Erica Windisch <erica@windisch.us> <ewindisch@docker.com>
Erik Hollensbe <github@hollensbe.org> <erik+github@hollensbe.org>
Erwin van der Koogh <info@erronis.nl>
Ethan Bell <ebgamer29@gmail.com>
Euan Kemp <euan.kemp@coreos.com> <euank@amazon.com>
Eugen Krizo <eugen.krizo@gmail.com>
Evan Hazlett <ejhazlett@gmail.com> <ehazlett@users.noreply.github.com>
Evelyn Xu <evelynhsu21@gmail.com>
Evgeny Shmarnev <shmarnev@gmail.com>
Faiz Khan <faizkhan00@gmail.com>
Fangming Fang <fangming.fang@arm.com>
Felix Hupfeld <felix@quobyte.com> <quofelix@users.noreply.github.com>
Felix Ruess <felix.ruess@gmail.com> <felix.ruess@roboception.de>
Feng Yan <fy2462@gmail.com>
@ -155,6 +160,7 @@ Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@charmes.net>
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@docker.com>
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@dotcloud.com>
Guri <odg0318@gmail.com>
Gurjeet Singh <gurjeet@singh.im> <singh.gurjeet@gmail.com>
Gustav Sinder <gustav.sinder@gmail.com>
Günther Jungbluth <gunther@gameslabs.net>
@ -211,6 +217,8 @@ John Howard (VM) <John.Howard@microsoft.com> <jhoward@ntdev.microsoft.com>
John Howard (VM) <John.Howard@microsoft.com> <jhowardmsft@users.noreply.github.com>
John Howard (VM) <John.Howard@microsoft.com> <john.howard@microsoft.com>
John Stephens <johnstep@docker.com> <johnstep@users.noreply.github.com>
Jonathan Choy <jonathan.j.choy@gmail.com>
Jonathan Choy <jonathan.j.choy@gmail.com> <oni@tetsujinlabs.com>
Jon Surrell <jon.surrell@gmail.com> <jon.surrell@automattic.com>
Jordan Arentsen <blissdev@gmail.com>
Jordan Jennings <jjn2009@gmail.com> <jjn2009@users.noreply.github.com>
@ -286,6 +294,7 @@ Martin Redmond <redmond.martin@gmail.com> <xgithub@redmond5.com>
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
Masato Ohba <over.rye@gmail.com>
Matt Bentley <matt.bentley@docker.com> <mbentley@mbentley.net>
Matt Schurenko <matt.schurenko@gmail.com>
Matt Williams <mattyw@me.com>
@ -298,9 +307,12 @@ Mauricio Garavaglia <mauricio@medallia.com> <mauriciogaravaglia@gmail.com>
Michael Crosby <michael@docker.com> <crosby.michael@gmail.com>
Michael Crosby <michael@docker.com> <crosbymichael@gmail.com>
Michael Crosby <michael@docker.com> <michael@crosbymichael.com>
Michał Gryko <github@odkurzacz.org>
Michael Hudson-Doyle <michael.hudson@canonical.com> <michael.hudson@linaro.org>
Michael Huettermann <michael@huettermann.net>
Michael Käufl <docker@c.michael-kaeufl.de> <michael-k@users.noreply.github.com>
Michael Nussbaum <michael.nussbaum@getbraintree.com>
Michael Nussbaum <michael.nussbaum@getbraintree.com> <code@getbraintree.com>
Michael Spetsiotis <michael_spets@hotmail.com>
Michal Minář <miminar@redhat.com>
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com> <30386061+doncicuto@users.noreply.github.com>

View File

@ -39,7 +39,7 @@ Ahmed Kamal <email.ahmedkamal@googlemail.com>
Ahmet Alp Balkan <ahmetb@microsoft.com>
Aidan Feldman <aidan.feldman@gmail.com>
Aidan Hobson Sayers <aidanhs@cantab.net>
AJ Bowen <aj@gandi.net>
AJ Bowen <aj@soulshake.net>
Ajey Charantimath <ajey.charantimath@gmail.com>
ajneu <ajneu@users.noreply.github.com>
Akash Gupta <akagup@microsoft.com>
@ -54,6 +54,7 @@ Alan Scherger <flyinprogrammer@gmail.com>
Alan Thompson <cloojure@gmail.com>
Albert Callarisa <shark234@gmail.com>
Albert Zhang <zhgwenming@gmail.com>
Alejandro González Hevia <alejandrgh11@gmail.com>
Aleksa Sarai <asarai@suse.de>
Aleksandrs Fadins <aleks@s-ko.net>
Alena Prokharchyk <alena@rancher.com>
@ -65,6 +66,7 @@ Alex Coventry <alx@empirical.com>
Alex Crawford <alex.crawford@coreos.com>
Alex Ellis <alexellis2@gmail.com>
Alex Gaynor <alex.gaynor@gmail.com>
Alex Goodman <wagoodman@gmail.com>
Alex Olshansky <i@creagenics.com>
Alex Samorukov <samm@os2.kiev.ua>
Alex Warhawk <ax.warhawk@gmail.com>
@ -77,6 +79,7 @@ Alexander Shopov <ash@kambanaria.org>
Alexandre Beslic <alexandre.beslic@gmail.com>
Alexandre Garnier <zigarn@gmail.com>
Alexandre González <agonzalezro@gmail.com>
Alexandre Jomin <alexandrejomin@gmail.com>
Alexandru Sfirlogea <alexandru.sfirlogea@gmail.com>
Alexey Guskov <lexag@mail.ru>
Alexey Kotlyarov <alexey@infoxchange.net.au>
@ -98,11 +101,13 @@ Amir Goldstein <amir73il@aquasec.com>
Amit Bakshi <ambakshi@gmail.com>
Amit Krishnan <amit.krishnan@oracle.com>
Amit Shukla <amit.shukla@docker.com>
Amr Gawish <amr.gawish@gmail.com>
Amy Lindburg <amy.lindburg@docker.com>
Anand Patil <anand.prabhakar.patil@gmail.com>
AnandkumarPatel <anandkumarpatel@gmail.com>
Anatoly Borodin <anatoly.borodin@gmail.com>
Anchal Agrawal <aagrawa4@illinois.edu>
Anda Xu <anda.xu@docker.com>
Anders Janmyr <anders@janmyr.com>
Andre Dublin <81dublin@gmail.com>
Andre Granovsky <robotciti@live.com>
@ -197,6 +202,7 @@ Ben Toews <mastahyeti@gmail.com>
Ben Wiklund <ben@daisyowl.com>
Benjamin Atkin <ben@benatkin.com>
Benjamin Boudreau <boudreau.benjamin@gmail.com>
Benjamin Yolken <yolken@stripe.com>
Benoit Chesneau <bchesneau@gmail.com>
Bernerd Schaefer <bj.schaefer@gmail.com>
Bernhard M. Wiedemann <bwiedemann@suse.de>
@ -267,6 +273,7 @@ Carlos Sanchez <carlos@apache.org>
Carol Fager-Higgins <carol.fager-higgins@docker.com>
Cary <caryhartline@users.noreply.github.com>
Casey Bisson <casey.bisson@joyent.com>
Catalin Pirvu <pirvu.catalin94@gmail.com>
Ce Gao <ce.gao@outlook.com>
Cedric Davies <cedricda@microsoft.com>
Cezar Sa Espinola <cezarsa@gmail.com>
@ -293,6 +300,8 @@ Chen Min <chenmin46@huawei.com>
Chen Mingjie <chenmingjie0828@163.com>
Chen Qiu <cheney-90@hotmail.com>
Cheng-mean Liu <soccerl@microsoft.com>
Chengguang Xu <cgxu519@gmx.com>
chenyuzhu <chenyuzhi@oschina.cn>
Chetan Birajdar <birajdar.chetan@gmail.com>
Chewey <prosto-chewey@users.noreply.github.com>
Chia-liang Kao <clkao@clkao.org>
@ -313,6 +322,7 @@ Chris Snow <chsnow123@gmail.com>
Chris St. Pierre <chris.a.st.pierre@gmail.com>
Chris Stivers <chris@stivers.us>
Chris Swan <chris.swan@iee.org>
Chris Telfer <ctelfer@docker.com>
Chris Wahl <github@wahlnetwork.com>
Chris Weyl <cweyl@alumni.drew.edu>
Christian Berendt <berendt@b1-systems.de>
@ -336,6 +346,7 @@ Chun Chen <ramichen@tencent.com>
Ciro S. Costa <ciro.costa@usp.br>
Clayton Coleman <ccoleman@redhat.com>
Clinton Kitson <clintonskitson@gmail.com>
Cody Roseborough <crrosebo@amazon.com>
Coenraad Loubser <coenraad@wish.org.za>
Colin Dunklau <colin.dunklau@gmail.com>
Colin Hebert <hebert.colin@gmail.com>
@ -411,6 +422,7 @@ Dave MacDonald <mindlapse@gmail.com>
Dave Tucker <dt@docker.com>
David Anderson <dave@natulte.net>
David Calavera <david.calavera@gmail.com>
David Chung <david.chung@docker.com>
David Corking <dmc-source@dcorking.com>
David Cramer <davcrame@cisco.com>
David Currie <david_currie@uk.ibm.com>
@ -510,6 +522,7 @@ Eike Herzbach <eike@herzbach.net>
Eivin Giske Skaaren <eivinsn@axis.com>
Eivind Uggedal <eivind@uggedal.com>
Elan Ruusamäe <glen@pld-linux.org>
Elango Sivanandam <elango.siva@docker.com>
Elena Morozova <lelenanam@gmail.com>
Eli Uriegas <eli.uriegas@docker.com>
Elias Faxö <elias.faxo@tre.se>
@ -548,6 +561,7 @@ Erik St. Martin <alakriti@gmail.com>
Erik Weathers <erikdw@gmail.com>
Erno Hopearuoho <erno.hopearuoho@gmail.com>
Erwin van der Koogh <info@erronis.nl>
Ethan Bell <ebgamer29@gmail.com>
Euan Kemp <euan.kemp@coreos.com>
Eugen Krizo <eugen.krizo@gmail.com>
Eugene Yakubovich <eugene.yakubovich@coreos.com>
@ -575,6 +589,7 @@ Fabrizio Regini <freegenie@gmail.com>
Fabrizio Soppelsa <fsoppelsa@mirantis.com>
Faiz Khan <faizkhan00@gmail.com>
falmp <chico.lopes@gmail.com>
Fangming Fang <fangming.fang@arm.com>
Fangyuan Gao <21551127@zju.edu.cn>
Fareed Dudhia <fareeddudhia@googlemail.com>
Fathi Boudra <fathi.boudra@linaro.org>
@ -673,6 +688,7 @@ Guilherme Salgado <gsalgado@gmail.com>
Guillaume Dufour <gdufour.prestataire@voyages-sncf.com>
Guillaume J. Charmes <guillaume.charmes@docker.com>
guoxiuyan <guoxiuyan@huawei.com>
Guri <odg0318@gmail.com>
Gurjeet Singh <gurjeet@singh.im>
Guruprasad <lgp171188@gmail.com>
Gustav Sinder <gustav.sinder@gmail.com>
@ -696,6 +712,7 @@ heartlock <21521209@zju.edu.cn>
Hector Castro <hectcastro@gmail.com>
Helen Xie <chenjg@harmonycloud.cn>
Henning Sprang <henning.sprang@gmail.com>
Hiroshi Hatake <hatake@clear-code.com>
Hobofan <goisser94@gmail.com>
Hollie Teal <hollie@docker.com>
Hong Xu <hong@topbug.net>
@ -808,6 +825,7 @@ Jean-Pierre Huynh <jean-pierre.huynh@ounet.fr>
Jean-Tiare Le Bigot <jt@yadutaf.fr>
Jeeva S. Chelladhurai <sjeeva@gmail.com>
Jeff Anderson <jeff@docker.com>
Jeff Hajewski <jeff.hajewski@gmail.com>
Jeff Johnston <jeff.johnston.mn@gmail.com>
Jeff Lindsay <progrium@gmail.com>
Jeff Mickey <j@codemac.net>
@ -893,6 +911,7 @@ Jonas Pfenniger <jonas@pfenniger.name>
Jonathan A. Sternberg <jonathansternberg@gmail.com>
Jonathan Boulle <jonathanboulle@gmail.com>
Jonathan Camp <jonathan@irondojo.com>
Jonathan Choy <jonathan.j.choy@gmail.com>
Jonathan Dowland <jon+github@alcopop.org>
Jonathan Lebon <jlebon@redhat.com>
Jonathan Lomas <jonathan@floatinglomas.ca>
@ -962,6 +981,7 @@ Kareem Khazem <karkhaz@karkhaz.com>
kargakis <kargakis@users.noreply.github.com>
Karl Grzeszczak <karlgrz@gmail.com>
Karol Duleba <mr.fuxi@gmail.com>
Karthik Karanth <karanth.karthik@gmail.com>
Karthik Nayak <Karthik.188@gmail.com>
Kate Heddleston <kate.heddleston@gmail.com>
Katie McLaughlin <katie@glasnt.com>
@ -1108,6 +1128,7 @@ Manfred Zabarauskas <manfredas@zabarauskas.com>
Manjunath A Kumatagi <mkumatag@in.ibm.com>
Mansi Nahar <mmn4185@rit.edu>
Manuel Meurer <manuel@krautcomputing.com>
Manuel Rüger <manuel@rueg.eu>
Manuel Woelker <github@manuel.woelker.org>
mapk0y <mapk0y@gmail.com>
Marc Abramowitz <marc@marc-abramowitz.com>
@ -1149,10 +1170,12 @@ Martin Mosegaard Amdisen <martin.amdisen@praqma.com>
Martin Redmond <redmond.martin@gmail.com>
Mary Anthony <mary.anthony@docker.com>
Masahito Zembutsu <zembutsu@users.noreply.github.com>
Masato Ohba <over.rye@gmail.com>
Masayuki Morita <minamijoyo@gmail.com>
Mason Malone <mason.malone@gmail.com>
Mateusz Sulima <sulima.mateusz@gmail.com>
Mathias Monnerville <mathias@monnerville.com>
Mathieu Champlon <mathieu.champlon@docker.com>
Mathieu Le Marec - Pasquet <kiorky@cryptelium.net>
Mathieu Parent <math.parent@gmail.com>
Matt Apperson <me@mattapperson.com>
@ -1209,6 +1232,7 @@ Michael Huettermann <michael@huettermann.net>
Michael Irwin <mikesir87@gmail.com>
Michael Käufl <docker@c.michael-kaeufl.de>
Michael Neale <michael.neale@gmail.com>
Michael Nussbaum <michael.nussbaum@getbraintree.com>
Michael Prokop <github@michael-prokop.at>
Michael Scharf <github@scharf.gr>
Michael Spetsiotis <michael_spets@hotmail.com>
@ -1223,6 +1247,7 @@ Michal Minář <miminar@redhat.com>
Michal Wieczorek <wieczorek-michal@wp.pl>
Michaël Pailloncy <mpapo.dev@gmail.com>
Michał Czeraszkiewicz <czerasz@gmail.com>
Michał Gryko <github@odkurzacz.org>
Michiel@unhosted <michiel@unhosted.org>
Mickaël FORTUNATO <morsi.morsicus@gmail.com>
Miguel Angel Fernández <elmendalerenda@gmail.com>
@ -1239,6 +1264,7 @@ Mike Estes <mike.estes@logos.com>
Mike Gaffney <mike@uberu.com>
Mike Goelzer <mike.goelzer@docker.com>
Mike Leone <mleone896@gmail.com>
Mike Lundy <mike@fluffypenguin.org>
Mike MacCana <mike.maccana@gmail.com>
Mike Naberezny <mike@naberezny.com>
Mike Snitzer <snitzer@redhat.com>
@ -1297,6 +1323,7 @@ Niall O'Higgins <niallo@unworkable.org>
Nicholas E. Rabenau <nerab@gmx.at>
Nick DeCoursin <n.decoursin@foodpanda.com>
Nick Irvine <nfirvine@nfirvine.com>
Nick Neisen <nwneisen@gmail.com>
Nick Parker <nikaios@gmail.com>
Nick Payne <nick@kurai.co.uk>
Nick Russo <nicholasjamesrusso@gmail.com>
@ -1322,6 +1349,7 @@ Nishant Totla <nishanttotla@gmail.com>
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
Noah Meyerhans <nmeyerha@amazon.com>
Noah Treuhaft <noah.treuhaft@docker.com>
NobodyOnSE <ich@sektor.selfip.com>
noducks <onemannoducks@gmail.com>
Nolan Darilek <nolan@thewordnerd.info>
nponeccop <andy.melnikov@gmail.com>
@ -1329,7 +1357,6 @@ Nuutti Kotivuori <naked@iki.fi>
nzwsch <hi@nzwsch.com>
O.S. Tezer <ostezer@gmail.com>
objectified <objectified@gmail.com>
odk- <github@odkurzacz.org>
Oguz Bilgic <fisyonet@gmail.com>
Oh Jinkyun <tintypemolly@gmail.com>
Ohad Schneider <ohadschn@users.noreply.github.com>
@ -1352,6 +1379,7 @@ Patrick Böänziger <patrick.baenziger@bsi-software.com>
Patrick Devine <patrick.devine@docker.com>
Patrick Hemmer <patrick.hemmer@gmail.com>
Patrick Stapleton <github@gdi2290.com>
Patrik Cyvoct <patrik@ptrk.io>
pattichen <craftsbear@gmail.com>
Paul <paul9869@gmail.com>
paul <paul@inkling.com>
@ -1423,6 +1451,7 @@ Pradip Dhara <pradipd@microsoft.com>
Prasanna Gautam <prasannagautam@gmail.com>
Pratik Karki <prertik@outlook.com>
Prayag Verma <prayag.verma@gmail.com>
Priya Wadhwa <priyawadhwa@google.com>
Przemek Hejman <przemyslaw.hejman@gmail.com>
Pure White <daniel48@126.com>
pysqz <randomq@126.com>
@ -1838,11 +1867,13 @@ Wang Xing <hzwangxing@corp.netease.com>
Wang Yuexiao <wang.yuexiao@zte.com.cn>
Ward Vandewege <ward@jhvc.com>
WarheadsSE <max@warheads.net>
Wassim Dhif <wassimdhif@gmail.com>
Wayne Chang <wayne@neverfear.org>
Wayne Song <wsong@docker.com>
Weerasak Chongnguluam <singpor@gmail.com>
Wei Wu <wuwei4455@gmail.com>
Wei-Ting Kuo <waitingkuo0527@gmail.com>
weipeng <weipeng@tuscloud.io>
weiyan <weiyan3@huawei.com>
Weiyang Zhu <cnresonant@gmail.com>
Wen Cheng Ma <wenchma@cn.ibm.com>
@ -1948,5 +1979,6 @@ Zunayed Ali <zunayed@gmail.com>
Átila Camurça Alves <camurca.home@gmail.com>
尹吉峰 <jifeng.yin@gmail.com>
徐俊杰 <paco.xu@daocloud.io>
慕陶 <jihui.xjh@alibaba-inc.com>
搏通 <yufeng.pyf@alibaba-inc.com>
黄艳红00139573 <huang.yanhong@zte.com.cn>

View File

@ -4663,7 +4663,7 @@ paths:
AppArmorProfile:
type: "string"
ExecIDs:
type: "string"
type: "array"
HostConfig:
$ref: "#/definitions/HostConfig"
GraphDriver:

View File

@ -82,11 +82,14 @@ func GetTimestamp(value string, reference time.Time) (string, error) {
}
if err != nil {
// if there is a `-` then it's an RFC3339 like timestamp otherwise assume unixtimestamp
// if there is a `-` then it's an RFC3339 like timestamp
if strings.Contains(value, "-") {
return "", err // was probably an RFC3339 like timestamp but the parser failed with an error
}
return value, nil // unixtimestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server)
if _, _, err := parseTimestamp(value); err != nil {
return "", fmt.Errorf("failed to parse value as time or duration: %q", value)
}
return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server)
}
return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil
@ -104,6 +107,10 @@ func ParseTimestamps(value string, def int64) (int64, int64, error) {
if value == "" {
return def, 0, nil
}
return parseTimestamp(value)
}
func parseTimestamp(value string) (int64, int64, error) {
sa := strings.SplitN(value, ".", 2)
s, err := strconv.ParseInt(sa[0], 10, 64)
if err != nil {

View File

@ -49,8 +49,8 @@ func TestGetTimestamp(t *testing.T) {
{"1.5h", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false},
{"1h30m", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false},
// String fallback
{"invalid", "invalid", false},
{"invalid", "", true},
{"", "", true},
}
for _, c := range cases {

View File

@ -6,9 +6,10 @@ import (
"github.com/docker/docker/builder/dockerfile/instructions"
"github.com/docker/docker/builder/remotecontext"
"github.com/docker/docker/internal/testutil"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/reexec"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/skip"
)
@ -139,5 +140,5 @@ func executeTestCase(t *testing.T, testCase dispatchTestCase) {
b := newBuilderWithMockBackend()
sb := newDispatchRequest(b, '`', context, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
err = dispatch(sb, testCase.cmd)
testutil.ErrorContains(t, err, testCase.expectedError)
assert.Check(t, is.ErrorContains(err, testCase.expectedError))
}

View File

@ -6,7 +6,6 @@ import (
"github.com/docker/docker/builder/dockerfile/command"
"github.com/docker/docker/builder/dockerfile/parser"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
@ -19,11 +18,11 @@ func TestCommandsExactlyOneArgument(t *testing.T) {
"STOPSIGNAL",
}
for _, command := range commands {
ast, err := parser.Parse(strings.NewReader(command))
for _, cmd := range commands {
ast, err := parser.Parse(strings.NewReader(cmd))
assert.NilError(t, err)
_, err = ParseInstruction(ast.AST.Children[0])
assert.Check(t, is.Error(err, errExactlyOneArgument(command).Error()))
assert.Check(t, is.Error(err, errExactlyOneArgument(cmd).Error()))
}
}
@ -37,11 +36,11 @@ func TestCommandsAtLeastOneArgument(t *testing.T) {
"VOLUME",
}
for _, command := range commands {
ast, err := parser.Parse(strings.NewReader(command))
for _, cmd := range commands {
ast, err := parser.Parse(strings.NewReader(cmd))
assert.NilError(t, err)
_, err = ParseInstruction(ast.AST.Children[0])
assert.Check(t, is.Error(err, errAtLeastOneArgument(command).Error()))
assert.Check(t, is.Error(err, errAtLeastOneArgument(cmd).Error()))
}
}
@ -51,11 +50,11 @@ func TestCommandsNoDestinationArgument(t *testing.T) {
"COPY",
}
for _, command := range commands {
ast, err := parser.Parse(strings.NewReader(command + " arg1"))
for _, cmd := range commands {
ast, err := parser.Parse(strings.NewReader(cmd + " arg1"))
assert.NilError(t, err)
_, err = ParseInstruction(ast.AST.Children[0])
assert.Check(t, is.Error(err, errNoDestinationArgument(command).Error()))
assert.Check(t, is.Error(err, errNoDestinationArgument(cmd).Error()))
}
}
@ -90,10 +89,10 @@ func TestCommandsBlankNames(t *testing.T) {
"LABEL",
}
for _, command := range commands {
for _, cmd := range commands {
node := &parser.Node{
Original: command + " =arg2",
Value: strings.ToLower(command),
Original: cmd + " =arg2",
Value: strings.ToLower(cmd),
Next: &parser.Node{
Value: "",
Next: &parser.Node{
@ -102,7 +101,7 @@ func TestCommandsBlankNames(t *testing.T) {
},
}
_, err := ParseInstruction(node)
assert.Check(t, is.Error(err, errBlankCommandNames(command).Error()))
assert.Check(t, is.Error(err, errBlankCommandNames(cmd).Error()))
}
}
@ -134,7 +133,7 @@ func TestParseOptInterval(t *testing.T) {
Value: "50ns",
}
_, err := parseOptInterval(flInterval)
testutil.ErrorContains(t, err, "cannot be less than 1ms")
assert.Check(t, is.ErrorContains(err, "cannot be less than 1ms"))
flInterval.Value = "1ms"
_, err = parseOptInterval(flInterval)
@ -194,6 +193,6 @@ func TestErrorCases(t *testing.T) {
}
n := ast.AST.Children[0]
_, err = ParseInstruction(n)
testutil.ErrorContains(t, err, c.expectedError)
assert.Check(t, is.ErrorContains(err, c.expectedError))
}
}

View File

@ -6,7 +6,6 @@ import (
"fmt"
"testing"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
@ -48,7 +47,7 @@ func TestNormalizeDest(t *testing.T) {
}
assert.Check(t, is.Equal(testcase.expected, actual), msg)
} else {
testutil.ErrorContains(t, err, testcase.etext)
assert.Check(t, is.ErrorContains(err, testcase.etext))
}
}
}

View File

@ -10,7 +10,6 @@ import (
"testing"
"github.com/docker/docker/builder"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/fs"
@ -232,7 +231,7 @@ func TestGetWithStatusError(t *testing.T) {
assert.NilError(t, err)
assert.Check(t, is.Contains(string(body), testcase.expectedBody))
} else {
testutil.ErrorContains(t, err, testcase.expectedErr)
assert.Check(t, is.ErrorContains(err, testcase.expectedErr))
}
}
}

View File

@ -2,10 +2,8 @@ package cli // import "github.com/docker/docker/cli"
import (
"fmt"
"strings"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -21,7 +19,7 @@ func SetupRootCommand(rootCmd *cobra.Command) {
rootCmd.SetUsageTemplate(usageTemplate)
rootCmd.SetHelpTemplate(helpTemplate)
rootCmd.SetFlagErrorFunc(FlagErrorFunc)
rootCmd.SetHelpCommand(helpCommand)
rootCmd.SetVersionTemplate("Docker version {{.Version}}\n")
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
@ -44,23 +42,6 @@ func FlagErrorFunc(cmd *cobra.Command, err error) error {
}
}
var helpCommand = &cobra.Command{
Use: "help [command]",
Short: "Help about the command",
PersistentPreRun: func(cmd *cobra.Command, args []string) {},
PersistentPostRun: func(cmd *cobra.Command, args []string) {},
RunE: func(c *cobra.Command, args []string) error {
cmd, args, e := c.Root().Find(args)
if cmd == nil || e != nil || len(args) > 0 {
return errors.Errorf("unknown help topic: %v", strings.Join(args, " "))
}
helpFunc := cmd.HelpFunc()
helpFunc(cmd, args)
return nil
},
}
func hasSubCommands(cmd *cobra.Command) bool {
return len(operationSubCommands(cmd)) > 0
}
@ -116,7 +97,7 @@ Examples:
{{ .Example }}
{{- end}}
{{- if .HasFlags}}
{{- if .HasAvailableFlags}}
Options:
{{ wrappedFlagUsages . | trimRightSpace}}

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/env"
@ -162,7 +161,7 @@ func TestParseHostURL(t *testing.T) {
for _, testcase := range testcases {
actual, err := ParseHostURL(testcase.host)
if testcase.expectedErr != "" {
testutil.ErrorContains(t, err, testcase.expectedErr)
assert.Check(t, is.ErrorContains(err, testcase.expectedErr))
}
assert.Check(t, is.DeepEqual(testcase.expected, actual))
}

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/docker/api/types"
timetypes "github.com/docker/docker/api/types/time"
"github.com/pkg/errors"
)
// ContainerLogs returns the logs generated by a container in an io.ReadCloser.
@ -45,7 +46,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options
if options.Since != "" {
ts, err := timetypes.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, err
return nil, errors.Wrap(err, `invalid value for "since"`)
}
query.Set("since", ts)
}
@ -53,7 +54,7 @@ func (cli *Client) ContainerLogs(ctx context.Context, container string, options
if options.Until != "" {
ts, err := timetypes.GetTimestamp(options.Until, time.Now())
if err != nil {
return nil, err
return nil, errors.Wrap(err, `invalid value for "until"`)
}
query.Set("until", ts)
}

View File

@ -2,6 +2,7 @@ package client // import "github.com/docker/docker/client"
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
@ -12,10 +13,9 @@ import (
"testing"
"time"
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
func TestContainerLogsNotFoundError(t *testing.T) {
@ -33,17 +33,15 @@ func TestContainerLogsError(t *testing.T) {
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
assert.Check(t, is.Error(err, "Error response from daemon: Server error"))
_, err = client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{
Since: "2006-01-02TZ",
})
testutil.ErrorContains(t, err, `parsing time "2006-01-02TZ"`)
assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`))
_, err = client.ContainerLogs(context.Background(), "container_id", types.ContainerLogsOptions{
Until: "2006-01-02TZ",
})
testutil.ErrorContains(t, err, `parsing time "2006-01-02TZ"`)
assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`))
}
func TestContainerLogs(t *testing.T) {
@ -51,6 +49,7 @@ func TestContainerLogs(t *testing.T) {
cases := []struct {
options types.ContainerLogsOptions
expectedQueryParams map[string]string
expectedError string
}{
{
expectedQueryParams: map[string]string{
@ -84,32 +83,44 @@ func TestContainerLogs(t *testing.T) {
},
{
options: types.ContainerLogsOptions{
// An complete invalid date, timestamp or go duration will be
// passed as is
Since: "invalid but valid",
// timestamp will be passed as is
Since: "1136073600.000000001",
},
expectedQueryParams: map[string]string{
"tail": "",
"since": "invalid but valid",
"since": "1136073600.000000001",
},
},
{
options: types.ContainerLogsOptions{
// An complete invalid date, timestamp or go duration will be
// passed as is
Until: "invalid but valid",
// timestamp will be passed as is
Until: "1136073600.000000001",
},
expectedQueryParams: map[string]string{
"tail": "",
"until": "invalid but valid",
"until": "1136073600.000000001",
},
},
{
options: types.ContainerLogsOptions{
// An complete invalid date will not be passed
Since: "invalid value",
},
expectedError: `invalid value for "since": failed to parse value as time or duration: "invalid value"`,
},
{
options: types.ContainerLogsOptions{
// An complete invalid date will not be passed
Until: "invalid value",
},
expectedError: `invalid value for "until": failed to parse value as time or duration: "invalid value"`,
},
}
for _, logCase := range cases {
client := &Client{
client: newMockClient(func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
}
// Check query parameters
query := r.URL.Query()
@ -126,17 +137,15 @@ func TestContainerLogs(t *testing.T) {
}),
}
body, err := client.ContainerLogs(context.Background(), "container_id", logCase.options)
if err != nil {
t.Fatal(err)
if logCase.expectedError != "" {
assert.Check(t, is.Error(err, logCase.expectedError))
continue
}
assert.NilError(t, err)
defer body.Close()
content, err := ioutil.ReadAll(body)
if err != nil {
t.Fatal(err)
}
if string(content) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(content))
}
assert.NilError(t, err)
assert.Check(t, is.Contains(string(content), "response"))
}
}

View File

@ -8,6 +8,7 @@ import (
"github.com/docker/docker/api/types"
timetypes "github.com/docker/docker/api/types/time"
"github.com/pkg/errors"
)
// ServiceLogs returns the logs generated by a service in an io.ReadCloser.
@ -25,7 +26,7 @@ func (cli *Client) ServiceLogs(ctx context.Context, serviceID string, options ty
if options.Since != "" {
ts, err := timetypes.GetTimestamp(options.Since, time.Now())
if err != nil {
return nil, err
return nil, errors.Wrap(err, `invalid value for "since"`)
}
query.Set("since", ts)
}

View File

@ -2,6 +2,7 @@ package client // import "github.com/docker/docker/client"
import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
@ -12,9 +13,9 @@ import (
"testing"
"time"
"context"
"github.com/docker/docker/api/types"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
func TestServiceLogsError(t *testing.T) {
@ -22,15 +23,11 @@ func TestServiceLogsError(t *testing.T) {
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.ServiceLogs(context.Background(), "service_id", types.ContainerLogsOptions{})
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
assert.Check(t, is.Error(err, "Error response from daemon: Server error"))
_, err = client.ServiceLogs(context.Background(), "service_id", types.ContainerLogsOptions{
Since: "2006-01-02TZ",
})
if err == nil || !strings.Contains(err.Error(), `parsing time "2006-01-02TZ"`) {
t.Fatalf("expected a 'parsing time' error, got %v", err)
}
assert.Check(t, is.ErrorContains(err, `parsing time "2006-01-02TZ"`))
}
func TestServiceLogs(t *testing.T) {
@ -38,6 +35,7 @@ func TestServiceLogs(t *testing.T) {
cases := []struct {
options types.ContainerLogsOptions
expectedQueryParams map[string]string
expectedError string
}{
{
expectedQueryParams: map[string]string{
@ -71,21 +69,27 @@ func TestServiceLogs(t *testing.T) {
},
{
options: types.ContainerLogsOptions{
// An complete invalid date, timestamp or go duration will be
// passed as is
Since: "invalid but valid",
// timestamp will be passed as is
Since: "1136073600.000000001",
},
expectedQueryParams: map[string]string{
"tail": "",
"since": "invalid but valid",
"since": "1136073600.000000001",
},
},
{
options: types.ContainerLogsOptions{
// An complete invalid date will not be passed
Since: "invalid value",
},
expectedError: `invalid value for "since": failed to parse value as time or duration: "invalid value"`,
},
}
for _, logCase := range cases {
client := &Client{
client: newMockClient(func(r *http.Request) (*http.Response, error) {
if !strings.HasPrefix(r.URL.Path, expectedURL) {
return nil, fmt.Errorf("Expected URL '%s', got '%s'", expectedURL, r.URL)
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, r.URL)
}
// Check query parameters
query := r.URL.Query()
@ -102,17 +106,15 @@ func TestServiceLogs(t *testing.T) {
}),
}
body, err := client.ServiceLogs(context.Background(), "service_id", logCase.options)
if err != nil {
t.Fatal(err)
if logCase.expectedError != "" {
assert.Check(t, is.Error(err, logCase.expectedError))
continue
}
assert.NilError(t, err)
defer body.Close()
content, err := ioutil.ReadAll(body)
if err != nil {
t.Fatal(err)
}
if string(content) != "response" {
t.Fatalf("expected response to contain 'response', got %s", string(content))
}
assert.NilError(t, err)
assert.Check(t, is.Contains(string(content), "response"))
}
}

View File

@ -11,7 +11,6 @@ import (
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
@ -22,7 +21,7 @@ func TestSwarmGetUnlockKeyError(t *testing.T) {
}
_, err := client.SwarmGetUnlockKey(context.Background())
testutil.ErrorContains(t, err, "Error response from daemon: Server error")
assert.Check(t, is.ErrorContains(err, "Error response from daemon: Server error"))
}
func TestSwarmGetUnlockKey(t *testing.T) {

View File

@ -11,7 +11,6 @@ import (
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/pkg/errors"
@ -23,7 +22,7 @@ func TestVolumeInspectError(t *testing.T) {
}
_, err := client.VolumeInspect(context.Background(), "nothing")
testutil.ErrorContains(t, err, "Error response from daemon: Server error")
assert.Check(t, is.ErrorContains(err, "Error response from daemon: Server error"))
}
func TestVolumeInspectNotFound(t *testing.T) {

View File

@ -4,7 +4,6 @@ import (
"testing"
"github.com/docker/docker/daemon/config"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/fs"
@ -58,7 +57,7 @@ func TestLoadDaemonCliConfigWithConflicts(t *testing.T) {
assert.Check(t, flags.Set("label", "l2=baz"))
_, err := loadDaemonCliConfig(opts)
testutil.ErrorContains(t, err, "as a flag and in the configuration file: labels")
assert.Check(t, is.ErrorContains(err, "as a flag and in the configuration file: labels"))
}
func TestLoadDaemonCliWithConflictingNodeGenericResources(t *testing.T) {
@ -74,7 +73,7 @@ func TestLoadDaemonCliWithConflictingNodeGenericResources(t *testing.T) {
assert.Check(t, flags.Set("node-generic-resource", "r2=baz"))
_, err := loadDaemonCliConfig(opts)
testutil.ErrorContains(t, err, "as a flag and in the configuration file: node-generic-resources")
assert.Check(t, is.ErrorContains(err, "as a flag and in the configuration file: node-generic-resources"))
}
func TestLoadDaemonCliWithConflictingLabels(t *testing.T) {

View File

@ -24,18 +24,16 @@ func newDaemonCommand() *cobra.Command {
SilenceErrors: true,
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if opts.version {
showVersion()
return nil
}
opts.flags = cmd.Flags()
return runDaemon(opts)
},
DisableFlagsInUseLine: true,
Version: fmt.Sprintf("%s, build %s", dockerversion.Version, dockerversion.GitCommit),
}
cli.SetupRootCommand(cmd)
flags := cmd.Flags()
flags.BoolVarP(&opts.version, "version", "v", false, "Print version information and quit")
flags.BoolP("version", "v", false, "Print version information and quit")
flags.StringVar(&opts.configFile, "config-file", defaultDaemonConfigFile, "Daemon configuration file")
opts.InstallFlags(flags)
installConfigFlags(opts.daemonConfig, flags)
@ -44,10 +42,6 @@ func newDaemonCommand() *cobra.Command {
return cmd
}
func showVersion() {
fmt.Printf("Docker version %s, build %s\n", dockerversion.Version, dockerversion.GitCommit)
}
func main() {
if reexec.Init() {
return

View File

@ -30,7 +30,6 @@ var (
)
type daemonOptions struct {
version bool
configFile string
daemonConfig *config.Config
flags *pflag.FlagSet

View File

@ -7,7 +7,6 @@ import (
"testing"
"github.com/docker/docker/daemon/discovery"
"github.com/docker/docker/internal/testutil"
"github.com/docker/docker/opts"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -62,10 +61,7 @@ func TestFindConfigurationConflicts(t *testing.T) {
flags.String("authorization-plugins", "", "")
assert.Check(t, flags.Set("authorization-plugins", "asdf"))
testutil.ErrorContains(t,
findConfigurationConflicts(config, flags),
"authorization-plugins: (from flag: asdf, from file: foobar)")
assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "authorization-plugins: (from flag: asdf, from file: foobar)"))
}
func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
@ -76,8 +72,7 @@ func TestFindConfigurationConflictsWithNamedOptions(t *testing.T) {
flags.VarP(opts.NewNamedListOptsRef("hosts", &hosts, opts.ValidateHost), "host", "H", "Daemon socket(s) to connect to")
assert.Check(t, flags.Set("host", "tcp://127.0.0.1:4444"))
assert.Check(t, flags.Set("host", "unix:///var/run/docker.sock"))
testutil.ErrorContains(t, findConfigurationConflicts(config, flags), "hosts")
assert.Check(t, is.ErrorContains(findConfigurationConflicts(config, flags), "hosts"))
}
func TestDaemonConfigurationMergeConflicts(t *testing.T) {
@ -460,8 +455,7 @@ func TestReloadSetConfigFileNotExist(t *testing.T) {
flags.Set("config-file", configFile)
err := Reload(configFile, flags, func(c *Config) {})
assert.Check(t, is.ErrorContains(err, ""))
testutil.ErrorContains(t, err, "unable to configure the Docker daemon with file")
assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file"))
}
// TestReloadDefaultConfigNotExist tests that if the default configuration file
@ -494,8 +488,7 @@ func TestReloadBadDefaultConfig(t *testing.T) {
flags := pflag.NewFlagSet("test", pflag.ContinueOnError)
flags.String("config-file", configFile, "")
err = Reload(configFile, flags, func(c *Config) {})
assert.Check(t, is.ErrorContains(err, ""))
testutil.ErrorContains(t, err, "unable to configure the Docker daemon with file")
assert.Check(t, is.ErrorContains(err, "unable to configure the Docker daemon with file"))
}
func TestReloadWithConflictingLabels(t *testing.T) {
@ -508,7 +501,7 @@ func TestReloadWithConflictingLabels(t *testing.T) {
flags.String("config-file", configFile, "")
flags.StringSlice("labels", lbls, "")
err := Reload(configFile, flags, func(c *Config) {})
testutil.ErrorContains(t, err, "conflict labels for foo=baz and foo=bar")
assert.Check(t, is.ErrorContains(err, "conflict labels for foo=baz and foo=bar"))
}
func TestReloadWithDuplicateLabels(t *testing.T) {

View File

@ -9,8 +9,8 @@ import (
"github.com/docker/docker/api/types"
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/container"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
func newDaemonWithTmpRoot(t *testing.T) (*Daemon, func()) {
@ -30,7 +30,6 @@ func newContainerWithState(state *container.State) *container.Container {
State: state,
Config: &containertypes.Config{},
}
}
// TestContainerDelete tests that a useful error message and instructions is
@ -74,8 +73,8 @@ func TestContainerDelete(t *testing.T) {
d.containers.Add(c.ID, c)
err := d.ContainerRm(c.ID, &types.ContainerRmConfig{ForceRemove: false})
testutil.ErrorContains(t, err, te.errMsg)
testutil.ErrorContains(t, err, te.fixMsg)
assert.Check(t, is.ErrorContains(err, te.errMsg))
assert.Check(t, is.ErrorContains(err, te.fixMsg))
}
}
@ -92,5 +91,5 @@ func TestContainerDoubleDelete(t *testing.T) {
// Try to remove the container when its state is removalInProgress.
// It should return an error indicating it is under removal progress.
err := d.ContainerRm(c.ID, &types.ContainerRmConfig{ForceRemove: true})
testutil.ErrorContains(t, err, fmt.Sprintf("removal of container %s is already in progress", c.ID))
assert.Check(t, is.ErrorContains(err, fmt.Sprintf("removal of container %s is already in progress", c.ID)))
}

View File

@ -4,7 +4,6 @@ import (
"testing"
"time"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
@ -12,11 +11,11 @@ import (
func TestFastTimeMarshalJSONWithInvalidYear(t *testing.T) {
aTime := time.Date(-1, 1, 1, 0, 0, 0, 0, time.Local)
_, err := fastTimeMarshalJSON(aTime)
testutil.ErrorContains(t, err, "year outside of range")
assert.Check(t, is.ErrorContains(err, "year outside of range"))
anotherTime := time.Date(10000, 1, 1, 0, 0, 0, 0, time.Local)
_, err = fastTimeMarshalJSON(anotherTime)
testutil.ErrorContains(t, err, "year outside of range")
assert.Check(t, is.ErrorContains(err, "year outside of range"))
}
func TestFastTimeMarshalJSON(t *testing.T) {

View File

@ -6,7 +6,6 @@ import (
"path/filepath"
"testing"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/fs"
@ -22,7 +21,7 @@ func TestLoadOrCreateTrustKeyInvalidKeyFile(t *testing.T) {
assert.NilError(t, err)
_, err = loadOrCreateTrustKey(tmpKeyFile.Name())
testutil.ErrorContains(t, err, "Error loading key file")
assert.Check(t, is.ErrorContains(err, "Error loading key file"))
}
func TestLoadOrCreateTrustKeyCreateKeyWhenFileDoesNotExist(t *testing.T) {

View File

@ -10,7 +10,8 @@ import (
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/reference"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/opencontainers/go-digest"
)
@ -104,7 +105,7 @@ func TestFixManifestLayersBadParent(t *testing.T) {
}
err := fixManifestLayers(&duplicateLayerManifest)
testutil.ErrorContains(t, err, "invalid parent ID")
assert.Check(t, is.ErrorContains(err, "invalid parent ID"))
}
// TestValidateManifest verifies the validateManifest function

View File

@ -10,10 +10,9 @@ import (
"path/filepath"
"testing"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest"
)
func defaultFSStoreBackend(t *testing.T) (StoreBackend, func()) {
@ -39,7 +38,7 @@ func TestFSGetInvalidData(t *testing.T) {
assert.Check(t, err)
_, err = store.Get(id)
testutil.ErrorContains(t, err, "failed to verify")
assert.Check(t, is.ErrorContains(err, "failed to verify"))
}
func TestFSInvalidSet(t *testing.T) {
@ -51,7 +50,7 @@ func TestFSInvalidSet(t *testing.T) {
assert.Check(t, err)
_, err = store.Set([]byte("foobar"))
testutil.ErrorContains(t, err, "failed to write digest data")
assert.Check(t, is.ErrorContains(err, "failed to write digest data"))
}
func TestFSInvalidRoot(t *testing.T) {
@ -78,7 +77,7 @@ func TestFSInvalidRoot(t *testing.T) {
f.Close()
_, err = NewFSStoreBackend(root)
testutil.ErrorContains(t, err, "failed to create storage backend")
assert.Check(t, is.ErrorContains(err, "failed to create storage backend"))
os.RemoveAll(root)
}
@ -116,14 +115,14 @@ func TestFSMetadataGetSet(t *testing.T) {
}
_, err = store.GetMetadata(id2, "tkey2")
testutil.ErrorContains(t, err, "failed to read metadata")
assert.Check(t, is.ErrorContains(err, "failed to read metadata"))
id3 := digest.FromBytes([]byte("baz"))
err = store.SetMetadata(id3, "tkey", []byte("tval"))
testutil.ErrorContains(t, err, "failed to get digest")
assert.Check(t, is.ErrorContains(err, "failed to get digest"))
_, err = store.GetMetadata(id3, "tkey")
testutil.ErrorContains(t, err, "failed to get digest")
assert.Check(t, is.ErrorContains(err, "failed to get digest"))
}
func TestFSInvalidWalker(t *testing.T) {
@ -191,7 +190,7 @@ func TestFSGetUnsetKey(t *testing.T) {
for _, key := range []digest.Digest{"foobar:abc", "sha256:abc", "sha256:c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2a"} {
_, err := store.Get(key)
testutil.ErrorContains(t, err, "failed to get digest")
assert.Check(t, is.ErrorContains(err, "failed to get digest"))
}
}
@ -201,7 +200,7 @@ func TestFSGetEmptyData(t *testing.T) {
for _, emptyData := range [][]byte{nil, {}} {
_, err := store.Set(emptyData)
testutil.ErrorContains(t, err, "invalid empty data")
assert.Check(t, is.ErrorContains(err, "invalid empty data"))
}
}
@ -219,7 +218,7 @@ func TestFSDelete(t *testing.T) {
assert.Check(t, err)
_, err = store.Get(id)
testutil.ErrorContains(t, err, "failed to get digest")
assert.Check(t, is.ErrorContains(err, "failed to get digest"))
_, err = store.Get(id2)
assert.Check(t, err)
@ -228,7 +227,7 @@ func TestFSDelete(t *testing.T) {
assert.Check(t, err)
_, err = store.Get(id2)
testutil.ErrorContains(t, err, "failed to get digest")
assert.Check(t, is.ErrorContains(err, "failed to get digest"))
}
func TestFSWalker(t *testing.T) {
@ -267,5 +266,5 @@ func TestFSWalkerStopOnError(t *testing.T) {
err = store.Walk(func(id digest.Digest) error {
return errors.New("what")
})
testutil.ErrorContains(t, err, "what")
assert.Check(t, is.ErrorContains(err, "what"))
}

View File

@ -13,7 +13,6 @@ import (
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/internal/testutil"
"github.com/docker/docker/pkg/stdcopy"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -136,7 +135,7 @@ func TestConfigsCreateAndDelete(t *testing.T) {
assert.NilError(t, err)
insp, _, err = client.ConfigInspectWithRaw(ctx, configID)
testutil.ErrorContains(t, err, "No such config")
assert.Check(t, is.ErrorContains(err, "No such config"))
}
func TestConfigsUpdate(t *testing.T) {
@ -190,7 +189,7 @@ func TestConfigsUpdate(t *testing.T) {
// this test will produce an error in func UpdateConfig
insp.Spec.Data = []byte("TESTINGDATA2")
err = client.ConfigUpdate(ctx, configID, insp.Version, insp.Spec)
testutil.ErrorContains(t, err, "only updates to Labels are allowed")
assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed"))
}
func TestTemplatedConfig(t *testing.T) {

View File

@ -8,7 +8,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/skip"
@ -22,9 +21,9 @@ func TestCopyFromContainerPathDoesNotExist(t *testing.T) {
cid := container.Create(t, ctx, apiclient)
_, _, err := apiclient.CopyFromContainer(ctx, cid, "/dne")
assert.Assert(t, client.IsErrNotFound(err))
assert.Check(t, client.IsErrNotFound(err))
expected := fmt.Sprintf("No such container:path: %s:%s", cid, "/dne")
testutil.ErrorContains(t, err, expected)
assert.Check(t, is.ErrorContains(err, expected))
}
func TestCopyFromContainerPathIsNotDir(t *testing.T) {
@ -36,7 +35,7 @@ func TestCopyFromContainerPathIsNotDir(t *testing.T) {
cid := container.Create(t, ctx, apiclient)
_, _, err := apiclient.CopyFromContainer(ctx, cid, "/etc/passwd/")
assert.Assert(t, is.Contains(err.Error(), "not a directory"))
assert.Assert(t, is.ErrorContains(err, "not a directory"))
}
func TestCopyToContainerPathDoesNotExist(t *testing.T) {
@ -48,9 +47,9 @@ func TestCopyToContainerPathDoesNotExist(t *testing.T) {
cid := container.Create(t, ctx, apiclient)
err := apiclient.CopyToContainer(ctx, cid, "/dne", nil, types.CopyToContainerOptions{})
assert.Assert(t, client.IsErrNotFound(err))
assert.Check(t, client.IsErrNotFound(err))
expected := fmt.Sprintf("No such container:path: %s:%s", cid, "/dne")
testutil.ErrorContains(t, err, expected)
assert.Check(t, is.ErrorContains(err, expected))
}
func TestCopyToContainerPathIsNotDir(t *testing.T) {
@ -62,5 +61,5 @@ func TestCopyToContainerPathIsNotDir(t *testing.T) {
cid := container.Create(t, ctx, apiclient)
err := apiclient.CopyToContainer(ctx, cid, "/etc/passwd/", nil, types.CopyToContainerOptions{})
assert.Assert(t, is.Contains(err.Error(), "not a directory"))
assert.Assert(t, is.ErrorContains(err, "not a directory"))
}

View File

@ -8,7 +8,8 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/network"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/skip"
)
@ -48,7 +49,7 @@ func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
&network.NetworkingConfig{},
"",
)
testutil.ErrorContains(t, err, tc.expectedError)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
})
}
}
@ -88,7 +89,7 @@ func TestCreateWithInvalidEnv(t *testing.T) {
&network.NetworkingConfig{},
"",
)
testutil.ErrorContains(t, err, tc.expectedError)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
})
}
}
@ -133,6 +134,6 @@ func TestCreateTmpfsMountsTarget(t *testing.T) {
&network.NetworkingConfig{},
"",
)
testutil.ErrorContains(t, err, tc.expectedError)
assert.Check(t, is.ErrorContains(err, tc.expectedError))
}
}

View File

@ -12,7 +12,6 @@ import (
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/poll"
@ -62,7 +61,7 @@ func TestPauseFailsOnWindowsServerContainers(t *testing.T) {
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerPause(ctx, cID)
testutil.ErrorContains(t, err, "cannot pause Windows Server Containers")
assert.Check(t, is.ErrorContains(err, "cannot pause Windows Server Containers"))
}
func TestPauseStopPausedContainer(t *testing.T) {

View File

@ -10,7 +10,6 @@ import (
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/fs"
@ -50,7 +49,7 @@ func TestRemoveContainerWithRemovedVolume(t *testing.T) {
assert.NilError(t, err)
_, _, err = client.ContainerInspectWithRaw(ctx, cID, true)
testutil.ErrorContains(t, err, "No such container")
assert.Check(t, is.ErrorContains(err, "No such container"))
}
// Test case for #2099/#2125
@ -87,7 +86,7 @@ func TestRemoveContainerRunning(t *testing.T) {
cID := container.Run(t, ctx, client)
err := client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{})
testutil.ErrorContains(t, err, "cannot remove a running container")
assert.Check(t, is.ErrorContains(err, "cannot remove a running container"))
}
func TestRemoveContainerForceRemoveRunning(t *testing.T) {
@ -109,5 +108,5 @@ func TestRemoveInvalidContainer(t *testing.T) {
client := request.NewAPIClient(t)
err := client.ContainerRemove(ctx, "unknown", types.ContainerRemoveOptions{})
testutil.ErrorContains(t, err, "No such container")
assert.Check(t, is.ErrorContains(err, "No such container"))
}

View File

@ -11,7 +11,6 @@ import (
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/docker/docker/pkg/stringid"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -89,7 +88,7 @@ func TestRenameRunningContainerAndReuse(t *testing.T) {
assert.Check(t, is.Equal("/"+newName, inspect.Name))
_, err = client.ContainerInspect(ctx, oldName)
testutil.ErrorContains(t, err, "No such container: "+oldName)
assert.Check(t, is.ErrorContains(err, "No such container: "+oldName))
cID = container.Run(t, ctx, client, container.WithName(oldName))
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
@ -109,7 +108,7 @@ func TestRenameInvalidName(t *testing.T) {
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerRename(ctx, oldName, "new:invalid")
testutil.ErrorContains(t, err, "Invalid container name")
assert.Check(t, is.ErrorContains(err, "Invalid container name"))
inspect, err := client.ContainerInspect(ctx, oldName)
assert.NilError(t, err)
@ -179,9 +178,9 @@ func TestRenameContainerWithSameName(t *testing.T) {
poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond))
err := client.ContainerRename(ctx, oldName, oldName)
testutil.ErrorContains(t, err, "Renaming a container with the same name")
assert.Check(t, is.ErrorContains(err, "Renaming a container with the same name"))
err = client.ContainerRename(ctx, cID, oldName)
testutil.ErrorContains(t, err, "Renaming a container with the same name")
assert.Check(t, is.ErrorContains(err, "Renaming a container with the same name"))
}
// Test case for GitHub issue 23973

View File

@ -11,7 +11,6 @@ import (
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
req "github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/poll"
@ -63,5 +62,5 @@ func TestResizeWhenContainerNotStarted(t *testing.T) {
Height: 40,
Width: 40,
})
testutil.ErrorContains(t, err, "is not running")
assert.Check(t, is.ErrorContains(err, "is not running"))
}

View File

@ -8,7 +8,6 @@ import (
containertypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
"github.com/gotestyourself/gotestyourself/poll"
@ -61,5 +60,5 @@ func TestUpdateRestartWithAutoRemove(t *testing.T) {
Name: "always",
},
})
testutil.ErrorContains(t, err, "Restart policy cannot be updated because AutoRemove is enabled for the container")
assert.Check(t, is.ErrorContains(err, "Restart policy cannot be updated because AutoRemove is enabled for the container"))
}

View File

@ -7,7 +7,6 @@ import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
)
@ -56,5 +55,5 @@ func TestRemoveImageOrphaning(t *testing.T) {
// check if the second image has been deleted
_, _, err = client.ImageInspectWithRaw(ctx, commitResp2.ID)
testutil.ErrorContains(t, err, "No such image:")
assert.Check(t, is.ErrorContains(err, "No such image:"))
}

View File

@ -39,7 +39,7 @@ func TestTagInvalidReference(t *testing.T) {
for _, repo := range invalidRepos {
err := client.ImageTag(ctx, "busybox", repo)
testutil.ErrorContains(t, err, "not a valid repository/tag")
assert.Check(t, is.ErrorContains(err, "not a valid repository/tag"))
}
longTag := testutil.GenerateRandomAlphaOnlyString(121)
@ -48,24 +48,24 @@ func TestTagInvalidReference(t *testing.T) {
for _, repotag := range invalidTags {
err := client.ImageTag(ctx, "busybox", repotag)
testutil.ErrorContains(t, err, "not a valid repository/tag")
assert.Check(t, is.ErrorContains(err, "not a valid repository/tag"))
}
// test repository name begin with '-'
err := client.ImageTag(ctx, "busybox:latest", "-busybox:test")
testutil.ErrorContains(t, err, "Error parsing reference")
assert.Check(t, is.ErrorContains(err, "Error parsing reference"))
// test namespace name begin with '-'
err = client.ImageTag(ctx, "busybox:latest", "-test/busybox:test")
testutil.ErrorContains(t, err, "Error parsing reference")
assert.Check(t, is.ErrorContains(err, "Error parsing reference"))
// test index name begin with '-'
err = client.ImageTag(ctx, "busybox:latest", "-index:5000/busybox:test")
testutil.ErrorContains(t, err, "Error parsing reference")
assert.Check(t, is.ErrorContains(err, "Error parsing reference"))
// test setting tag fails
err = client.ImageTag(ctx, "busybox:latest", "sha256:sometag")
testutil.ErrorContains(t, err, "refusing to create an ambiguous tag using digest algorithm as name")
assert.Check(t, is.ErrorContains(err, "refusing to create an ambiguous tag using digest algorithm as name"))
}
// ensure we allow the use of valid tags
@ -132,8 +132,9 @@ func TestTagMatchesDigest(t *testing.T) {
digest := "busybox@sha256:abcdef76720241213f5303bda7704ec4c2ef75613173910a56fb1b6e20251507"
// test setting tag fails
err := client.ImageTag(ctx, "busybox:latest", digest)
testutil.ErrorContains(t, err, "refusing to create a tag with a digest reference")
assert.Check(t, is.ErrorContains(err, "refusing to create a tag with a digest reference"))
// check that no new image matches the digest
_, _, err = client.ImageInspectWithRaw(ctx, digest)
testutil.ErrorContains(t, err, fmt.Sprintf("No such image: %s", digest))
assert.Check(t, is.ErrorContains(err, fmt.Sprintf("No such image: %s", digest)))
}

View File

@ -12,7 +12,6 @@ import (
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/swarm"
"github.com/docker/docker/internal/testutil"
"github.com/docker/docker/pkg/stdcopy"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -148,17 +147,17 @@ func TestSecretsCreateAndDelete(t *testing.T) {
},
Data: []byte("TESTINGDATA"),
})
testutil.ErrorContains(t, err, "already exists")
assert.Check(t, is.ErrorContains(err, "already exists"))
// Ported from original TestSecretsDelete
err = client.SecretRemove(ctx, secretID)
assert.NilError(t, err)
_, _, err = client.SecretInspectWithRaw(ctx, secretID)
testutil.ErrorContains(t, err, "No such secret")
assert.Check(t, is.ErrorContains(err, "No such secret"))
err = client.SecretRemove(ctx, "non-existin")
testutil.ErrorContains(t, err, "No such secret: non-existin")
assert.Check(t, is.ErrorContains(err, "No such secret: non-existin"))
// Ported from original TestSecretsCreteaWithLabels
testName = "test_secret_with_labels"
@ -223,7 +222,7 @@ func TestSecretsUpdate(t *testing.T) {
// this test will produce an error in func UpdateSecret
insp.Spec.Data = []byte("TESTINGDATA2")
err = client.SecretUpdate(ctx, secretID, insp.Version, insp.Spec)
testutil.ErrorContains(t, err, "only updates to Labels are allowed")
assert.Check(t, is.ErrorContains(err, "only updates to Labels are allowed"))
}
func TestTemplatedSecret(t *testing.T) {

View File

@ -12,7 +12,6 @@ import (
volumetypes "github.com/docker/docker/api/types/volume"
"github.com/docker/docker/integration/internal/container"
"github.com/docker/docker/internal/test/request"
"github.com/docker/docker/internal/testutil"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/gotestyourself/gotestyourself/assert"
is "github.com/gotestyourself/gotestyourself/assert/cmp"
@ -63,7 +62,7 @@ func TestVolumesRemove(t *testing.T) {
vname := c.Mounts[0].Name
err = client.VolumeRemove(ctx, vname, false)
testutil.ErrorContains(t, err, "volume is in use")
assert.Check(t, is.ErrorContains(err, "volume is in use"))
err = client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{
Force: true,

View File

@ -2,24 +2,8 @@ package testutil // import "github.com/docker/docker/internal/testutil"
import (
"io"
"github.com/gotestyourself/gotestyourself/assert"
)
type helperT interface {
Helper()
}
// ErrorContains checks that the error is not nil, and contains the expected
// substring.
// Deprecated: use assert.Assert(t, cmp.ErrorContains(err, expected))
func ErrorContains(t assert.TestingT, err error, expectedError string, msgAndArgs ...interface{}) {
if ht, ok := t.(helperT); ok {
ht.Helper()
}
assert.ErrorContains(t, err, expectedError, msgAndArgs...)
}
// DevZero acts like /dev/zero but in an OS-independent fashion.
var DevZero io.Reader = devZero{}

View File

@ -32,7 +32,7 @@ github.com/tonistiigi/fsutil dea3a0da73aee887fc02142d995be764106ac5e2
#get libnetwork packages
# When updating, also update LIBNETWORK_COMMIT in hack/dockerfile/install/proxy accordingly
github.com/docker/libnetwork c15b372ef22125880d378167dde44f4b134e1a77
github.com/docker/libnetwork eb6b2a57955e5c149d47c3973573216e8f8baa09
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec
@ -141,8 +141,8 @@ github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9
github.com/grpc-ecosystem/go-grpc-prometheus 6b7015e65d366bf3f19b2b2a000a831940f0f7e0
# cli
github.com/spf13/cobra v1.5.1 https://github.com/dnephin/cobra.git
github.com/spf13/pflag 9ff6c6923cfffbcd502984b8e0c80539a94968b7
github.com/spf13/cobra v0.0.3
github.com/spf13/pflag v1.0.1
github.com/inconshreveable/mousetrap 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
github.com/Nvveen/Gotty a8b993ba6abdb0e0c12b0125c603323a71c7790c https://github.com/ijc25/Gotty

View File

@ -326,7 +326,6 @@ func (h *Handle) set(ordinal, start, end uint64, any bool, release bool, serial
}
h.Lock() // Acquire the lock back
}
logrus.Debugf("Received set for ordinal %v, start %v, end %v, any %t, release %t, serial:%v curr:%d \n", ordinal, start, end, any, release, serial, h.curr)
if serial {
curr = h.curr
}
@ -466,8 +465,8 @@ func (h *Handle) Unselected() uint64 {
func (h *Handle) String() string {
h.Lock()
defer h.Unlock()
return fmt.Sprintf("App: %s, ID: %s, DBIndex: 0x%x, bits: %d, unselected: %d, sequence: %s",
h.app, h.id, h.dbIndex, h.bits, h.unselected, h.head.toString())
return fmt.Sprintf("App: %s, ID: %s, DBIndex: 0x%x, Bits: %d, Unselected: %d, Sequence: %s Curr:%d",
h.app, h.id, h.dbIndex, h.bits, h.unselected, h.head.toString(), h.curr)
}
// MarshalJSON encodes Handle into json message

View File

@ -1,8 +1,9 @@
package cluster
import (
"context"
"github.com/docker/docker/api/types/network"
"golang.org/x/net/context"
)
const (

View File

@ -438,7 +438,7 @@ func (d *driver) setKeys(keys []*key) error {
d.keys = keys
d.secMap = &encrMap{nodes: map[string][]*spi{}}
d.Unlock()
logrus.Debugf("Initial encryption keys: %v", d.keys)
logrus.Debugf("Initial encryption keys: %v", keys)
return nil
}
@ -458,6 +458,8 @@ func (d *driver) updateKeys(newKey, primary, pruneKey *key) error {
)
d.Lock()
defer d.Unlock()
// add new
if newKey != nil {
d.keys = append(d.keys, newKey)
@ -471,7 +473,6 @@ func (d *driver) updateKeys(newKey, primary, pruneKey *key) error {
delIdx = i
}
}
d.Unlock()
if (newKey != nil && newIdx == -1) ||
(primary != nil && priIdx == -1) ||
@ -480,17 +481,18 @@ func (d *driver) updateKeys(newKey, primary, pruneKey *key) error {
"(newIdx,priIdx,delIdx):(%d, %d, %d)", newIdx, priIdx, delIdx)
}
if priIdx != -1 && priIdx == delIdx {
return types.BadRequestErrorf("attempting to both make a key (index %d) primary and delete it", priIdx)
}
d.secMapWalk(func(rIPs string, spis []*spi) ([]*spi, bool) {
rIP := net.ParseIP(rIPs)
return updateNodeKey(lIP, aIP, rIP, spis, d.keys, newIdx, priIdx, delIdx), false
})
d.Lock()
// swap primary
if priIdx != -1 {
swp := d.keys[0]
d.keys[0] = d.keys[priIdx]
d.keys[priIdx] = swp
d.keys[0], d.keys[priIdx] = d.keys[priIdx], d.keys[0]
}
// prune
if delIdx != -1 {
@ -499,7 +501,6 @@ func (d *driver) updateKeys(newKey, primary, pruneKey *key) error {
}
d.keys = append(d.keys[:delIdx], d.keys[delIdx+1:]...)
}
d.Unlock()
logrus.Debugf("Updated: %v", d.keys)

View File

@ -203,6 +203,12 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
n.subnets = append(n.subnets, s)
}
d.Lock()
defer d.Unlock()
if d.networks[n.id] != nil {
return fmt.Errorf("attempt to create overlay network %v that already exists", n.id)
}
if err := n.writeToStore(); err != nil {
return fmt.Errorf("failed to update data store for network %v: %v", n.id, err)
}
@ -217,11 +223,13 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
if nInfo != nil {
if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
// XXX Undo writeToStore? No method to so. Why?
return err
}
}
d.addNetwork(n)
d.networks[id] = n
return nil
}
@ -235,7 +243,15 @@ func (d *driver) DeleteNetwork(nid string) error {
return err
}
n := d.network(nid)
d.Lock()
defer d.Unlock()
// This is similar to d.network(), but we need to keep holding the lock
// until we are done removing this network.
n, ok := d.networks[nid]
if !ok {
n = d.restoreNetworkFromStore(nid)
}
if n == nil {
return fmt.Errorf("could not find network with id %s", nid)
}
@ -255,7 +271,7 @@ func (d *driver) DeleteNetwork(nid string) error {
}
// flush the peerDB entries
d.peerFlush(nid)
d.deleteNetwork(nid)
delete(d.networks, nid)
vnis, err := n.releaseVxlanID()
if err != nil {
@ -805,32 +821,25 @@ func (n *network) watchMiss(nlSock *nl.NetlinkSocket, nsPath string) {
}
}
func (d *driver) addNetwork(n *network) {
d.Lock()
d.networks[n.id] = n
d.Unlock()
}
func (d *driver) deleteNetwork(nid string) {
d.Lock()
delete(d.networks, nid)
d.Unlock()
// Restore a network from the store to the driver if it is present.
// Must be called with the driver locked!
func (d *driver) restoreNetworkFromStore(nid string) *network {
n := d.getNetworkFromStore(nid)
if n != nil {
n.driver = d
n.endpoints = endpointTable{}
n.once = &sync.Once{}
d.networks[nid] = n
}
return n
}
func (d *driver) network(nid string) *network {
d.Lock()
defer d.Unlock()
n, ok := d.networks[nid]
d.Unlock()
if !ok {
n = d.getNetworkFromStore(nid)
if n != nil {
n.driver = d
n.endpoints = endpointTable{}
n.once = &sync.Once{}
d.Lock()
d.networks[nid] = n
d.Unlock()
}
n = d.restoreNetworkFromStore(nid)
}
return n

View File

@ -125,8 +125,12 @@ func (d *driver) NetworkAllocate(id string, option map[string]string, ipV4Data,
opts[netlabel.OverlayVxlanIDList] = val
d.Lock()
defer d.Unlock()
if _, ok := d.networks[id]; ok {
n.releaseVxlanID()
return nil, fmt.Errorf("network %s already exists", id)
}
d.networks[id] = n
d.Unlock()
return opts, nil
}
@ -137,8 +141,8 @@ func (d *driver) NetworkFree(id string) error {
}
d.Lock()
defer d.Unlock()
n, ok := d.networks[id]
d.Unlock()
if !ok {
return fmt.Errorf("overlay network with id %s not found", id)
@ -147,9 +151,7 @@ func (d *driver) NetworkFree(id string) error {
// Release all vxlan IDs in one shot.
n.releaseVxlanID()
d.Lock()
delete(d.networks, id)
d.Unlock()
return nil
}

View File

@ -526,6 +526,7 @@ func (a *Allocator) ReleaseAddress(poolID string, address net.IP) error {
return types.InternalErrorf("could not find bitmask in datastore for %s on address %v release from pool %s: %v",
k.String(), address, poolID, err)
}
defer logrus.Debugf("Released address PoolID:%s, Address:%v Sequence:%s", poolID, address, bm.String())
return bm.Unset(ipToUint64(h))
}
@ -537,6 +538,7 @@ func (a *Allocator) getAddress(nw *net.IPNet, bitmask *bitseq.Handle, prefAddres
base *net.IPNet
)
logrus.Debugf("Request address PoolID:%v %s Serial:%v PrefAddress:%v ", nw, bitmask.String(), serial, prefAddress)
base = types.GetIPNetCopy(nw)
if bitmask.Unselected() <= 0 {

View File

@ -9,6 +9,7 @@ import (
"strconv"
"strings"
"sync"
"time"
"github.com/sirupsen/logrus"
)
@ -45,7 +46,7 @@ var (
iptablesPath string
supportsXlock = false
supportsCOpt = false
xLockWaitMsg = "Another app is currently holding the xtables lock; waiting"
xLockWaitMsg = "Another app is currently holding the xtables lock"
// used to lock iptables commands if xtables lock is not supported
bestEffortLock sync.Mutex
// ErrIptablesNotFound is returned when the rule is not found.
@ -423,12 +424,32 @@ func existsRaw(table Table, chain string, rule ...string) bool {
return strings.Contains(string(existingRules), ruleString)
}
// Maximum duration that an iptables operation can take
// before flagging a warning.
const opWarnTime = 2 * time.Second
func filterOutput(start time.Time, output []byte, args ...string) []byte {
// Flag operations that have taken a long time to complete
opTime := time.Since(start)
if opTime > opWarnTime {
logrus.Warnf("xtables contention detected while running [%s]: Waited for %.2f seconds and received %q", strings.Join(args, " "), float64(opTime)/float64(time.Second), string(output))
}
// ignore iptables' message about xtables lock:
// it is a warning, not an error.
if strings.Contains(string(output), xLockWaitMsg) {
output = []byte("")
}
// Put further filters here if desired
return output
}
// Raw calls 'iptables' system command, passing supplied arguments.
func Raw(args ...string) ([]byte, error) {
if firewalldRunning {
startTime := time.Now()
output, err := Passthrough(Iptables, args...)
if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") {
return output, err
return filterOutput(startTime, output, args...), err
}
}
return raw(args...)
@ -447,17 +468,13 @@ func raw(args ...string) ([]byte, error) {
logrus.Debugf("%s, %v", iptablesPath, args)
startTime := time.Now()
output, err := exec.Command(iptablesPath, args...).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("iptables failed: iptables %v: %s (%s)", strings.Join(args, " "), output, err)
}
// ignore iptables' message about xtables lock
if strings.Contains(string(output), xLockWaitMsg) {
output = []byte("")
}
return output, err
return filterOutput(startTime, output, args...), err
}
// RawCombinedOutput inernally calls the Raw function and returns a non nil

View File

@ -2,6 +2,7 @@ package networkdb
import (
"bytes"
"context"
"crypto/rand"
"encoding/hex"
"fmt"
@ -17,10 +18,12 @@ import (
)
const (
reapPeriod = 5 * time.Second
retryInterval = 1 * time.Second
nodeReapInterval = 24 * time.Hour
nodeReapPeriod = 2 * time.Hour
reapPeriod = 5 * time.Second
rejoinClusterDuration = 10 * time.Second
rejoinInterval = 60 * time.Second
retryInterval = 1 * time.Second
nodeReapInterval = 24 * time.Hour
nodeReapPeriod = 2 * time.Hour
)
type logWriter struct{}
@ -154,7 +157,7 @@ func (nDB *NetworkDB) clusterInit() error {
return fmt.Errorf("failed to create memberlist: %v", err)
}
nDB.stopCh = make(chan struct{})
nDB.ctx, nDB.cancelCtx = context.WithCancel(context.Background())
nDB.memberlist = mlist
for _, trigger := range []struct {
@ -166,16 +169,17 @@ func (nDB *NetworkDB) clusterInit() error {
{config.PushPullInterval, nDB.bulkSyncTables},
{retryInterval, nDB.reconnectNode},
{nodeReapPeriod, nDB.reapDeadNode},
{rejoinInterval, nDB.rejoinClusterBootStrap},
} {
t := time.NewTicker(trigger.interval)
go nDB.triggerFunc(trigger.interval, t.C, nDB.stopCh, trigger.fn)
go nDB.triggerFunc(trigger.interval, t.C, trigger.fn)
nDB.tickers = append(nDB.tickers, t)
}
return nil
}
func (nDB *NetworkDB) retryJoin(members []string, stop <-chan struct{}) {
func (nDB *NetworkDB) retryJoin(ctx context.Context, members []string) {
t := time.NewTicker(retryInterval)
defer t.Stop()
@ -191,7 +195,7 @@ func (nDB *NetworkDB) retryJoin(members []string, stop <-chan struct{}) {
continue
}
return
case <-stop:
case <-ctx.Done():
return
}
}
@ -202,8 +206,8 @@ func (nDB *NetworkDB) clusterJoin(members []string) error {
mlist := nDB.memberlist
if _, err := mlist.Join(members); err != nil {
// In case of failure, keep retrying join until it succeeds or the cluster is shutdown.
go nDB.retryJoin(members, nDB.stopCh)
// In case of failure, we no longer need to explicitly call retryJoin.
// rejoinClusterBootStrap, which runs every minute, will retryJoin for 10sec
return fmt.Errorf("could not join node to memberlist: %v", err)
}
@ -225,7 +229,8 @@ func (nDB *NetworkDB) clusterLeave() error {
return err
}
close(nDB.stopCh)
// cancel the context
nDB.cancelCtx()
for _, t := range nDB.tickers {
t.Stop()
@ -234,19 +239,19 @@ func (nDB *NetworkDB) clusterLeave() error {
return mlist.Shutdown()
}
func (nDB *NetworkDB) triggerFunc(stagger time.Duration, C <-chan time.Time, stop <-chan struct{}, f func()) {
func (nDB *NetworkDB) triggerFunc(stagger time.Duration, C <-chan time.Time, f func()) {
// Use a random stagger to avoid syncronizing
randStagger := time.Duration(uint64(rnd.Int63()) % uint64(stagger))
select {
case <-time.After(randStagger):
case <-stop:
case <-nDB.ctx.Done():
return
}
for {
select {
case <-C:
f()
case <-stop:
case <-nDB.ctx.Done():
return
}
}
@ -270,6 +275,35 @@ func (nDB *NetworkDB) reapDeadNode() {
}
}
// rejoinClusterBootStrap is called periodically to check if all bootStrap nodes are active in the cluster,
// if not, call the cluster join to merge 2 separate clusters that are formed when all managers
// stopped/started at the same time
func (nDB *NetworkDB) rejoinClusterBootStrap() {
nDB.RLock()
if len(nDB.bootStrapIP) == 0 {
nDB.RUnlock()
return
}
bootStrapIPs := make([]string, 0, len(nDB.bootStrapIP))
for _, bootIP := range nDB.bootStrapIP {
for _, node := range nDB.nodes {
if node.Addr.Equal(bootIP) {
// One of the bootstrap nodes is part of the cluster, return
nDB.RUnlock()
return
}
}
bootStrapIPs = append(bootStrapIPs, bootIP.String())
}
nDB.RUnlock()
// None of the bootStrap nodes are in the cluster, call memberlist join
logrus.Debugf("rejoinClusterBootStrap, calling cluster join with bootStrap %v", bootStrapIPs)
ctx, cancel := context.WithTimeout(nDB.ctx, rejoinClusterDuration)
defer cancel()
nDB.retryJoin(ctx, bootStrapIPs)
}
func (nDB *NetworkDB) reconnectNode() {
nDB.RLock()
if len(nDB.failedNodes) == 0 {

View File

@ -38,16 +38,11 @@ func (nDB *NetworkDB) handleNodeEvent(nEvent *NodeEvent) bool {
// If we are here means that the event is fresher and the node is known. Update the laport time
n.ltime = nEvent.LTime
// If it is a node leave event for a manager and this is the only manager we
// know of we want the reconnect logic to kick in. In a single manager
// cluster manager's gossip can't be bootstrapped unless some other node
// connects to it.
if len(nDB.bootStrapIP) == 1 && nEvent.Type == NodeEventTypeLeave {
for _, ip := range nDB.bootStrapIP {
if ip.Equal(n.Addr) {
return true
}
}
// If the node is not known from memberlist we cannot process save any state of it else if it actually
// dies we won't receive any notification and we will remain stuck with it
if _, ok := nDB.nodes[nEvent.NodeName]; !ok {
logrus.Error("node: %s is unknown to memberlist", nEvent.NodeName)
return false
}
switch nEvent.Type {

View File

@ -3,6 +3,7 @@ package networkdb
//go:generate protoc -I.:../vendor/github.com/gogo/protobuf --gogo_out=import_path=github.com/docker/libnetwork/networkdb,Mgogoproto/gogo.proto=github.com/gogo/protobuf/gogoproto:. networkdb.proto
import (
"context"
"fmt"
"net"
"os"
@ -77,9 +78,10 @@ type NetworkDB struct {
// Broadcast queue for node event gossip.
nodeBroadcasts *memberlist.TransmitLimitedQueue
// A central stop channel to stop all go routines running on
// A central context to stop all go routines running on
// behalf of the NetworkDB instance.
stopCh chan struct{}
ctx context.Context
cancelCtx context.CancelFunc
// A central broadcaster for all local watchers watching table
// events.

View File

@ -48,7 +48,7 @@ github.com/ugorji/go f1f1a805ed361a0e078bb537e4ea78cd37dcf065
github.com/vishvananda/netlink b2de5d10e38ecce8607e6b438b6d174f389a004e
github.com/vishvananda/netns 604eaf189ee867d8c147fafc28def2394e878d25
golang.org/x/crypto 558b6879de74bc843225cde5686419267ff707ca
golang.org/x/net 7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
golang.org/x/sys 07c182904dbd53199946ba614a412c61d3c548f5
golang.org/x/sync fd80eb99c8f653c847d294a001bdf2a3a6f768f5
github.com/pkg/errors 839d9e913e063e28dfd0e6c7b7512793e0a48be9

File diff suppressed because it is too large Load Diff

View File

@ -16,14 +16,14 @@ func legacyArgs(cmd *Command, args []string) error {
return nil
}
// root command with subcommands, do subcommand checking
// root command with subcommands, do subcommand checking.
if !cmd.HasParent() && len(args) > 0 {
return fmt.Errorf("unknown command %q for %q%s", args[0], cmd.CommandPath(), cmd.findSuggestions(args[0]))
}
return nil
}
// NoArgs returns an error if any args are included
// NoArgs returns an error if any args are included.
func NoArgs(cmd *Command, args []string) error {
if len(args) > 0 {
return fmt.Errorf("unknown command %q for %q", args[0], cmd.CommandPath())
@ -31,7 +31,7 @@ func NoArgs(cmd *Command, args []string) error {
return nil
}
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs
// OnlyValidArgs returns an error if any args are not in the list of ValidArgs.
func OnlyValidArgs(cmd *Command, args []string) error {
if len(cmd.ValidArgs) > 0 {
for _, v := range args {
@ -43,21 +43,12 @@ func OnlyValidArgs(cmd *Command, args []string) error {
return nil
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// ArbitraryArgs never returns an error
// ArbitraryArgs never returns an error.
func ArbitraryArgs(cmd *Command, args []string) error {
return nil
}
// MinimumNArgs returns an error if there is not at least N args
// MinimumNArgs returns an error if there is not at least N args.
func MinimumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) < n {
@ -67,7 +58,7 @@ func MinimumNArgs(n int) PositionalArgs {
}
}
// MaximumNArgs returns an error if there are more than N args
// MaximumNArgs returns an error if there are more than N args.
func MaximumNArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) > n {
@ -77,7 +68,7 @@ func MaximumNArgs(n int) PositionalArgs {
}
}
// ExactArgs returns an error if there are not exactly n args
// ExactArgs returns an error if there are not exactly n args.
func ExactArgs(n int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) != n {
@ -87,7 +78,7 @@ func ExactArgs(n int) PositionalArgs {
}
}
// RangeArgs returns an error if the number of args is not within the expected range
// RangeArgs returns an error if the number of args is not within the expected range.
func RangeArgs(min int, max int) PositionalArgs {
return func(cmd *Command, args []string) error {
if len(args) < min || len(args) > max {

View File

@ -1,6 +1,7 @@
package cobra
import (
"bytes"
"fmt"
"io"
"os"
@ -10,20 +11,18 @@ import (
"github.com/spf13/pflag"
)
// Annotations for Bash completion.
const (
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extentions"
BashCompFilenameExt = "cobra_annotation_bash_completion_filename_extensions"
BashCompCustom = "cobra_annotation_bash_completion_custom"
BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
BashCompSubdirsInDir = "cobra_annotation_bash_completion_subdirs_in_dir"
)
func preamble(out io.Writer, name string) error {
_, err := fmt.Fprintf(out, "# bash completion for %-36s -*- shell-script -*-\n", name)
if err != nil {
return err
}
_, err = fmt.Fprint(out, `
__debug()
func writePreamble(buf *bytes.Buffer, name string) {
buf.WriteString(fmt.Sprintf("# bash completion for %-36s -*- shell-script -*-\n", name))
buf.WriteString(fmt.Sprintf(`
__%[1]s_debug()
{
if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
@ -32,13 +31,13 @@ __debug()
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
__my_init_completion()
__%[1]s_init_completion()
{
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
__index_of_word()
__%[1]s_index_of_word()
{
local w word=$1
shift
@ -50,7 +49,7 @@ __index_of_word()
index=-1
}
__contains_word()
__%[1]s_contains_word()
{
local w word=$1; shift
for w in "$@"; do
@ -59,9 +58,9 @@ __contains_word()
return 1
}
__handle_reply()
__%[1]s_handle_reply()
{
__debug "${FUNCNAME[0]}"
__%[1]s_debug "${FUNCNAME[0]}"
case $cur in
-*)
if [[ $(type -t compopt) = "builtin" ]]; then
@ -86,14 +85,14 @@ __handle_reply()
local index flag
flag="${cur%%=*}"
__index_of_word "${flag}" "${flags_with_completion[@]}"
__%[1]s_index_of_word "${flag}" "${flags_with_completion[@]}"
COMPREPLY=()
if [[ ${index} -ge 0 ]]; then
COMPREPLY=()
PREFIX=""
cur="${cur#*=}"
${flags_completion[${index}]}
if [ -n "${ZSH_VERSION}" ]; then
# zfs completion needs --flag= prefix
# zsh completion needs --flag= prefix
eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )"
fi
fi
@ -104,7 +103,7 @@ __handle_reply()
# check if we are handling a flag with special work handling
local index
__index_of_word "${prev}" "${flags_with_completion[@]}"
__%[1]s_index_of_word "${prev}" "${flags_with_completion[@]}"
if [[ ${index} -ge 0 ]]; then
${flags_completion[${index}]}
return
@ -133,25 +132,34 @@ __handle_reply()
declare -F __custom_func >/dev/null && __custom_func
fi
__ltrim_colon_completions "$cur"
# available in bash-completion >= 2, not always present on macOS
if declare -F __ltrim_colon_completions >/dev/null; then
__ltrim_colon_completions "$cur"
fi
# If there is only 1 completion and it is a flag with an = it will be completed
# but we don't want a space after the =
if [[ "${#COMPREPLY[@]}" -eq "1" ]] && [[ $(type -t compopt) = "builtin" ]] && [[ "${COMPREPLY[0]}" == --*= ]]; then
compopt -o nospace
fi
}
# The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag()
__%[1]s_handle_filename_extension_flag()
{
local ext="$1"
_filedir "@(${ext})"
}
__handle_subdirs_in_dir_flag()
__%[1]s_handle_subdirs_in_dir_flag()
{
local dir="$1"
pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
}
__handle_flag()
__%[1]s_handle_flag()
{
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
# if a command required a flag, and we found it, unset must_have_one_flag()
local flagname=${words[c]}
@ -162,27 +170,30 @@ __handle_flag()
flagname=${flagname%%=*} # strip everything after the =
flagname="${flagname}=" # but put the = back
fi
__debug "${FUNCNAME[0]}: looking for ${flagname}"
if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
__%[1]s_debug "${FUNCNAME[0]}: looking for ${flagname}"
if __%[1]s_contains_word "${flagname}" "${must_have_one_flag[@]}"; then
must_have_one_flag=()
fi
# if you set a flag which only applies to this command, don't show subcommands
if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
if __%[1]s_contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then
commands=()
fi
# keep flag value with flagname as flaghash
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
elif [ -n "${words[ $((c+1)) ]}" ] ; then
flaghash[${flagname}]=${words[ $((c+1)) ]}
else
flaghash[${flagname}]="true" # pad "true" for bool flag
# flaghash variable is an associative array which is only supported in bash > 3.
if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
if [ -n "${flagvalue}" ] ; then
flaghash[${flagname}]=${flagvalue}
elif [ -n "${words[ $((c+1)) ]}" ] ; then
flaghash[${flagname}]=${words[ $((c+1)) ]}
else
flaghash[${flagname}]="true" # pad "true" for bool flag
fi
fi
# skip the argument to a two word flag
if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then
c=$((c+1))
# if we are looking for a flags value, don't show commands
if [[ $c -eq $cword ]]; then
@ -194,13 +205,13 @@ __handle_flag()
}
__handle_noun()
__%[1]s_handle_noun()
{
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
if __%[1]s_contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
must_have_one_noun=()
elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then
elif __%[1]s_contains_word "${words[c]}" "${noun_aliases[@]}"; then
must_have_one_noun=()
fi
@ -208,61 +219,66 @@ __handle_noun()
c=$((c+1))
}
__handle_command()
__%[1]s_handle_command()
{
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
local next_command
if [[ -n ${last_command} ]]; then
next_command="_${last_command}_${words[c]//:/__}"
else
if [[ $c -eq 0 ]]; then
next_command="_$(basename "${words[c]//:/__}")"
next_command="_%[1]s_root_command"
else
next_command="_${words[c]//:/__}"
fi
fi
c=$((c+1))
__debug "${FUNCNAME[0]}: looking for ${next_command}"
declare -F $next_command >/dev/null && $next_command
__%[1]s_debug "${FUNCNAME[0]}: looking for ${next_command}"
declare -F "$next_command" >/dev/null && $next_command
}
__handle_word()
__%[1]s_handle_word()
{
if [[ $c -ge $cword ]]; then
__handle_reply
__%[1]s_handle_reply
return
fi
__debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
__%[1]s_debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}"
if [[ "${words[c]}" == -* ]]; then
__handle_flag
elif __contains_word "${words[c]}" "${commands[@]}"; then
__handle_command
elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then
__handle_command
__%[1]s_handle_flag
elif __%[1]s_contains_word "${words[c]}" "${commands[@]}"; then
__%[1]s_handle_command
elif [[ $c -eq 0 ]]; then
__%[1]s_handle_command
elif __%[1]s_contains_word "${words[c]}" "${command_aliases[@]}"; then
# aliashash variable is an associative array which is only supported in bash > 3.
if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then
words[c]=${aliashash[${words[c]}]}
__%[1]s_handle_command
else
__%[1]s_handle_noun
fi
else
__handle_noun
__%[1]s_handle_noun
fi
__handle_word
__%[1]s_handle_word
}
`)
return err
`, name))
}
func postscript(w io.Writer, name string) error {
func writePostscript(buf *bytes.Buffer, name string) {
name = strings.Replace(name, ":", "__", -1)
_, err := fmt.Fprintf(w, "__start_%s()\n", name)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, `{
buf.WriteString(fmt.Sprintf("__start_%s()\n", name))
buf.WriteString(fmt.Sprintf(`{
local cur prev words cword
declare -A flaghash 2>/dev/null || :
declare -A aliashash 2>/dev/null || :
if declare -F _init_completion >/dev/null 2>&1; then
_init_completion -s || return
else
__my_init_completion -n "=" || return
__%[1]s_init_completion -n "=" || return
fi
local c=0
@ -271,350 +287,288 @@ func postscript(w io.Writer, name string) error {
local local_nonpersistent_flags=()
local flags_with_completion=()
local flags_completion=()
local commands=("%s")
local commands=("%[1]s")
local must_have_one_flag=()
local must_have_one_noun=()
local last_command
local nouns=()
__handle_word
__%[1]s_handle_word
}
`, name)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, `if [[ $(type -t compopt) = "builtin" ]]; then
`, name))
buf.WriteString(fmt.Sprintf(`if [[ $(type -t compopt) = "builtin" ]]; then
complete -o default -F __start_%s %s
else
complete -o default -o nospace -F __start_%s %s
fi
`, name, name, name, name)
if err != nil {
return err
}
_, err = fmt.Fprintf(w, "# ex: ts=4 sw=4 et filetype=sh\n")
return err
`, name, name, name, name))
buf.WriteString("# ex: ts=4 sw=4 et filetype=sh\n")
}
func writeCommands(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " commands=()\n"); err != nil {
return err
}
func writeCommands(buf *bytes.Buffer, cmd *Command) {
buf.WriteString(" commands=()\n")
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
if _, err := fmt.Fprintf(w, " commands+=(%q)\n", c.Name()); err != nil {
return err
}
buf.WriteString(fmt.Sprintf(" commands+=(%q)\n", c.Name()))
writeCmdAliases(buf, c)
}
_, err := fmt.Fprintf(w, "\n")
return err
buf.WriteString("\n")
}
func writeFlagHandler(name string, annotations map[string][]string, w io.Writer) error {
func writeFlagHandler(buf *bytes.Buffer, name string, annotations map[string][]string, cmd *Command) {
for key, value := range annotations {
switch key {
case BashCompFilenameExt:
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
if err != nil {
return err
}
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
var ext string
if len(value) > 0 {
ext := "__handle_filename_extension_flag " + strings.Join(value, "|")
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
ext = fmt.Sprintf("__%s_handle_filename_extension_flag ", cmd.Root().Name()) + strings.Join(value, "|")
} else {
ext := "_filedir"
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
}
if err != nil {
return err
ext = "_filedir"
}
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
case BashCompCustom:
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
if err != nil {
return err
}
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
if len(value) > 0 {
handlers := strings.Join(value, "; ")
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", handlers)
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", handlers))
} else {
_, err = fmt.Fprintf(w, " flags_completion+=(:)\n")
}
if err != nil {
return err
buf.WriteString(" flags_completion+=(:)\n")
}
case BashCompSubdirsInDir:
_, err := fmt.Fprintf(w, " flags_with_completion+=(%q)\n", name)
buf.WriteString(fmt.Sprintf(" flags_with_completion+=(%q)\n", name))
var ext string
if len(value) == 1 {
ext := "__handle_subdirs_in_dir_flag " + value[0]
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
ext = fmt.Sprintf("__%s_handle_subdirs_in_dir_flag ", cmd.Root().Name()) + value[0]
} else {
ext := "_filedir -d"
_, err = fmt.Fprintf(w, " flags_completion+=(%q)\n", ext)
}
if err != nil {
return err
ext = "_filedir -d"
}
buf.WriteString(fmt.Sprintf(" flags_completion+=(%q)\n", ext))
}
}
return nil
}
func writeShortFlag(flag *pflag.Flag, w io.Writer) error {
b := (len(flag.NoOptDefVal) > 0)
func writeShortFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Shorthand
format := " "
if !b {
if len(flag.NoOptDefVal) == 0 {
format += "two_word_"
}
format += "flags+=(\"-%s\")\n"
if _, err := fmt.Fprintf(w, format, name); err != nil {
return err
}
return writeFlagHandler("-"+name, flag.Annotations, w)
buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "-"+name, flag.Annotations, cmd)
}
func writeFlag(flag *pflag.Flag, w io.Writer) error {
b := (len(flag.NoOptDefVal) > 0)
func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) {
name := flag.Name
format := " flags+=(\"--%s"
if !b {
if len(flag.NoOptDefVal) == 0 {
format += "="
}
format += "\")\n"
if _, err := fmt.Fprintf(w, format, name); err != nil {
return err
}
return writeFlagHandler("--"+name, flag.Annotations, w)
buf.WriteString(fmt.Sprintf(format, name))
writeFlagHandler(buf, "--"+name, flag.Annotations, cmd)
}
func writeLocalNonPersistentFlag(flag *pflag.Flag, w io.Writer) error {
b := (len(flag.NoOptDefVal) > 0)
func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) {
name := flag.Name
format := " local_nonpersistent_flags+=(\"--%s"
if !b {
if len(flag.NoOptDefVal) == 0 {
format += "="
}
format += "\")\n"
if _, err := fmt.Fprintf(w, format, name); err != nil {
return err
}
return nil
buf.WriteString(fmt.Sprintf(format, name))
}
func writeFlags(cmd *Command, w io.Writer) error {
_, err := fmt.Fprintf(w, ` flags=()
func writeFlags(buf *bytes.Buffer, cmd *Command) {
buf.WriteString(` flags=()
two_word_flags=()
local_nonpersistent_flags=()
flags_with_completion=()
flags_completion=()
`)
if err != nil {
return err
}
localNonPersistentFlags := cmd.LocalNonPersistentFlags()
var visitErr error
cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
if err := writeFlag(flag, w); err != nil {
visitErr = err
if nonCompletableFlag(flag) {
return
}
writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 {
if err := writeShortFlag(flag, w); err != nil {
visitErr = err
return
}
writeShortFlag(buf, flag, cmd)
}
if localNonPersistentFlags.Lookup(flag.Name) != nil {
if err := writeLocalNonPersistentFlag(flag, w); err != nil {
visitErr = err
return
}
writeLocalNonPersistentFlag(buf, flag)
}
})
if visitErr != nil {
return visitErr
}
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
if err := writeFlag(flag, w); err != nil {
visitErr = err
if nonCompletableFlag(flag) {
return
}
writeFlag(buf, flag, cmd)
if len(flag.Shorthand) > 0 {
if err := writeShortFlag(flag, w); err != nil {
visitErr = err
return
}
writeShortFlag(buf, flag, cmd)
}
})
if visitErr != nil {
return visitErr
}
_, err = fmt.Fprintf(w, "\n")
return err
buf.WriteString("\n")
}
func writeRequiredFlag(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " must_have_one_flag=()\n"); err != nil {
return err
}
func writeRequiredFlag(buf *bytes.Buffer, cmd *Command) {
buf.WriteString(" must_have_one_flag=()\n")
flags := cmd.NonInheritedFlags()
var visitErr error
flags.VisitAll(func(flag *pflag.Flag) {
if nonCompletableFlag(flag) {
return
}
for key := range flag.Annotations {
switch key {
case BashCompOneRequiredFlag:
format := " must_have_one_flag+=(\"--%s"
b := (flag.Value.Type() == "bool")
if !b {
if flag.Value.Type() != "bool" {
format += "="
}
format += "\")\n"
if _, err := fmt.Fprintf(w, format, flag.Name); err != nil {
visitErr = err
return
}
buf.WriteString(fmt.Sprintf(format, flag.Name))
if len(flag.Shorthand) > 0 {
if _, err := fmt.Fprintf(w, " must_have_one_flag+=(\"-%s\")\n", flag.Shorthand); err != nil {
visitErr = err
return
}
buf.WriteString(fmt.Sprintf(" must_have_one_flag+=(\"-%s\")\n", flag.Shorthand))
}
}
}
})
return visitErr
}
func writeRequiredNouns(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " must_have_one_noun=()\n"); err != nil {
return err
}
func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) {
buf.WriteString(" must_have_one_noun=()\n")
sort.Sort(sort.StringSlice(cmd.ValidArgs))
for _, value := range cmd.ValidArgs {
if _, err := fmt.Fprintf(w, " must_have_one_noun+=(%q)\n", value); err != nil {
return err
}
buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value))
}
return nil
}
func writeArgAliases(cmd *Command, w io.Writer) error {
if _, err := fmt.Fprintf(w, " noun_aliases=()\n"); err != nil {
return err
func writeCmdAliases(buf *bytes.Buffer, cmd *Command) {
if len(cmd.Aliases) == 0 {
return
}
sort.Sort(sort.StringSlice(cmd.Aliases))
buf.WriteString(fmt.Sprint(` if [[ -z "${BASH_VERSION}" || "${BASH_VERSINFO[0]}" -gt 3 ]]; then`, "\n"))
for _, value := range cmd.Aliases {
buf.WriteString(fmt.Sprintf(" command_aliases+=(%q)\n", value))
buf.WriteString(fmt.Sprintf(" aliashash[%q]=%q\n", value, cmd.Name()))
}
buf.WriteString(` fi`)
buf.WriteString("\n")
}
func writeArgAliases(buf *bytes.Buffer, cmd *Command) {
buf.WriteString(" noun_aliases=()\n")
sort.Sort(sort.StringSlice(cmd.ArgAliases))
for _, value := range cmd.ArgAliases {
if _, err := fmt.Fprintf(w, " noun_aliases+=(%q)\n", value); err != nil {
return err
}
buf.WriteString(fmt.Sprintf(" noun_aliases+=(%q)\n", value))
}
return nil
}
func gen(cmd *Command, w io.Writer) error {
func gen(buf *bytes.Buffer, cmd *Command) {
for _, c := range cmd.Commands() {
if !c.IsAvailableCommand() || c == cmd.helpCommand {
continue
}
if err := gen(c, w); err != nil {
return err
}
gen(buf, c)
}
commandName := cmd.CommandPath()
commandName = strings.Replace(commandName, " ", "_", -1)
commandName = strings.Replace(commandName, ":", "__", -1)
if _, err := fmt.Fprintf(w, "_%s()\n{\n", commandName); err != nil {
return err
if cmd.Root() == cmd {
buf.WriteString(fmt.Sprintf("_%s_root_command()\n{\n", commandName))
} else {
buf.WriteString(fmt.Sprintf("_%s()\n{\n", commandName))
}
if _, err := fmt.Fprintf(w, " last_command=%q\n", commandName); err != nil {
return err
}
if err := writeCommands(cmd, w); err != nil {
return err
}
if err := writeFlags(cmd, w); err != nil {
return err
}
if err := writeRequiredFlag(cmd, w); err != nil {
return err
}
if err := writeRequiredNouns(cmd, w); err != nil {
return err
}
if err := writeArgAliases(cmd, w); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "}\n\n"); err != nil {
return err
}
return nil
buf.WriteString(fmt.Sprintf(" last_command=%q\n", commandName))
buf.WriteString("\n")
buf.WriteString(" command_aliases=()\n")
buf.WriteString("\n")
writeCommands(buf, cmd)
writeFlags(buf, cmd)
writeRequiredFlag(buf, cmd)
writeRequiredNouns(buf, cmd)
writeArgAliases(buf, cmd)
buf.WriteString("}\n\n")
}
func (cmd *Command) GenBashCompletion(w io.Writer) error {
if err := preamble(w, cmd.Name()); err != nil {
return err
// GenBashCompletion generates bash completion file and writes to the passed writer.
func (c *Command) GenBashCompletion(w io.Writer) error {
buf := new(bytes.Buffer)
writePreamble(buf, c.Name())
if len(c.BashCompletionFunction) > 0 {
buf.WriteString(c.BashCompletionFunction + "\n")
}
if len(cmd.BashCompletionFunction) > 0 {
if _, err := fmt.Fprintf(w, "%s\n", cmd.BashCompletionFunction); err != nil {
return err
}
}
if err := gen(cmd, w); err != nil {
return err
}
return postscript(w, cmd.Name())
gen(buf, c)
writePostscript(buf, c.Name())
_, err := buf.WriteTo(w)
return err
}
func (cmd *Command) GenBashCompletionFile(filename string) error {
func nonCompletableFlag(flag *pflag.Flag) bool {
return flag.Hidden || len(flag.Deprecated) > 0
}
// GenBashCompletionFile generates bash completion file.
func (c *Command) GenBashCompletionFile(filename string) error {
outFile, err := os.Create(filename)
if err != nil {
return err
}
defer outFile.Close()
return cmd.GenBashCompletion(outFile)
return c.GenBashCompletion(outFile)
}
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
func (cmd *Command) MarkFlagRequired(name string) error {
return MarkFlagRequired(cmd.Flags(), name)
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
// and causes your command to report an error if invoked without the flag.
func (c *Command) MarkFlagRequired(name string) error {
return MarkFlagRequired(c.Flags(), name)
}
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag, if it exists.
func (cmd *Command) MarkPersistentFlagRequired(name string) error {
return MarkFlagRequired(cmd.PersistentFlags(), name)
// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists,
// and causes your command to report an error if invoked without the flag.
func (c *Command) MarkPersistentFlagRequired(name string) error {
return MarkFlagRequired(c.PersistentFlags(), name)
}
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists,
// and causes your command to report an error if invoked without the flag.
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
}
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(cmd.Flags(), name, extensions...)
func (c *Command) MarkFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(c.Flags(), name, extensions...)
}
// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists.
// Generated bash autocompletion will call the bash function f for the flag.
func (cmd *Command) MarkFlagCustom(name string, f string) error {
return MarkFlagCustom(cmd.Flags(), name, f)
func (c *Command) MarkFlagCustom(name string, f string) error {
return MarkFlagCustom(c.Flags(), name, f)
}
// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(cmd.PersistentFlags(), name, extensions...)
func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error {
return MarkFlagFilename(c.PersistentFlags(), name, extensions...)
}
// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.

View File

@ -27,48 +27,60 @@ import (
)
var templateFuncs = template.FuncMap{
"trim": strings.TrimSpace,
"trimRightSpace": trimRightSpace,
"appendIfNotPresent": appendIfNotPresent,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
"trim": strings.TrimSpace,
"trimRightSpace": trimRightSpace,
"trimTrailingWhitespaces": trimRightSpace,
"appendIfNotPresent": appendIfNotPresent,
"rpad": rpad,
"gt": Gt,
"eq": Eq,
}
var initializers []func()
// automatic prefix matching can be a dangerous thing to automatically enable in CLI tools.
// Set this to true to enable it
// EnablePrefixMatching allows to set automatic prefix matching. Automatic prefix matching can be a dangerous thing
// to automatically enable in CLI tools.
// Set this to true to enable it.
var EnablePrefixMatching = false
//EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
//To disable sorting, set it to false.
// EnableCommandSorting controls sorting of the slice of commands, which is turned on by default.
// To disable sorting, set it to false.
var EnableCommandSorting = true
//AddTemplateFunc adds a template function that's available to Usage and Help
//template generation.
// MousetrapHelpText enables an information splash screen on Windows
// if the CLI is started from explorer.exe.
// To disable the mousetrap, just set this variable to blank string ("").
// Works only on Microsoft Windows.
var MousetrapHelpText string = `This is a command line tool.
You need to open cmd.exe and run it from there.
`
// AddTemplateFunc adds a template function that's available to Usage and Help
// template generation.
func AddTemplateFunc(name string, tmplFunc interface{}) {
templateFuncs[name] = tmplFunc
}
//AddTemplateFuncs adds multiple template functions availalble to Usage and
//Help template generation.
// AddTemplateFuncs adds multiple template functions that are available to Usage and
// Help template generation.
func AddTemplateFuncs(tmplFuncs template.FuncMap) {
for k, v := range tmplFuncs {
templateFuncs[k] = v
}
}
//OnInitialize takes a series of func() arguments and appends them to a slice of func().
// OnInitialize sets the passed functions to be run when each command's
// Execute method is called.
func OnInitialize(y ...func()) {
for _, x := range y {
initializers = append(initializers, x)
}
initializers = append(initializers, y...)
}
//Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
//Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
//ints and then compared.
// FIXME Gt is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
// Gt takes two types and checks whether the first type is greater than the second. In case of types Arrays, Chans,
// Maps and Slices, Gt will compare their lengths. Ints are compared directly while strings are first parsed as
// ints and then compared.
func Gt(a interface{}, b interface{}) bool {
var left, right int64
av := reflect.ValueOf(a)
@ -96,7 +108,9 @@ func Gt(a interface{}, b interface{}) bool {
return left > right
}
//Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
// FIXME Eq is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
// Eq takes two types and checks whether they are equal. Supported types are int and string. Unsupported types will panic.
func Eq(a interface{}, b interface{}) bool {
av := reflect.ValueOf(a)
bv := reflect.ValueOf(b)
@ -116,7 +130,9 @@ func trimRightSpace(s string) string {
return strings.TrimRightFunc(s, unicode.IsSpace)
}
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s
// FIXME appendIfNotPresent is unused by cobra and should be removed in a version 2. It exists only for compatibility with users of cobra.
// appendIfNotPresent will append stringToAppend to the end of s, but only if it's not yet present in s.
func appendIfNotPresent(s, stringToAppend string) string {
if strings.Contains(s, stringToAppend) {
return s
@ -124,7 +140,7 @@ func appendIfNotPresent(s, stringToAppend string) string {
return s + " " + stringToAppend
}
//rpad adds padding to the right of a string
// rpad adds padding to the right of a string.
func rpad(s string, padding int) string {
template := fmt.Sprintf("%%-%ds", padding)
return fmt.Sprintf(template, s)
@ -138,7 +154,7 @@ func tmpl(w io.Writer, text string, data interface{}) error {
return t.Execute(w, data)
}
// ld compares two strings and returns the levenshtein distance between them
// ld compares two strings and returns the levenshtein distance between them.
func ld(s, t string, ignoreCase bool) int {
if ignoreCase {
s = strings.ToLower(s)
@ -173,3 +189,12 @@ func ld(s, t string, ignoreCase bool) int {
}
return d[len(s)][len(t)]
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

File diff suppressed because it is too large Load Diff

View File

@ -11,14 +11,8 @@ import (
var preExecHookFn = preExecHook
// enables an information splash screen on Windows if the CLI is started from explorer.exe.
var MousetrapHelpText string = `This is a command line tool
You need to open cmd.exe and run it from there.
`
func preExecHook(c *Command) {
if mousetrap.StartedByExplorer() {
if MousetrapHelpText != "" && mousetrap.StartedByExplorer() {
c.Print(MousetrapHelpText)
time.Sleep(5 * time.Second)
os.Exit(1)

View File

@ -0,0 +1,126 @@
package cobra
import (
"bytes"
"fmt"
"io"
"os"
"strings"
)
// GenZshCompletionFile generates zsh completion file.
func (c *Command) GenZshCompletionFile(filename string) error {
outFile, err := os.Create(filename)
if err != nil {
return err
}
defer outFile.Close()
return c.GenZshCompletion(outFile)
}
// GenZshCompletion generates a zsh completion file and writes to the passed writer.
func (c *Command) GenZshCompletion(w io.Writer) error {
buf := new(bytes.Buffer)
writeHeader(buf, c)
maxDepth := maxDepth(c)
writeLevelMapping(buf, maxDepth)
writeLevelCases(buf, maxDepth, c)
_, err := buf.WriteTo(w)
return err
}
func writeHeader(w io.Writer, cmd *Command) {
fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name())
}
func maxDepth(c *Command) int {
if len(c.Commands()) == 0 {
return 0
}
maxDepthSub := 0
for _, s := range c.Commands() {
subDepth := maxDepth(s)
if subDepth > maxDepthSub {
maxDepthSub = subDepth
}
}
return 1 + maxDepthSub
}
func writeLevelMapping(w io.Writer, numLevels int) {
fmt.Fprintln(w, `_arguments \`)
for i := 1; i <= numLevels; i++ {
fmt.Fprintf(w, ` '%d: :->level%d' \`, i, i)
fmt.Fprintln(w)
}
fmt.Fprintf(w, ` '%d: :%s'`, numLevels+1, "_files")
fmt.Fprintln(w)
}
func writeLevelCases(w io.Writer, maxDepth int, root *Command) {
fmt.Fprintln(w, "case $state in")
defer fmt.Fprintln(w, "esac")
for i := 1; i <= maxDepth; i++ {
fmt.Fprintf(w, " level%d)\n", i)
writeLevel(w, root, i)
fmt.Fprintln(w, " ;;")
}
fmt.Fprintln(w, " *)")
fmt.Fprintln(w, " _arguments '*: :_files'")
fmt.Fprintln(w, " ;;")
}
func writeLevel(w io.Writer, root *Command, i int) {
fmt.Fprintf(w, " case $words[%d] in\n", i)
defer fmt.Fprintln(w, " esac")
commands := filterByLevel(root, i)
byParent := groupByParent(commands)
for p, c := range byParent {
names := names(c)
fmt.Fprintf(w, " %s)\n", p)
fmt.Fprintf(w, " _arguments '%d: :(%s)'\n", i, strings.Join(names, " "))
fmt.Fprintln(w, " ;;")
}
fmt.Fprintln(w, " *)")
fmt.Fprintln(w, " _arguments '*: :_files'")
fmt.Fprintln(w, " ;;")
}
func filterByLevel(c *Command, l int) []*Command {
cs := make([]*Command, 0)
if l == 0 {
cs = append(cs, c)
return cs
}
for _, s := range c.Commands() {
cs = append(cs, filterByLevel(s, l-1)...)
}
return cs
}
func groupByParent(commands []*Command) map[string][]*Command {
m := make(map[string][]*Command)
for _, c := range commands {
parent := c.Parent()
if parent == nil {
continue
}
m[parent.Name()] = append(m[parent.Name()], c)
}
return m
}
func names(commands []*Command) []string {
ns := make([]string, len(commands))
for i, c := range commands {
ns[i] = c.Name()
}
return ns
}

View File

@ -246,6 +246,25 @@ It is possible to mark a flag as hidden, meaning it will still function as norma
flags.MarkHidden("secretFlag")
```
## Disable sorting of flags
`pflag` allows you to disable sorting of flags for help and usage message.
**Example**:
```go
flags.BoolP("verbose", "v", false, "verbose output")
flags.String("coolflag", "yeaah", "it's really cool flag")
flags.Int("usefulflag", 777, "sometimes it's very useful")
flags.SortFlags = false
flags.PrintDefaults()
```
**Output**:
```
-v, --verbose verbose output
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
```
## Supporting Go flags when using pflag
In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary
to support flags defined by third-party dependencies (e.g. `golang/glog`).
@ -270,8 +289,8 @@ func main() {
You can see the full reference documentation of the pflag package
[at godoc.org][3], or through go's standard documentation system by
running `godoc -http=:6060` and browsing to
[http://localhost:6060/pkg/github.com/ogier/pflag][2] after
[http://localhost:6060/pkg/github.com/spf13/pflag][2] after
installation.
[2]: http://localhost:6060/pkg/github.com/ogier/pflag
[3]: http://godoc.org/github.com/ogier/pflag
[2]: http://localhost:6060/pkg/github.com/spf13/pflag
[3]: http://godoc.org/github.com/spf13/pflag

View File

@ -0,0 +1,105 @@
package pflag
import (
"encoding/hex"
"fmt"
"strings"
)
// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded
type bytesHexValue []byte
func (bytesHex bytesHexValue) String() string {
return fmt.Sprintf("%X", []byte(bytesHex))
}
func (bytesHex *bytesHexValue) Set(value string) error {
bin, err := hex.DecodeString(strings.TrimSpace(value))
if err != nil {
return err
}
*bytesHex = bin
return nil
}
func (*bytesHexValue) Type() string {
return "bytesHex"
}
func newBytesHexValue(val []byte, p *[]byte) *bytesHexValue {
*p = val
return (*bytesHexValue)(p)
}
func bytesHexConv(sval string) (interface{}, error) {
bin, err := hex.DecodeString(sval)
if err == nil {
return bin, nil
}
return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err)
}
// GetBytesHex return the []byte value of a flag with the given name
func (f *FlagSet) GetBytesHex(name string) ([]byte, error) {
val, err := f.getFlagType(name, "bytesHex", bytesHexConv)
if err != nil {
return []byte{}, err
}
return val.([]byte), nil
}
// BytesHexVar defines an []byte flag with specified name, default value, and usage string.
// The argument p points to an []byte variable in which to store the value of the flag.
func (f *FlagSet) BytesHexVar(p *[]byte, name string, value []byte, usage string) {
f.VarP(newBytesHexValue(value, p), name, "", usage)
}
// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) {
f.VarP(newBytesHexValue(value, p), name, shorthand, usage)
}
// BytesHexVar defines an []byte flag with specified name, default value, and usage string.
// The argument p points to an []byte variable in which to store the value of the flag.
func BytesHexVar(p *[]byte, name string, value []byte, usage string) {
CommandLine.VarP(newBytesHexValue(value, p), name, "", usage)
}
// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash.
func BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) {
CommandLine.VarP(newBytesHexValue(value, p), name, shorthand, usage)
}
// BytesHex defines an []byte flag with specified name, default value, and usage string.
// The return value is the address of an []byte variable that stores the value of the flag.
func (f *FlagSet) BytesHex(name string, value []byte, usage string) *[]byte {
p := new([]byte)
f.BytesHexVarP(p, name, "", value, usage)
return p
}
// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
p := new([]byte)
f.BytesHexVarP(p, name, shorthand, value, usage)
return p
}
// BytesHex defines an []byte flag with specified name, default value, and usage string.
// The return value is the address of an []byte variable that stores the value of the flag.
func BytesHex(name string, value []byte, usage string) *[]byte {
return CommandLine.BytesHexP(name, "", value, usage)
}
// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash.
func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
return CommandLine.BytesHexP(name, shorthand, value, usage)
}

View File

@ -11,13 +11,13 @@ func newCountValue(val int, p *int) *countValue {
}
func (i *countValue) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 64)
// -1 means that no specific value was passed, so increment
if v == -1 {
// "+1" means that no specific value was passed, so increment
if s == "+1" {
*i = countValue(*i + 1)
} else {
*i = countValue(v)
return nil
}
v, err := strconv.ParseInt(s, 0, 0)
*i = countValue(v)
return err
}
@ -54,7 +54,7 @@ func (f *FlagSet) CountVar(p *int, name string, usage string) {
// CountVarP is like CountVar only take a shorthand for the flag name.
func (f *FlagSet) CountVarP(p *int, name, shorthand string, usage string) {
flag := f.VarPF(newCountValue(0, p), name, shorthand, usage)
flag.NoOptDefVal = "-1"
flag.NoOptDefVal = "+1"
}
// CountVar like CountVar only the flag is placed on the CommandLine instead of a given flag set
@ -83,7 +83,9 @@ func (f *FlagSet) CountP(name, shorthand string, usage string) *int {
return p
}
// Count like Count only the flag is placed on the CommandLine isntead of a given flag set
// Count defines a count flag with specified name, default value, and usage string.
// The return value is the address of an int variable that stores the value of the flag.
// A count flag will add 1 to its value evey time it is found on the command line
func Count(name string, usage string) *int {
return CommandLine.CountP(name, "", usage)
}

View File

@ -0,0 +1,128 @@
package pflag
import (
"fmt"
"strings"
"time"
)
// -- durationSlice Value
type durationSliceValue struct {
value *[]time.Duration
changed bool
}
func newDurationSliceValue(val []time.Duration, p *[]time.Duration) *durationSliceValue {
dsv := new(durationSliceValue)
dsv.value = p
*dsv.value = val
return dsv
}
func (s *durationSliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]time.Duration, len(ss))
for i, d := range ss {
var err error
out[i], err = time.ParseDuration(d)
if err != nil {
return err
}
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *durationSliceValue) Type() string {
return "durationSlice"
}
func (s *durationSliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%s", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func durationSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []time.Duration{}, nil
}
ss := strings.Split(val, ",")
out := make([]time.Duration, len(ss))
for i, d := range ss {
var err error
out[i], err = time.ParseDuration(d)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetDurationSlice returns the []time.Duration value of a flag with the given name
func (f *FlagSet) GetDurationSlice(name string) ([]time.Duration, error) {
val, err := f.getFlagType(name, "durationSlice", durationSliceConv)
if err != nil {
return []time.Duration{}, err
}
return val.([]time.Duration), nil
}
// DurationSliceVar defines a durationSlice flag with specified name, default value, and usage string.
// The argument p points to a []time.Duration variable in which to store the value of the flag.
func (f *FlagSet) DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) {
f.VarP(newDurationSliceValue(value, p), name, "", usage)
}
// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) {
f.VarP(newDurationSliceValue(value, p), name, shorthand, usage)
}
// DurationSliceVar defines a duration[] flag with specified name, default value, and usage string.
// The argument p points to a duration[] variable in which to store the value of the flag.
func DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) {
CommandLine.VarP(newDurationSliceValue(value, p), name, "", usage)
}
// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash.
func DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) {
CommandLine.VarP(newDurationSliceValue(value, p), name, shorthand, usage)
}
// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a []time.Duration variable that stores the value of the flag.
func (f *FlagSet) DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration {
p := []time.Duration{}
f.DurationSliceVarP(&p, name, "", value, usage)
return &p
}
// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration {
p := []time.Duration{}
f.DurationSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a []time.Duration variable that stores the value of the flag.
func DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration {
return CommandLine.DurationSliceP(name, "", value, usage)
}
// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash.
func DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration {
return CommandLine.DurationSliceP(name, shorthand, value, usage)
}

View File

@ -16,9 +16,9 @@ pflag is a drop-in replacement of Go's native flag package. If you import
pflag under the name "flag" then all code should continue to function
with no changes.
import flag "github.com/ogier/pflag"
import flag "github.com/spf13/pflag"
There is one exception to this: if you directly instantiate the Flag struct
There is one exception to this: if you directly instantiate the Flag struct
there is one more field "Shorthand" that you will need to set.
Most code never instantiates this struct directly, and instead uses
functions such as String(), BoolVar(), and Var(), and is therefore
@ -101,6 +101,7 @@ package pflag
import (
"bytes"
"errors"
goflag "flag"
"fmt"
"io"
"os"
@ -123,6 +124,12 @@ const (
PanicOnError
)
// ParseErrorsWhitelist defines the parsing errors that can be ignored
type ParseErrorsWhitelist struct {
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
UnknownFlags bool
}
// NormalizedName is a flag name that has been normalized according to rules
// for the FlagSet (e.g. making '-' and '_' equivalent).
type NormalizedName string
@ -134,18 +141,30 @@ type FlagSet struct {
// a custom error handler.
Usage func()
// SortFlags is used to indicate, if user wants to have sorted flags in
// help/usage messages.
SortFlags bool
// ParseErrorsWhitelist is used to configure a whitelist of errors
ParseErrorsWhitelist ParseErrorsWhitelist
name string
parsed bool
actual map[NormalizedName]*Flag
orderedActual []*Flag
sortedActual []*Flag
formal map[NormalizedName]*Flag
orderedFormal []*Flag
sortedFormal []*Flag
shorthands map[byte]*Flag
args []string // arguments after flags
argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no --
exitOnError bool // does the program exit if there's an error?
errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor
interspersed bool // allow interspersed option/non-option args
normalizeNameFunc func(f *FlagSet, name string) NormalizedName
addedGoFlagSets []*goflag.FlagSet
}
// A Flag represents the state of a flag.
@ -156,7 +175,7 @@ type Flag struct {
Value Value // value as set
DefValue string // default value (as text); for usage message
Changed bool // If the user set the value (or if left to default)
NoOptDefVal string //default value (as text); if the flag is on the command line without any options
NoOptDefVal string // default value (as text); if the flag is on the command line without any options
Deprecated string // If this flag is deprecated, this string is the new or now thing to use
Hidden bool // used by cobra.Command to allow flags to be hidden from help/usage text
ShorthandDeprecated string // If the shorthand of this flag is deprecated, this string is the new or now thing to use
@ -194,11 +213,19 @@ func sortFlags(flags map[NormalizedName]*Flag) []*Flag {
// "--getUrl" which may also be translated to "geturl" and everything will work.
func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) {
f.normalizeNameFunc = n
for k, v := range f.formal {
delete(f.formal, k)
nname := f.normalizeFlagName(string(k))
f.formal[nname] = v
v.Name = string(nname)
f.sortedFormal = f.sortedFormal[:0]
for fname, flag := range f.formal {
nname := f.normalizeFlagName(flag.Name)
if fname == nname {
continue
}
flag.Name = string(nname)
delete(f.formal, fname)
f.formal[nname] = flag
if _, set := f.actual[fname]; set {
delete(f.actual, fname)
f.actual[nname] = flag
}
}
}
@ -229,46 +256,78 @@ func (f *FlagSet) SetOutput(output io.Writer) {
f.output = output
}
// VisitAll visits the flags in lexicographical order, calling fn for each.
// VisitAll visits the flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits all flags, even those not set.
func (f *FlagSet) VisitAll(fn func(*Flag)) {
for _, flag := range sortFlags(f.formal) {
if len(f.formal) == 0 {
return
}
var flags []*Flag
if f.SortFlags {
if len(f.formal) != len(f.sortedFormal) {
f.sortedFormal = sortFlags(f.formal)
}
flags = f.sortedFormal
} else {
flags = f.orderedFormal
}
for _, flag := range flags {
fn(flag)
}
}
// HasFlags returns a bool to indicate if the FlagSet has any flags definied.
// HasFlags returns a bool to indicate if the FlagSet has any flags defined.
func (f *FlagSet) HasFlags() bool {
return len(f.formal) > 0
}
// HasAvailableFlags returns a bool to indicate if the FlagSet has any flags
// definied that are not hidden or deprecated.
// that are not hidden.
func (f *FlagSet) HasAvailableFlags() bool {
for _, flag := range f.formal {
if !flag.Hidden && len(flag.Deprecated) == 0 {
if !flag.Hidden {
return true
}
}
return false
}
// VisitAll visits the command-line flags in lexicographical order, calling
// fn for each. It visits all flags, even those not set.
// VisitAll visits the command-line flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits all flags, even those not set.
func VisitAll(fn func(*Flag)) {
CommandLine.VisitAll(fn)
}
// Visit visits the flags in lexicographical order, calling fn for each.
// Visit visits the flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits only those flags that have been set.
func (f *FlagSet) Visit(fn func(*Flag)) {
for _, flag := range sortFlags(f.actual) {
if len(f.actual) == 0 {
return
}
var flags []*Flag
if f.SortFlags {
if len(f.actual) != len(f.sortedActual) {
f.sortedActual = sortFlags(f.actual)
}
flags = f.sortedActual
} else {
flags = f.orderedActual
}
for _, flag := range flags {
fn(flag)
}
}
// Visit visits the command-line flags in lexicographical order, calling fn
// for each. It visits only those flags that have been set.
// Visit visits the command-line flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits only those flags that have been set.
func Visit(fn func(*Flag)) {
CommandLine.Visit(fn)
}
@ -278,6 +337,22 @@ func (f *FlagSet) Lookup(name string) *Flag {
return f.lookup(f.normalizeFlagName(name))
}
// ShorthandLookup returns the Flag structure of the short handed flag,
// returning nil if none exists.
// It panics, if len(name) > 1.
func (f *FlagSet) ShorthandLookup(name string) *Flag {
if name == "" {
return nil
}
if len(name) > 1 {
msg := fmt.Sprintf("can not look up shorthand which is more than one ASCII character: %q", name)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
c := name[0]
return f.shorthands[c]
}
// lookup returns the Flag structure of the named flag, returning nil if none exists.
func (f *FlagSet) lookup(name NormalizedName) *Flag {
return f.formal[name]
@ -319,10 +394,11 @@ func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error {
if flag == nil {
return fmt.Errorf("flag %q does not exist", name)
}
if len(usageMessage) == 0 {
if usageMessage == "" {
return fmt.Errorf("deprecated message for flag %q must be set", name)
}
flag.Deprecated = usageMessage
flag.Hidden = true
return nil
}
@ -334,7 +410,7 @@ func (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) erro
if flag == nil {
return fmt.Errorf("flag %q does not exist", name)
}
if len(usageMessage) == 0 {
if usageMessage == "" {
return fmt.Errorf("deprecated message for flag %q must be set", name)
}
flag.ShorthandDeprecated = usageMessage
@ -358,6 +434,12 @@ func Lookup(name string) *Flag {
return CommandLine.Lookup(name)
}
// ShorthandLookup returns the Flag structure of the short handed flag,
// returning nil if none exists.
func ShorthandLookup(name string) *Flag {
return CommandLine.ShorthandLookup(name)
}
// Set sets the value of the named flag.
func (f *FlagSet) Set(name, value string) error {
normalName := f.normalizeFlagName(name)
@ -365,17 +447,30 @@ func (f *FlagSet) Set(name, value string) error {
if !ok {
return fmt.Errorf("no such flag -%v", name)
}
err := flag.Value.Set(value)
if err != nil {
return err
var flagName string
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
flagName = fmt.Sprintf("-%s, --%s", flag.Shorthand, flag.Name)
} else {
flagName = fmt.Sprintf("--%s", flag.Name)
}
return fmt.Errorf("invalid argument %q for %q flag: %v", value, flagName, err)
}
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
if !flag.Changed {
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
}
f.actual[normalName] = flag
f.orderedActual = append(f.orderedActual, flag)
flag.Changed = true
}
f.actual[normalName] = flag
flag.Changed = true
if len(flag.Deprecated) > 0 {
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
if flag.Deprecated != "" {
fmt.Fprintf(f.out(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
}
return nil
}
@ -482,6 +577,14 @@ func UnquoteUsage(flag *Flag) (name string, usage string) {
name = "int"
case "uint64":
name = "uint"
case "stringSlice":
name = "strings"
case "intSlice":
name = "ints"
case "uintSlice":
name = "uints"
case "boolSlice":
name = "bools"
}
return
@ -496,11 +599,14 @@ func wrapN(i, slop int, s string) (string, string) {
return s, ""
}
w := strings.LastIndexAny(s[:i], " \t")
w := strings.LastIndexAny(s[:i], " \t\n")
if w <= 0 {
return s, ""
}
nlPos := strings.LastIndex(s[:i], "\n")
if nlPos > 0 && nlPos < w {
return s[:nlPos], s[nlPos+1:]
}
return s[:w], s[w+1:]
}
@ -509,7 +615,7 @@ func wrapN(i, slop int, s string) (string, string) {
// caller). Pass `w` == 0 to do no wrapping
func wrap(i, w int, s string) string {
if w == 0 {
return s
return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
}
// space between indent i and end of line width w into which
@ -527,7 +633,7 @@ func wrap(i, w int, s string) string {
}
// If still not enough space then don't even try to wrap.
if wrap < 24 {
return s
return strings.Replace(s, "\n", r, -1)
}
// Try to avoid short orphan words on the final line, by
@ -539,14 +645,14 @@ func wrap(i, w int, s string) string {
// Handle first line, which is indented by the caller (or the
// special case above)
l, s = wrapN(wrap, slop, s)
r = r + l
r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
// Now wrap the rest
for s != "" {
var t string
t, s = wrapN(wrap, slop, s)
r = r + "\n" + strings.Repeat(" ", i) + t
r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
}
return r
@ -557,28 +663,28 @@ func wrap(i, w int, s string) string {
// for all flags in the FlagSet. Wrapped to `cols` columns (0 for no
// wrapping)
func (f *FlagSet) FlagUsagesWrapped(cols int) string {
x := new(bytes.Buffer)
buf := new(bytes.Buffer)
lines := make([]string, 0, len(f.formal))
maxlen := 0
f.VisitAll(func(flag *Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden {
if flag.Hidden {
return
}
line := ""
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 {
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
} else {
line = fmt.Sprintf(" --%s", flag.Name)
}
varname, usage := UnquoteUsage(flag)
if len(varname) > 0 {
if varname != "" {
line += " " + varname
}
if len(flag.NoOptDefVal) > 0 {
if flag.NoOptDefVal != "" {
switch flag.Value.Type() {
case "string":
line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
@ -586,6 +692,10 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
if flag.NoOptDefVal != "true" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
case "count":
if flag.NoOptDefVal != "+1" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
default:
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
@ -601,11 +711,14 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
line += usage
if !flag.defaultIsZeroValue() {
if flag.Value.Type() == "string" {
line += fmt.Sprintf(" (default \"%s\")", flag.DefValue)
line += fmt.Sprintf(" (default %q)", flag.DefValue)
} else {
line += fmt.Sprintf(" (default %s)", flag.DefValue)
}
}
if len(flag.Deprecated) != 0 {
line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
}
lines = append(lines, line)
})
@ -614,10 +727,10 @@ func (f *FlagSet) FlagUsagesWrapped(cols int) string {
sidx := strings.Index(line, "\x00")
spacing := strings.Repeat(" ", maxlen-sidx)
// maxlen + 2 comes from + 1 for the \x00 and + 1 for the (deliberate) off-by-one in maxlen-sidx
fmt.Fprintln(x, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
fmt.Fprintln(buf, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
}
return x.String()
return buf.String()
}
// FlagUsages returns a string containing the usage information for all flags in
@ -714,11 +827,10 @@ func (f *FlagSet) VarP(value Value, name, shorthand, usage string) {
// AddFlag will add the flag to the FlagSet
func (f *FlagSet) AddFlag(flag *Flag) {
// Call normalizeFlagName function only once
normalizedFlagName := f.normalizeFlagName(flag.Name)
_, alreadythere := f.formal[normalizedFlagName]
if alreadythere {
_, alreadyThere := f.formal[normalizedFlagName]
if alreadyThere {
msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name)
fmt.Fprintln(f.out(), msg)
panic(msg) // Happens only if flags are declared with identical names
@ -729,28 +841,31 @@ func (f *FlagSet) AddFlag(flag *Flag) {
flag.Name = string(normalizedFlagName)
f.formal[normalizedFlagName] = flag
f.orderedFormal = append(f.orderedFormal, flag)
if len(flag.Shorthand) == 0 {
if flag.Shorthand == "" {
return
}
if len(flag.Shorthand) > 1 {
fmt.Fprintf(f.out(), "%s shorthand more than ASCII character: %s\n", f.name, flag.Shorthand)
panic("shorthand is more than one character")
msg := fmt.Sprintf("%q shorthand is more than one ASCII character", flag.Shorthand)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
if f.shorthands == nil {
f.shorthands = make(map[byte]*Flag)
}
c := flag.Shorthand[0]
old, alreadythere := f.shorthands[c]
if alreadythere {
fmt.Fprintf(f.out(), "%s shorthand reused: %q for %s already used for %s\n", f.name, c, flag.Name, old.Name)
panic("shorthand redefinition")
used, alreadyThere := f.shorthands[c]
if alreadyThere {
msg := fmt.Sprintf("unable to redefine %q shorthand in %q flagset: it's already used for %q flag", c, f.name, used.Name)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
f.shorthands[c] = flag
}
// AddFlagSet adds one FlagSet to another. If a flag is already present in f
// the flag from newSet will be ignored
// the flag from newSet will be ignored.
func (f *FlagSet) AddFlagSet(newSet *FlagSet) {
if newSet == nil {
return
@ -781,8 +896,10 @@ func VarP(value Value, name, shorthand, usage string) {
// returns the error.
func (f *FlagSet) failf(format string, a ...interface{}) error {
err := fmt.Errorf(format, a...)
fmt.Fprintln(f.out(), err)
f.usage()
if f.errorHandling != ContinueOnError {
fmt.Fprintln(f.out(), err)
f.usage()
}
return err
}
@ -798,32 +915,23 @@ func (f *FlagSet) usage() {
}
}
func (f *FlagSet) setFlag(flag *Flag, value string, origArg string) error {
if err := flag.Value.Set(value); err != nil {
return f.failf("invalid argument %q for %s: %v", value, origArg, err)
//--unknown (args will be empty)
//--unknown --next-flag ... (args will be --next-flag ...)
//--unknown arg ... (args will be arg ...)
func stripUnknownFlagValue(args []string) []string {
if len(args) == 0 {
//--unknown
return args
}
// mark as visited for Visit()
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
}
f.actual[f.normalizeFlagName(flag.Name)] = flag
flag.Changed = true
if len(flag.Deprecated) > 0 {
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
}
if len(flag.ShorthandDeprecated) > 0 && containsShorthand(origArg, flag.Shorthand) {
fmt.Fprintf(os.Stderr, "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
}
return nil
}
func containsShorthand(arg, shorthand string) bool {
// filter out flags --<flag_name>
if strings.HasPrefix(arg, "-") {
return false
first := args[0]
if first[0] == '-' {
//--unknown --next-flag ...
return args
}
arg = strings.SplitN(arg, "=", 2)[0]
return strings.Contains(arg, shorthand)
//--unknown arg ... (args will be arg ...)
return args[1:]
}
func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) {
@ -833,22 +941,35 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
err = f.failf("bad flag syntax: %s", s)
return
}
split := strings.SplitN(name, "=", 2)
name = split[0]
flag, alreadythere := f.formal[f.normalizeFlagName(name)]
if !alreadythere {
if name == "help" { // special case for nice help message.
flag, exists := f.formal[f.normalizeFlagName(name)]
if !exists {
switch {
case name == "help":
f.usage()
return a, ErrHelp
case f.ParseErrorsWhitelist.UnknownFlags:
// --unknown=unknownval arg ...
// we do not want to lose arg in this case
if len(split) >= 2 {
return a, nil
}
return stripUnknownFlagValue(a), nil
default:
err = f.failf("unknown flag: --%s", name)
return
}
err = f.failf("unknown flag: --%s", name)
return
}
var value string
if len(split) == 2 {
// '--flag=arg'
value = split[1]
} else if len(flag.NoOptDefVal) > 0 {
} else if flag.NoOptDefVal != "" {
// '--flag' (arg was optional)
value = flag.NoOptDefVal
} else if len(a) > 0 {
@ -860,7 +981,11 @@ func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []strin
err = f.failf("flag needs an argument: %s", s)
return
}
err = fn(flag, value, s)
err = fn(flag, value)
if err != nil {
f.failf(err.Error())
}
return
}
@ -868,38 +993,64 @@ func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parse
if strings.HasPrefix(shorthands, "test.") {
return
}
outArgs = args
outShorts = shorthands[1:]
c := shorthands[0]
flag, alreadythere := f.shorthands[c]
if !alreadythere {
if c == 'h' { // special case for nice help message.
flag, exists := f.shorthands[c]
if !exists {
switch {
case c == 'h':
f.usage()
err = ErrHelp
return
case f.ParseErrorsWhitelist.UnknownFlags:
// '-f=arg arg ...'
// we do not want to lose arg in this case
if len(shorthands) > 2 && shorthands[1] == '=' {
outShorts = ""
return
}
outArgs = stripUnknownFlagValue(outArgs)
return
default:
err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
return
}
//TODO continue on error
err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
return
}
var value string
if len(shorthands) > 2 && shorthands[1] == '=' {
// '-f=arg'
value = shorthands[2:]
outShorts = ""
} else if len(flag.NoOptDefVal) > 0 {
} else if flag.NoOptDefVal != "" {
// '-f' (arg was optional)
value = flag.NoOptDefVal
} else if len(shorthands) > 1 {
// '-farg'
value = shorthands[1:]
outShorts = ""
} else if len(args) > 0 {
// '-f arg'
value = args[0]
outArgs = args[1:]
} else {
// '-f' (arg was required)
err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return
}
err = fn(flag, value, shorthands)
if flag.ShorthandDeprecated != "" {
fmt.Fprintf(f.out(), "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
}
err = fn(flag, value)
if err != nil {
f.failf(err.Error())
}
return
}
@ -907,6 +1058,7 @@ func (f *FlagSet) parseShortArg(s string, args []string, fn parseFunc) (a []stri
a = args
shorthands := s[1:]
// "shorthands" can be a series of shorthand letters of flags (e.g. "-vvv").
for len(shorthands) > 0 {
shorthands, a, err = f.parseSingleShortArg(shorthands, args, fn)
if err != nil {
@ -953,19 +1105,30 @@ func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) {
// are defined and before flags are accessed by the program.
// The return value will be ErrHelp if -help was set but not defined.
func (f *FlagSet) Parse(arguments []string) error {
if f.addedGoFlagSets != nil {
for _, goFlagSet := range f.addedGoFlagSets {
goFlagSet.Parse(nil)
}
}
f.parsed = true
f.args = make([]string, 0, len(arguments))
assign := func(flag *Flag, value, origArg string) error {
return f.setFlag(flag, value, origArg)
if len(arguments) < 0 {
return nil
}
err := f.parseArgs(arguments, assign)
f.args = make([]string, 0, len(arguments))
set := func(flag *Flag, value string) error {
return f.Set(flag.Name, value)
}
err := f.parseArgs(arguments, set)
if err != nil {
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
fmt.Println(err)
os.Exit(2)
case PanicOnError:
panic(err)
@ -974,7 +1137,7 @@ func (f *FlagSet) Parse(arguments []string) error {
return nil
}
type parseFunc func(flag *Flag, value, origArg string) error
type parseFunc func(flag *Flag, value string) error
// ParseAll parses flag definitions from the argument list, which should not
// include the command name. The arguments for fn are flag and value. Must be
@ -985,11 +1148,7 @@ func (f *FlagSet) ParseAll(arguments []string, fn func(flag *Flag, value string)
f.parsed = true
f.args = make([]string, 0, len(arguments))
assign := func(flag *Flag, value, origArg string) error {
return fn(flag, value)
}
err := f.parseArgs(arguments, assign)
err := f.parseArgs(arguments, fn)
if err != nil {
switch f.errorHandling {
case ContinueOnError:
@ -1036,14 +1195,15 @@ func Parsed() bool {
// CommandLine is the default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// NewFlagSet returns a new, empty flag set with the specified name and
// error handling property.
// NewFlagSet returns a new, empty flag set with the specified name,
// error handling property and SortFlags set to true.
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{
name: name,
errorHandling: errorHandling,
argsLenAtDash: -1,
interspersed: true,
SortFlags: true,
}
return f
}

View File

@ -98,4 +98,8 @@ func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) {
newSet.VisitAll(func(goflag *goflag.Flag) {
f.AddGoFlag(goflag)
})
if f.addedGoFlagSets == nil {
f.addedGoFlagSets = make([]*goflag.FlagSet, 0)
}
f.addedGoFlagSets = append(f.addedGoFlagSets, newSet)
}

View File

@ -0,0 +1,88 @@
package pflag
import "strconv"
// -- int16 Value
type int16Value int16
func newInt16Value(val int16, p *int16) *int16Value {
*p = val
return (*int16Value)(p)
}
func (i *int16Value) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 16)
*i = int16Value(v)
return err
}
func (i *int16Value) Type() string {
return "int16"
}
func (i *int16Value) String() string { return strconv.FormatInt(int64(*i), 10) }
func int16Conv(sval string) (interface{}, error) {
v, err := strconv.ParseInt(sval, 0, 16)
if err != nil {
return 0, err
}
return int16(v), nil
}
// GetInt16 returns the int16 value of a flag with the given name
func (f *FlagSet) GetInt16(name string) (int16, error) {
val, err := f.getFlagType(name, "int16", int16Conv)
if err != nil {
return 0, err
}
return val.(int16), nil
}
// Int16Var defines an int16 flag with specified name, default value, and usage string.
// The argument p points to an int16 variable in which to store the value of the flag.
func (f *FlagSet) Int16Var(p *int16, name string, value int16, usage string) {
f.VarP(newInt16Value(value, p), name, "", usage)
}
// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int16VarP(p *int16, name, shorthand string, value int16, usage string) {
f.VarP(newInt16Value(value, p), name, shorthand, usage)
}
// Int16Var defines an int16 flag with specified name, default value, and usage string.
// The argument p points to an int16 variable in which to store the value of the flag.
func Int16Var(p *int16, name string, value int16, usage string) {
CommandLine.VarP(newInt16Value(value, p), name, "", usage)
}
// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash.
func Int16VarP(p *int16, name, shorthand string, value int16, usage string) {
CommandLine.VarP(newInt16Value(value, p), name, shorthand, usage)
}
// Int16 defines an int16 flag with specified name, default value, and usage string.
// The return value is the address of an int16 variable that stores the value of the flag.
func (f *FlagSet) Int16(name string, value int16, usage string) *int16 {
p := new(int16)
f.Int16VarP(p, name, "", value, usage)
return p
}
// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int16P(name, shorthand string, value int16, usage string) *int16 {
p := new(int16)
f.Int16VarP(p, name, shorthand, value, usage)
return p
}
// Int16 defines an int16 flag with specified name, default value, and usage string.
// The return value is the address of an int16 variable that stores the value of the flag.
func Int16(name string, value int16, usage string) *int16 {
return CommandLine.Int16P(name, "", value, usage)
}
// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash.
func Int16P(name, shorthand string, value int16, usage string) *int16 {
return CommandLine.Int16P(name, shorthand, value, usage)
}

View File

@ -52,7 +52,7 @@ func (f *FlagSet) GetStringArray(name string) ([]string, error) {
// StringArrayVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the values of the multiple flags.
// The value of each argument will not try to be separated by comma
// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) {
f.VarP(newStringArrayValue(value, p), name, "", usage)
}
@ -64,7 +64,7 @@ func (f *FlagSet) StringArrayVarP(p *[]string, name, shorthand string, value []s
// StringArrayVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag.
// The value of each argument will not try to be separated by comma
// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func StringArrayVar(p *[]string, name string, value []string, usage string) {
CommandLine.VarP(newStringArrayValue(value, p), name, "", usage)
}
@ -76,7 +76,7 @@ func StringArrayVarP(p *[]string, name, shorthand string, value []string, usage
// StringArray defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string {
p := []string{}
f.StringArrayVarP(&p, name, "", value, usage)
@ -92,7 +92,7 @@ func (f *FlagSet) StringArrayP(name, shorthand string, value []string, usage str
// StringArray defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma
// The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func StringArray(name string, value []string, usage string) *[]string {
return CommandLine.StringArrayP(name, "", value, usage)
}

View File

@ -82,6 +82,11 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) {
// StringSliceVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) {
f.VarP(newStringSliceValue(value, p), name, "", usage)
}
@ -93,6 +98,11 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s
// StringSliceVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func StringSliceVar(p *[]string, name string, value []string, usage string) {
CommandLine.VarP(newStringSliceValue(value, p), name, "", usage)
}
@ -104,6 +114,11 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage
// StringSlice defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string {
p := []string{}
f.StringSliceVarP(&p, name, "", value, usage)
@ -119,6 +134,11 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str
// StringSlice defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func StringSlice(name string, value []string, usage string) *[]string {
return CommandLine.StringSliceP(name, "", value, usage)
}