diff --git a/go.mod b/go.mod index 049e1a7..f4a281f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.16 require ( github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/charmbracelet/glamour v0.5.0 // indirect github.com/containerd/containerd v1.5.1 // indirect github.com/docker/docker v20.10.6+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect @@ -14,7 +15,7 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/sirupsen/logrus v1.8.1 // indirect + github.com/zclconf/go-cty v1.0.0 // indirect golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect - golang.org/x/sys v0.0.0-20210324051608-47abb6519492 // indirect google.golang.org/grpc v1.37.1 // indirect ) diff --git a/go.sum b/go.sum index 547e86e..e61f1fa 100644 --- a/go.sum +++ b/go.sum @@ -63,6 +63,8 @@ github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:H github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= +github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -76,6 +78,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= +github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -94,6 +98,8 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g= +github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc= github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -218,6 +224,8 @@ github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8l github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.4.0 h1:F1rxgk7p4uKjwIQxBs9oAXe5CqrXlCduYEJvrF4u93E= +github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= @@ -352,6 +360,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= +github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -416,6 +426,8 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= +github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= @@ -423,10 +435,18 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7 github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA= +github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y= +github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -445,6 +465,10 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= +github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= +github.com/muesli/termenv v0.9.0 h1:wnbOaGz+LUR3jNT0zOzinPnyDaCZUQRZj9GxK8eRVl8= +github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -453,6 +477,8 @@ github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -535,6 +561,9 @@ github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= @@ -590,6 +619,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -620,6 +650,10 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs= +github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg= +github.com/yuin/goldmark-emoji v1.0.1 h1:ctuWEyzGBwiucEqxzwe0SOYDXPAucOrE9NQC18Wa1os= +github.com/yuin/goldmark-emoji v1.0.1/go.mod h1:2w1E6FEWLcDQkoTE+7HU6QF1F6SLlNGjRIBbIZQFqkQ= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= @@ -725,6 +759,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -778,6 +814,7 @@ golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -803,6 +840,8 @@ golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492 h1:Paq34FxTluEPvVyayQqMPgHm+vTOrIifmcYxFBx9TLg= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da h1:b3NXsE2LusjYGGjL5bxEVZZORm/YEFFrWFjR8eFrw/c= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -812,6 +851,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/main.go b/main.go index 52f6851..2fee9ec 100644 --- a/main.go +++ b/main.go @@ -1,77 +1,78 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/everettraven/packageless/subcommands" - "github.com/everettraven/packageless/utils" -) - -func main() { - exitCode, exitErr := wrappedMain() - - if exitErr != nil { - fmt.Println(exitErr) - } - - os.Exit(exitCode) -} - -func wrappedMain() (int, error) { - //Create the utils for the subcommands - util := utils.NewUtility() - - //Create the copier for the subcommands - cp := &utils.CopyTool{} - - //Config file location - configLoc, err := os.UserHomeDir() - - if err != nil { - return 1, err - } - - configLoc = configLoc + "/.packageless/config.hcl" - - configBody, err := util.GetHCLBody(configLoc) - - if err != nil { - return 1, err - } - - //Parse the config file - parseOut, err := util.ParseBody(configBody, utils.Config{}) - - if err != nil { - return 1, err - } - - config := parseOut.(utils.Config) - - if strings.Contains(config.BaseDir, "~") { - homeDir, err := os.UserHomeDir() - if err != nil { - return 1, err - } - config.BaseDir = strings.Replace(config.BaseDir, "~", homeDir, 1) - } - - //Create the list of subcommands - scmds := []subcommands.Runner{ - subcommands.NewInstallCommand(util, cp, config), - subcommands.NewUninstallCommand(util, config), - subcommands.NewUpgradeCommand(util, cp, config), - subcommands.NewRunCommand(util, config), - subcommands.NewVersionCommand(), - subcommands.NewUpdateCommand(util, config), - } - - //Run the subcommands - if err := subcommands.SubCommand(os.Args[1:], scmds); err != nil { - return 1, err - } - - return 0, nil -} +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/everettraven/packageless/subcommands" + "github.com/everettraven/packageless/utils" +) + +func main() { + exitCode, exitErr := wrappedMain() + + if exitErr != nil { + errString := "# ERROR\n" + "**Encountered an error:** *%s*\n" + utils.NewUtility().RenderErrorMarkdown(fmt.Sprintf(errString, exitErr.Error())) + } + + os.Exit(exitCode) +} + +func wrappedMain() (int, error) { + //Create the utils for the subcommands + util := utils.NewUtility() + + //Create the copier for the subcommands + cp := &utils.CopyTool{} + + //Config file location + configLoc, err := os.UserHomeDir() + + if err != nil { + return 1, err + } + + configLoc = configLoc + "/.packageless/config.hcl" + + configBody, err := util.GetHCLBody(configLoc) + + if err != nil { + return 1, err + } + + //Parse the config file + parseOut, err := util.ParseBody(configBody, utils.Config{}) + + if err != nil { + return 1, err + } + + config := parseOut.(utils.Config) + + if strings.Contains(config.BaseDir, "~") { + homeDir, err := os.UserHomeDir() + if err != nil { + return 1, err + } + config.BaseDir = strings.Replace(config.BaseDir, "~", homeDir, 1) + } + + //Create the list of subcommands + scmds := []subcommands.Runner{ + subcommands.NewInstallCommand(util, cp, config), + subcommands.NewUninstallCommand(util, config), + subcommands.NewUpgradeCommand(util, cp, config), + subcommands.NewRunCommand(util, config), + subcommands.NewVersionCommand(util), + subcommands.NewUpdateCommand(util, config), + } + + //Run the subcommands + if err := subcommands.SubCommand(os.Args[1:], scmds); err != nil { + return 1, err + } + + return 0, nil +} diff --git a/subcommands/install_sc.go b/subcommands/install_sc.go index f768dde..73d5f9f 100644 --- a/subcommands/install_sc.go +++ b/subcommands/install_sc.go @@ -1,257 +1,262 @@ -package subcommands - -import ( - "errors" - "flag" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/docker/docker/client" - "github.com/everettraven/packageless/utils" -) - -//Install Sub-Command Object -type InstallCommand struct { - //FlagSet so that we can create a custom flag - fs *flag.FlagSet - - //String for the name of the pim to install - name string - - //Tools that can be used by the command - tools utils.Tools - - cp utils.Copier - - config utils.Config -} - -//Instantiation method for a new InstallCommand -func NewInstallCommand(tools utils.Tools, cp utils.Copier, config utils.Config) *InstallCommand { - //Create a new InstallCommand and set the FlagSet - ic := &InstallCommand{ - fs: flag.NewFlagSet("install", flag.ContinueOnError), - tools: tools, - cp: cp, - config: config, - } - - return ic -} - -//Name - Gets the name of the Sub-Command -func (ic *InstallCommand) Name() string { - return ic.fs.Name() -} - -//Init - Parses and Populates values of the Install subcommand -func (ic *InstallCommand) Init(args []string) error { - - if len(args) <= 0 { - return errors.New("No pim name was found. You must include the name of the pim you wish to install.") - } - - ic.name = args[0] - return nil -} - -//Run - Runs the install subcommand -func (ic *InstallCommand) Run() error { - //Create variables to use later - var found bool - var pim utils.PackageImage - var version utils.Version - - var pimName string - var pimVersion string - - if strings.Contains(ic.name, ":") { - split := strings.Split(ic.name, ":") - pimName = split[0] - pimVersion = split[1] - } else { - pimName = ic.name - pimVersion = "latest" - } - - pimConfigDir := ic.config.BaseDir + ic.config.PimsConfigDir - pimPath := pimConfigDir + pimName + ".hcl" - - pimDir := ic.config.BaseDir + ic.config.PimsDir - - //Make the pim config and pim directory if they do not already exist - err := ic.tools.MakeDir(pimConfigDir) - - if err != nil { - return err - } - - err = ic.tools.MakeDir(pimDir) - - if err != nil { - return err - } - - //Check if pim config already exists - if !ic.tools.FileExists(pimPath) { - err := ic.tools.FetchPimConfig(ic.config.RepositoryHost, pimName, pimConfigDir) - if err != nil { - return err - } - } - - //Create the Docker client - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return err - } - - pimListBody, err := ic.tools.GetHCLBody(pimPath) - - if err != nil { - return err - } - - //Parse the pim list - parseOut, err := ic.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) - - pims := parseOut.(utils.PimHCLUtil) - - //Check for errors - if err != nil { - return err - } - - //Look for the pim we want in the pim list - for _, pimItem := range pims.Pims { - //If we find it, set some variables and break - if pimItem.Name == pimName { - pim = pimItem - - for _, ver := range pim.Versions { - if ver.Version == pimVersion { - found = true - version = ver - break - } - } - } - } - - //Make sure we have found the pim in the pim list - if !found { - return errors.New("Could not find pim " + pimName + " with version '" + pimVersion + "' in the pim list") - } - - //Check if the corresponding pim image is already installed - imgExist, err := ic.tools.ImageExists(version.Image, cli) - - //Check for errors - if err != nil { - return err - } - - //If the image exists the pim is already installed - if imgExist { - return errors.New("pim " + pim.Name + " is already installed") - } - - fmt.Println("Installing", pim.Name+":"+version.Version) - //Pull the image down from Docker Hub - err = ic.tools.PullImage(version.Image, cli) - - if err != nil { - return err - } - - fmt.Println("Creating pim directories") - - //Create the base directory for the pim - err = ic.tools.MakeDir(pimDir + pim.BaseDir) - - if err != nil { - return err - } - - //Check the volumes and create the directories for them if they don't already exist - for _, vol := range version.Volumes { - //Make sure that a path is given. If not we already assume that the working directory will be mounted - if vol.Path != "" { - err = ic.tools.MakeDir(pimDir + vol.Path) - - if err != nil { - return err - } - } - } - - //Check and see if any files need to be copied from the container to one of the volumes on the host. - if len(version.Copies) > 0 { - - fmt.Println("Copying necessary files 1/3") - //Create the container so that we can copy the files over to the right places - containerID, err := ic.tools.CreateContainer(version.Image, cli) - - if err != nil { - return err - } - - fmt.Println("Copying necessary files 2/3") - //Copy the files from the container to the locations - for _, copy := range version.Copies { - err = ic.tools.CopyFromContainer(copy.Source, pimDir+copy.Dest, containerID, cli, ic.cp) - - if err != nil { - return err - } - } - - fmt.Println("Copying necessary files 3/3") - //Remove the Container - err = ic.tools.RemoveContainer(containerID, cli) - - if err != nil { - return err - } - - } - - //get the executable directory for setting the aliases - ex, err := os.Executable() - - if err != nil { - return err - } - - executableDir := filepath.Dir(ex) - - if ic.config.Alias { - //Set the alias for the command - fmt.Println("Setting Alias") - - if runtime.GOOS == "windows" { - if version.Version != "latest" { - err = ic.tools.AddAliasWin(pim.Name+":"+version.Version, executableDir) - } else { - err = ic.tools.AddAliasWin(pim.Name, executableDir) - } - } else { - if version.Version != "latest" { - err = ic.tools.AddAliasUnix(pim.Name+":"+version.Version, executableDir) - } else { - err = ic.tools.AddAliasUnix(pim.Name, executableDir) - } - } - - if err != nil { - return err - } - } - - fmt.Println(pim.Name, "successfully installed") - - return nil -} +package subcommands + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/docker/docker/client" + "github.com/everettraven/packageless/utils" +) + +//Install Sub-Command Object +type InstallCommand struct { + //FlagSet so that we can create a custom flag + fs *flag.FlagSet + + //String for the name of the pim to install + name string + + //Tools that can be used by the command + tools utils.Tools + + cp utils.Copier + + config utils.Config +} + +//Instantiation method for a new InstallCommand +func NewInstallCommand(tools utils.Tools, cp utils.Copier, config utils.Config) *InstallCommand { + //Create a new InstallCommand and set the FlagSet + ic := &InstallCommand{ + fs: flag.NewFlagSet("install", flag.ContinueOnError), + tools: tools, + cp: cp, + config: config, + } + + return ic +} + +//Name - Gets the name of the Sub-Command +func (ic *InstallCommand) Name() string { + return ic.fs.Name() +} + +//Init - Parses and Populates values of the Install subcommand +func (ic *InstallCommand) Init(args []string) error { + + if len(args) <= 0 { + return errors.New("No pim name was found. You must include the name of the pim you wish to install.") + } + + ic.name = args[0] + return nil +} + +//Run - Runs the install subcommand +func (ic *InstallCommand) Run() error { + //Create variables to use later + var found bool + var pim utils.PackageImage + var version utils.Version + + var pimName string + var pimVersion string + + if strings.Contains(ic.name, ":") { + split := strings.Split(ic.name, ":") + pimName = split[0] + pimVersion = split[1] + } else { + pimName = ic.name + pimVersion = "latest" + } + + pimConfigDir := ic.config.BaseDir + ic.config.PimsConfigDir + pimPath := pimConfigDir + pimName + ".hcl" + + pimDir := ic.config.BaseDir + ic.config.PimsDir + + //Make the pim config and pim directory if they do not already exist + err := ic.tools.MakeDir(pimConfigDir) + + if err != nil { + return err + } + + err = ic.tools.MakeDir(pimDir) + + if err != nil { + return err + } + + //Check if pim config already exists + if !ic.tools.FileExists(pimPath) { + err := ic.tools.FetchPimConfig(ic.config.RepositoryHost, pimName, pimConfigDir) + if err != nil { + return err + } + } + + //Create the Docker client + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return err + } + + pimListBody, err := ic.tools.GetHCLBody(pimPath) + + if err != nil { + return err + } + + //Parse the pim list + parseOut, err := ic.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) + + pims := parseOut.(utils.PimHCLUtil) + + //Check for errors + if err != nil { + return err + } + + //Look for the pim we want in the pim list + for _, pimItem := range pims.Pims { + //If we find it, set some variables and break + if pimItem.Name == pimName { + pim = pimItem + + for _, ver := range pim.Versions { + if ver.Version == pimVersion { + found = true + version = ver + break + } + } + } + } + + //Make sure we have found the pim in the pim list + if !found { + return errors.New("Could not find pim " + pimName + " with version '" + pimVersion + "' in the pim list") + } + + //Check if the corresponding pim image is already installed + imgExist, err := ic.tools.ImageExists(version.Image, cli) + + //Check for errors + if err != nil { + return err + } + + //If the image exists the pim is already installed + if imgExist { + return errors.New("pim " + pim.Name + " is already installed") + } + + ic.tools.RenderInfoMarkdown(fmt.Sprintf("**Installing**: *%s*", pim.Name+":"+version.Version)) + //Pull the image down from Docker Hub + ic.tools.RenderInfoMarkdown(fmt.Sprintf("- *Pulling image %s*", version.Image)) + err = ic.tools.PullImage(version.Image, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("- *Creating pim directories*") + + //Create the base directory for the pim + err = ic.tools.MakeDir(pimDir + pim.BaseDir) + + if err != nil { + return err + } + + //Check the volumes and create the directories for them if they don't already exist + for _, vol := range version.Volumes { + //Make sure that a path is given. If not we already assume that the working directory will be mounted + if vol.Path != "" { + err = ic.tools.MakeDir(pimDir + vol.Path) + + if err != nil { + return err + } + } + } + + //Check and see if any files need to be copied from the container to one of the volumes on the host. + if len(version.Copies) > 0 { + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (create container)*") + + //Create the container so that we can copy the files over to the right places + containerID, err := ic.tools.CreateContainer(version.Image, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (copy files from container)*") + + //Copy the files from the container to the locations + for _, copy := range version.Copies { + err = ic.tools.CopyFromContainer(copy.Source, pimDir+copy.Dest, containerID, cli, ic.cp) + + if err != nil { + return err + } + } + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (remove container)*") + + //Remove the Container + err = ic.tools.RemoveContainer(containerID, cli) + + if err != nil { + return err + } + + } + + //get the executable directory for setting the aliases + ex, err := os.Executable() + + if err != nil { + return err + } + + executableDir := filepath.Dir(ex) + + if ic.config.Alias { + //Set the alias for the command + ic.tools.RenderInfoMarkdown("- *Setting alias*") + + if runtime.GOOS == "windows" { + if version.Version != "latest" { + err = ic.tools.AddAliasWin(pim.Name+":"+version.Version, executableDir) + } else { + err = ic.tools.AddAliasWin(pim.Name, executableDir) + } + } else { + if version.Version != "latest" { + err = ic.tools.AddAliasUnix(pim.Name+":"+version.Version, executableDir) + } else { + err = ic.tools.AddAliasUnix(pim.Name, executableDir) + } + } + + if err != nil { + return err + } + } + + ic.tools.RenderInfoMarkdown("***") + ic.tools.RenderInfoMarkdown(fmt.Sprintf("*%s* **successfully installed**", pim.Name)) + + return nil +} diff --git a/subcommands/install_sc_test.go b/subcommands/install_sc_test.go index 63d4ef2..2d34091 100644 --- a/subcommands/install_sc_test.go +++ b/subcommands/install_sc_test.go @@ -1,1212 +1,1262 @@ -package subcommands - -import ( - "reflect" - "testing" - - "github.com/everettraven/packageless/utils" -) - -//Test to make sure the install subcommand has the proper name upon creation -func TestInstallName(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - if ic.Name() != "install" { - t.Fatal("The install subcommand's name should be: install | Subcommand Name: " + ic.Name()) - } -} - -//Test to make sure the install subcommand initializes correctly -func TestInstallInit(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - if ic.name != args[0] { - t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + ic.name) - } -} - -//Tests the flow of a correctly ran install subcommand -func TestInstallFlow(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - "AddAlias", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been pulled and make sure it matches from the MockUtility - var images []string - - //Lists of copy data - var copySources []string - var copyDests []string - - //directories to be created - var mkdirs []string - - //commands to have aliases created - var aliasCmds []string - - pimDir := config.BaseDir + config.PimsDir - pimConfigDir := config.BaseDir + config.PimsConfigDir - - mkdirs = append(mkdirs, pimConfigDir) - mkdirs = append(mkdirs, pimDir) - //Fill lists - for _, pim := range mu.Pim.Pims { - //Just use the first version - version := pim.Versions[0] - images = append(images, version.Image) - mkdirs = append(mkdirs, pimDir+pim.BaseDir) - aliasCmds = append(aliasCmds, pim.Name) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - mkdirs = append(mkdirs, pimDir+vol.Path) - } - - //Loop through the copies in the pim - for _, copy := range version.Copies { - copySources = append(copySources, copy.Source) - copyDests = append(copyDests, pimDir+copy.Dest) - } - - //Just use the first pim - break - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.PulledImgs) { - t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(mkdirs, mu.MadeDirs) { - t.Fatalf("Made directories does not match the expected directories. Made Directories: %v | Expected Made Directories: %v", mu.MadeDirs, mkdirs) - } - - //Make sure that the image passed into the CreateContainer function is correct - if !reflect.DeepEqual(mu.CreateImages, images) { - t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) - } - - //Make sure the proper ContainerID is being passed into the CopyFromContainer function - if mu.CopyContainerID != mu.ContainerID { - t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) - } - - //Ensure that the Copy sources are correct - if !reflect.DeepEqual(mu.CopySources, copySources) { - t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) - } - - //Ensure that the Copy destinations are correct - if !reflect.DeepEqual(mu.CopyDests, copyDests) { - t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) - } - - //Ensure that the ContainerID is passed correctly to the RemoveContainer function - if mu.RemoveContainerID != mu.ContainerID { - t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) - } - - //Make sure that the commands being passed to the alias functions is correct - if !reflect.DeepEqual(mu.CmdToAlias, aliasCmds) { - t.Fatalf("AddAlias Alias Commands does not match the expected Alias Commands. Alias Commands: %v | Expected Alias Commands: %v", mu.CmdToAlias, aliasCmds) - } - -} - -//Test the install subcommand getting an error after calling the GetHCLBody function -func TestInstallErrorAtGetHCLBody(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "GetHCLBody" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the ParseBody function -func TestInstallErrorAtParseBody(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "ParseBody" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the ImageExists function -func TestInstallErrorAtImageExists(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "ImageExists" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the PullImage function -func TestInstallErrorAtPullImage(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "PullImage" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the MakeDir function -func TestInstallErrorAtMakeDir(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "MakeDir" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the CreateContainer function -func TestInstallErrorAtCreateContainer(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "CreateContainer" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the CopyFromContainer function -func TestInstallErrorAtCopyFromContainer(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "CopyFromContainer" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - "CopyFromContainer", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the RemoveContainer function -func TestInstallErrorAtRemoveContainer(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "RemoveContainer" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand getting an error after calling the corresponding AddAlias function -func TestInstallErrorAtAddAlias(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "AddAlias" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - "AddAlias", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the install subcommand when ImageExists function returns true -func TestInstallImageExists(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - expectedErr := "pim python is already installed" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test the install subcommand with no arguments passed -func TestInstallNoPackage(t *testing.T) { - mu := utils.NewMockUtility() - - expectedErr := "No pim name was found. You must include the name of the pim you wish to install." - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{} - - err := ic.Init(args) - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } -} - -//Test the install subcommand if the passed in pim does not exist -func TestInstallNonExistPackage(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"nonexistent"} - - expectedErr := "Could not find pim nonexistent with version 'latest' in the pim list" - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test if the config has the Alias property set to false -func TestInstallAliasFalse(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: false, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been pulled and make sure it matches from the MockUtility - var images []string - - //Lists of copy data - var copySources []string - var copyDests []string - - //directories to be created - var mkdirs []string - - pimDir := config.BaseDir + config.PimsDir - pimConfigDir := config.BaseDir + config.PimsConfigDir - - mkdirs = append(mkdirs, pimConfigDir) - mkdirs = append(mkdirs, pimDir) - - //Fill lists - for _, pim := range mu.Pim.Pims { - //Just use the first version - version := pim.Versions[0] - images = append(images, version.Image) - mkdirs = append(mkdirs, pimDir+pim.BaseDir) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - mkdirs = append(mkdirs, pimDir+vol.Path) - } - - //Loop through the copies in the pim - for _, copy := range version.Copies { - copySources = append(copySources, copy.Source) - copyDests = append(copyDests, pimDir+copy.Dest) - } - - //Just use the first pim - break - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.PulledImgs) { - t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(mkdirs, mu.MadeDirs) { - t.Fatalf("Made directories does not match the expected directories. Made Directories: %v | Expected Made Directories: %v", mu.MadeDirs, mkdirs) - } - - //Make sure that the image passed into the CreateContainer function is correct - if !reflect.DeepEqual(mu.CreateImages, images) { - t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) - } - - //Make sure the proper ContainerID is being passed into the CopyFromContainer function - if mu.CopyContainerID != mu.ContainerID { - t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) - } - - //Ensure that the Copy sources are correct - if !reflect.DeepEqual(mu.CopySources, copySources) { - t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) - } - - //Ensure that the Copy destinations are correct - if !reflect.DeepEqual(mu.CopyDests, copyDests) { - t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) - } - - //Ensure that the ContainerID is passed correctly to the RemoveContainer function - if mu.RemoveContainerID != mu.ContainerID { - t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) - } - -} - -//Test the install function with a nonexistent pim version -func TestInstallNonExistVersion(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python:idontexist"} - - expectedErr := "Could not find pim python with version 'idontexist' in the pim list" - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -func TestInstallFlowPimConfigNotExist(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.PimConfigShouldExist = false - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "FetchPimConfig", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "MakeDir", - "MakeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - "AddAlias", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been pulled and make sure it matches from the MockUtility - var images []string - - //Lists of copy data - var copySources []string - var copyDests []string - - //directories to be created - var mkdirs []string - - //commands to have aliases created - var aliasCmds []string - - pimDir := config.BaseDir + config.PimsDir - pimConfigDir := config.BaseDir + config.PimsConfigDir - - mkdirs = append(mkdirs, pimConfigDir) - mkdirs = append(mkdirs, pimDir) - //Fill lists - for _, pim := range mu.Pim.Pims { - //Just use the first version - version := pim.Versions[0] - images = append(images, version.Image) - mkdirs = append(mkdirs, pimDir+pim.BaseDir) - aliasCmds = append(aliasCmds, pim.Name) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - mkdirs = append(mkdirs, pimDir+vol.Path) - } - - //Loop through the copies in the pim - for _, copy := range version.Copies { - copySources = append(copySources, copy.Source) - copyDests = append(copyDests, pimDir+copy.Dest) - } - - //Just use the first pim - break - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.PulledImgs) { - t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(mkdirs, mu.MadeDirs) { - t.Fatalf("Made directories does not match the expected directories. Made Directories: %v | Expected Made Directories: %v", mu.MadeDirs, mkdirs) - } - - //Make sure that the image passed into the CreateContainer function is correct - if !reflect.DeepEqual(mu.CreateImages, images) { - t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) - } - - //Make sure the proper ContainerID is being passed into the CopyFromContainer function - if mu.CopyContainerID != mu.ContainerID { - t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) - } - - //Ensure that the Copy sources are correct - if !reflect.DeepEqual(mu.CopySources, copySources) { - t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) - } - - //Ensure that the Copy destinations are correct - if !reflect.DeepEqual(mu.CopyDests, copyDests) { - t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) - } - - //Ensure that the ContainerID is passed correctly to the RemoveContainer function - if mu.RemoveContainerID != mu.ContainerID { - t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) - } - - //Make sure that the commands being passed to the alias functions is correct - if !reflect.DeepEqual(mu.CmdToAlias, aliasCmds) { - t.Fatalf("AddAlias Alias Commands does not match the expected Alias Commands. Alias Commands: %v | Expected Alias Commands: %v", mu.CmdToAlias, aliasCmds) - } -} - -func TestInstallErrorAtFetchPimConfig(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.PimConfigShouldExist = false - - mu.ErrorAt = "FetchPimConfig" - mu.ErrorMsg = "Test error message" - - ic := NewInstallCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Should have thrown an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatalf("Expected error: %s | Received: %s", mu.ErrorMsg, err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "MakeDir", - "MakeDir", - "FileExists", - "FetchPimConfig", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} +package subcommands + +import ( + "reflect" + "testing" + + "github.com/everettraven/packageless/utils" +) + +//Test to make sure the install subcommand has the proper name upon creation +func TestInstallName(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + if ic.Name() != "install" { + t.Fatal("The install subcommand's name should be: install | Subcommand Name: " + ic.Name()) + } +} + +//Test to make sure the install subcommand initializes correctly +func TestInstallInit(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + if ic.name != args[0] { + t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + ic.name) + } +} + +//Tests the flow of a correctly ran install subcommand +func TestInstallFlow(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "AddAlias", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been pulled and make sure it matches from the MockUtility + var images []string + + //Lists of copy data + var copySources []string + var copyDests []string + + //directories to be created + var mkdirs []string + + //commands to have aliases created + var aliasCmds []string + + pimDir := config.BaseDir + config.PimsDir + pimConfigDir := config.BaseDir + config.PimsConfigDir + + mkdirs = append(mkdirs, pimConfigDir) + mkdirs = append(mkdirs, pimDir) + //Fill lists + for _, pim := range mu.Pim.Pims { + //Just use the first version + version := pim.Versions[0] + images = append(images, version.Image) + mkdirs = append(mkdirs, pimDir+pim.BaseDir) + aliasCmds = append(aliasCmds, pim.Name) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + mkdirs = append(mkdirs, pimDir+vol.Path) + } + + //Loop through the copies in the pim + for _, copy := range version.Copies { + copySources = append(copySources, copy.Source) + copyDests = append(copyDests, pimDir+copy.Dest) + } + + //Just use the first pim + break + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.PulledImgs) { + t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(mkdirs, mu.MadeDirs) { + t.Fatalf("Made directories does not match the expected directories. Made Directories: %v | Expected Made Directories: %v", mu.MadeDirs, mkdirs) + } + + //Make sure that the image passed into the CreateContainer function is correct + if !reflect.DeepEqual(mu.CreateImages, images) { + t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) + } + + //Make sure the proper ContainerID is being passed into the CopyFromContainer function + if mu.CopyContainerID != mu.ContainerID { + t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) + } + + //Ensure that the Copy sources are correct + if !reflect.DeepEqual(mu.CopySources, copySources) { + t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) + } + + //Ensure that the Copy destinations are correct + if !reflect.DeepEqual(mu.CopyDests, copyDests) { + t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) + } + + //Ensure that the ContainerID is passed correctly to the RemoveContainer function + if mu.RemoveContainerID != mu.ContainerID { + t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) + } + + //Make sure that the commands being passed to the alias functions is correct + if !reflect.DeepEqual(mu.CmdToAlias, aliasCmds) { + t.Fatalf("AddAlias Alias Commands does not match the expected Alias Commands. Alias Commands: %v | Expected Alias Commands: %v", mu.CmdToAlias, aliasCmds) + } + +} + +//Test the install subcommand getting an error after calling the GetHCLBody function +func TestInstallErrorAtGetHCLBody(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "GetHCLBody" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the ParseBody function +func TestInstallErrorAtParseBody(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "ParseBody" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the ImageExists function +func TestInstallErrorAtImageExists(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "ImageExists" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the PullImage function +func TestInstallErrorAtPullImage(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "PullImage" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the MakeDir function +func TestInstallErrorAtMakeDir(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "MakeDir" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the CreateContainer function +func TestInstallErrorAtCreateContainer(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "CreateContainer" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the CopyFromContainer function +func TestInstallErrorAtCopyFromContainer(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "CopyFromContainer" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the RemoveContainer function +func TestInstallErrorAtRemoveContainer(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "RemoveContainer" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand getting an error after calling the corresponding AddAlias function +func TestInstallErrorAtAddAlias(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "AddAlias" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "AddAlias", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the install subcommand when ImageExists function returns true +func TestInstallImageExists(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + expectedErr := "pim python is already installed" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test the install subcommand with no arguments passed +func TestInstallNoPackage(t *testing.T) { + mu := utils.NewMockUtility() + + expectedErr := "No pim name was found. You must include the name of the pim you wish to install." + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{} + + err := ic.Init(args) + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } +} + +//Test the install subcommand if the passed in pim does not exist +func TestInstallNonExistPackage(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"nonexistent"} + + expectedErr := "Could not find pim nonexistent with version 'latest' in the pim list" + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test if the config has the Alias property set to false +func TestInstallAliasFalse(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: false, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been pulled and make sure it matches from the MockUtility + var images []string + + //Lists of copy data + var copySources []string + var copyDests []string + + //directories to be created + var mkdirs []string + + pimDir := config.BaseDir + config.PimsDir + pimConfigDir := config.BaseDir + config.PimsConfigDir + + mkdirs = append(mkdirs, pimConfigDir) + mkdirs = append(mkdirs, pimDir) + + //Fill lists + for _, pim := range mu.Pim.Pims { + //Just use the first version + version := pim.Versions[0] + images = append(images, version.Image) + mkdirs = append(mkdirs, pimDir+pim.BaseDir) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + mkdirs = append(mkdirs, pimDir+vol.Path) + } + + //Loop through the copies in the pim + for _, copy := range version.Copies { + copySources = append(copySources, copy.Source) + copyDests = append(copyDests, pimDir+copy.Dest) + } + + //Just use the first pim + break + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.PulledImgs) { + t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(mkdirs, mu.MadeDirs) { + t.Fatalf("Made directories does not match the expected directories. Made Directories: %v | Expected Made Directories: %v", mu.MadeDirs, mkdirs) + } + + //Make sure that the image passed into the CreateContainer function is correct + if !reflect.DeepEqual(mu.CreateImages, images) { + t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) + } + + //Make sure the proper ContainerID is being passed into the CopyFromContainer function + if mu.CopyContainerID != mu.ContainerID { + t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) + } + + //Ensure that the Copy sources are correct + if !reflect.DeepEqual(mu.CopySources, copySources) { + t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) + } + + //Ensure that the Copy destinations are correct + if !reflect.DeepEqual(mu.CopyDests, copyDests) { + t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) + } + + //Ensure that the ContainerID is passed correctly to the RemoveContainer function + if mu.RemoveContainerID != mu.ContainerID { + t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) + } + +} + +//Test the install function with a nonexistent pim version +func TestInstallNonExistVersion(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python:idontexist"} + + expectedErr := "Could not find pim python with version 'idontexist' in the pim list" + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +func TestInstallFlowPimConfigNotExist(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.PimConfigShouldExist = false + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "FetchPimConfig", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "MakeDir", + "MakeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "AddAlias", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been pulled and make sure it matches from the MockUtility + var images []string + + //Lists of copy data + var copySources []string + var copyDests []string + + //directories to be created + var mkdirs []string + + //commands to have aliases created + var aliasCmds []string + + pimDir := config.BaseDir + config.PimsDir + pimConfigDir := config.BaseDir + config.PimsConfigDir + + mkdirs = append(mkdirs, pimConfigDir) + mkdirs = append(mkdirs, pimDir) + //Fill lists + for _, pim := range mu.Pim.Pims { + //Just use the first version + version := pim.Versions[0] + images = append(images, version.Image) + mkdirs = append(mkdirs, pimDir+pim.BaseDir) + aliasCmds = append(aliasCmds, pim.Name) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + mkdirs = append(mkdirs, pimDir+vol.Path) + } + + //Loop through the copies in the pim + for _, copy := range version.Copies { + copySources = append(copySources, copy.Source) + copyDests = append(copyDests, pimDir+copy.Dest) + } + + //Just use the first pim + break + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.PulledImgs) { + t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(mkdirs, mu.MadeDirs) { + t.Fatalf("Made directories does not match the expected directories. Made Directories: %v | Expected Made Directories: %v", mu.MadeDirs, mkdirs) + } + + //Make sure that the image passed into the CreateContainer function is correct + if !reflect.DeepEqual(mu.CreateImages, images) { + t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) + } + + //Make sure the proper ContainerID is being passed into the CopyFromContainer function + if mu.CopyContainerID != mu.ContainerID { + t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) + } + + //Ensure that the Copy sources are correct + if !reflect.DeepEqual(mu.CopySources, copySources) { + t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) + } + + //Ensure that the Copy destinations are correct + if !reflect.DeepEqual(mu.CopyDests, copyDests) { + t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) + } + + //Ensure that the ContainerID is passed correctly to the RemoveContainer function + if mu.RemoveContainerID != mu.ContainerID { + t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) + } + + //Make sure that the commands being passed to the alias functions is correct + if !reflect.DeepEqual(mu.CmdToAlias, aliasCmds) { + t.Fatalf("AddAlias Alias Commands does not match the expected Alias Commands. Alias Commands: %v | Expected Alias Commands: %v", mu.CmdToAlias, aliasCmds) + } +} + +func TestInstallErrorAtFetchPimConfig(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.PimConfigShouldExist = false + + mu.ErrorAt = "FetchPimConfig" + mu.ErrorMsg = "Test error message" + + ic := NewInstallCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Should have thrown an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatalf("Expected error: %s | Received: %s", mu.ErrorMsg, err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "MakeDir", + "MakeDir", + "FileExists", + "FetchPimConfig", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} diff --git a/subcommands/subcommand.go b/subcommands/subcommand.go index 5662c44..01268ec 100644 --- a/subcommands/subcommand.go +++ b/subcommands/subcommand.go @@ -1,59 +1,55 @@ -package subcommands - -import ( - "errors" - "fmt" -) - -//Runner - Interface to enable easy interactions with the different subcommand objects -type Runner interface { - Init(args []string) error - Run() error - Name() string -} - -//SubCommand - Helper function that handles setting up and running subcommands -func SubCommand(args []string, scmds []Runner) error { - if len(args) < 1 { - return errors.New("A subcommand must be passed") - } - - subcommand := args[0] - - args = args[1:] - - for _, cmd := range scmds { - if cmd.Name() == subcommand { - //Subcommands that take multiple pims as arguments - if subcommand == "install" || subcommand == "upgrade" || subcommand == "uninstall" { - //Execute subcommand for each pim in arguments - for _, pim := range args { - p := []string{pim} - err := cmd.Init(p) - - if err != nil { - return err - } - - err = cmd.Run() - - if err != nil { - return err - } - } - } else { - err := cmd.Init(args) - - if err != nil { - return err - } - - return cmd.Run() - } - - return nil - } - } - - return fmt.Errorf("Unknown subcommand %s", subcommand) -} +package subcommands + +import ( + "errors" + "fmt" +) + +//Runner - Interface to enable easy interactions with the different subcommand objects +type Runner interface { + Init(args []string) error + Run() error + Name() string +} + +//SubCommand - Helper function that handles setting up and running subcommands +func SubCommand(args []string, scmds []Runner) error { + if len(args) < 1 { + return errors.New("A subcommand must be passed") + } + + subcommand := args[0] + + args = args[1:] + + for _, cmd := range scmds { + if cmd.Name() == subcommand { + //Subcommands that take multiple pims as arguments + if subcommand == "install" || subcommand == "upgrade" || subcommand == "uninstall" { + //Execute subcommand for each pim in arguments + for _, pim := range args { + p := []string{pim} + err := cmd.Init(p) + + if err != nil { + return err + } + + return cmd.Run() + } + } else { + err := cmd.Init(args) + + if err != nil { + return err + } + + return cmd.Run() + } + + return nil + } + } + + return fmt.Errorf("Unknown subcommand %s", subcommand) +} diff --git a/subcommands/uninstall_sc.go b/subcommands/uninstall_sc.go index 84fdabb..2a740fd 100644 --- a/subcommands/uninstall_sc.go +++ b/subcommands/uninstall_sc.go @@ -1,212 +1,215 @@ -package subcommands - -import ( - "errors" - "flag" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - - "github.com/docker/docker/client" - "github.com/everettraven/packageless/utils" -) - -//Uninstall Sub-Command Object -type UninstallCommand struct { - //FlagSet so that we can create a custom flag - fs *flag.FlagSet - - //String for the name of the pim to Uninstall - name string - - tools utils.Tools - - config utils.Config -} - -//Instantiation method for a new UninstallCommand -func NewUninstallCommand(tools utils.Tools, config utils.Config) *UninstallCommand { - //Create a new UninstallCommand and set the FlagSet - uc := &UninstallCommand{ - fs: flag.NewFlagSet("uninstall", flag.ContinueOnError), - tools: tools, - config: config, - } - - return uc -} - -//Name - Gets the name of the Sub-Command -func (uc *UninstallCommand) Name() string { - return uc.fs.Name() -} - -//Init - Parses and Populates values of the Uninstall subcommand -func (uc *UninstallCommand) Init(args []string) error { - - if len(args) <= 0 { - return errors.New("No pim name was found. You must include the name of the pim you wish to uninstall.") - } - - uc.name = args[0] - - return nil -} - -//Uninstall - Uninstalls the Uninstall subcommand -func (uc *UninstallCommand) Run() error { - //Create variables to use later - var found bool - var pim utils.PackageImage - var version utils.Version - - var pimName string - var pimVersion string - - if strings.Contains(uc.name, ":") { - split := strings.Split(uc.name, ":") - pimName = split[0] - pimVersion = split[1] - } else { - pimName = uc.name - pimVersion = "latest" - } - - pimConfigDir := uc.config.BaseDir + uc.config.PimsConfigDir - pimPath := pimConfigDir + pimName + ".hcl" - - pimDir := uc.config.BaseDir + uc.config.PimsDir - - //Check if pim config already exists - if !uc.tools.FileExists(pimPath) { - return errors.New("configuration for pim: " + pimName + " could not be found. Have you installed " + pimName + "?") - } - - //Create the Docker client - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return err - } - - pimListBody, err := uc.tools.GetHCLBody(pimPath) - - if err != nil { - return err - } - - //Parse the pim list - parseOut, err := uc.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) - - //Check for errors - if err != nil { - return err - } - - pims := parseOut.(utils.PimHCLUtil) - - //Check for errors - if err != nil { - return err - } - - //Look for the pim we want in the pim list - for _, pimItem := range pims.Pims { - //If we find it, set some variables and break - if pimItem.Name == pimName { - pim = pimItem - - for _, ver := range pim.Versions { - if ver.Version == pimVersion { - found = true - version = ver - break - } - } - } - } - - //Make sure we have found the pim in the pim list - if !found { - return errors.New("Could not find pim " + pimName + " with version '" + pimVersion + "' in the pim configuration") - } - - //Check if the corresponding pim is already Uninstalled - imgExist, err := uc.tools.ImageExists(version.Image, cli) - - //Check for errors - if err != nil { - return err - } - - //If the image doesn't exist it can't be uninstalled - if !imgExist { - return errors.New("pim " + pim.Name + " with version '" + version.Version + "' is not installed.") - } - - fmt.Println("Removing", pim.Name+":"+version.Version) - - //Check for the directories that correspond to this pims volumes - fmt.Println("Removing pim directories") - - //Check the volumes and remove the directories if they exist - for _, vol := range version.Volumes { - //Make sure that a path is given. - if vol.Path != "" { - err = uc.tools.RemoveDir(pimDir + vol.Path) - - if err != nil { - return err - } - } - } - - if err != nil { - return err - } - - fmt.Println("Removing Image") - - //Remove the image - err = uc.tools.RemoveImage(version.Image, cli) - - //Check for errors - if err != nil { - return err - } - - //get the executable directory for removing the aliases - ex, err := os.Executable() - - if err != nil { - return err - } - - executableDir := filepath.Dir(ex) - - if uc.config.Alias { - //Remove aliases - fmt.Println("Removing Alias") - - if runtime.GOOS == "windows" { - if version.Version != "latest" { - err = uc.tools.RemoveAliasWin(pim.Name+":"+version.Version, executableDir) - } else { - err = uc.tools.RemoveAliasWin(pim.Name, executableDir) - } - } else { - if version.Version != "latest" { - err = uc.tools.RemoveAliasUnix(pim.Name+":"+version.Version, executableDir) - } else { - err = uc.tools.RemoveAliasUnix(pim.Name, executableDir) - } - } - - if err != nil { - return err - } - } - - return nil -} +package subcommands + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/docker/docker/client" + "github.com/everettraven/packageless/utils" +) + +//Uninstall Sub-Command Object +type UninstallCommand struct { + //FlagSet so that we can create a custom flag + fs *flag.FlagSet + + //String for the name of the pim to Uninstall + name string + + tools utils.Tools + + config utils.Config +} + +//Instantiation method for a new UninstallCommand +func NewUninstallCommand(tools utils.Tools, config utils.Config) *UninstallCommand { + //Create a new UninstallCommand and set the FlagSet + uc := &UninstallCommand{ + fs: flag.NewFlagSet("uninstall", flag.ContinueOnError), + tools: tools, + config: config, + } + + return uc +} + +//Name - Gets the name of the Sub-Command +func (uc *UninstallCommand) Name() string { + return uc.fs.Name() +} + +//Init - Parses and Populates values of the Uninstall subcommand +func (uc *UninstallCommand) Init(args []string) error { + + if len(args) <= 0 { + return errors.New("No pim name was found. You must include the name of the pim you wish to uninstall.") + } + + uc.name = args[0] + + return nil +} + +//Uninstall - Uninstalls the Uninstall subcommand +func (uc *UninstallCommand) Run() error { + //Create variables to use later + var found bool + var pim utils.PackageImage + var version utils.Version + + var pimName string + var pimVersion string + + if strings.Contains(uc.name, ":") { + split := strings.Split(uc.name, ":") + pimName = split[0] + pimVersion = split[1] + } else { + pimName = uc.name + pimVersion = "latest" + } + + pimConfigDir := uc.config.BaseDir + uc.config.PimsConfigDir + pimPath := pimConfigDir + pimName + ".hcl" + + pimDir := uc.config.BaseDir + uc.config.PimsDir + + //Check if pim config already exists + if !uc.tools.FileExists(pimPath) { + return errors.New("configuration for pim: " + pimName + " could not be found. Have you installed " + pimName + "?") + } + + //Create the Docker client + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return err + } + + pimListBody, err := uc.tools.GetHCLBody(pimPath) + + if err != nil { + return err + } + + //Parse the pim list + parseOut, err := uc.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) + + //Check for errors + if err != nil { + return err + } + + pims := parseOut.(utils.PimHCLUtil) + + //Check for errors + if err != nil { + return err + } + + //Look for the pim we want in the pim list + for _, pimItem := range pims.Pims { + //If we find it, set some variables and break + if pimItem.Name == pimName { + pim = pimItem + + for _, ver := range pim.Versions { + if ver.Version == pimVersion { + found = true + version = ver + break + } + } + } + } + + //Make sure we have found the pim in the pim list + if !found { + return errors.New("Could not find pim " + pimName + " with version '" + pimVersion + "' in the pim configuration") + } + + //Check if the corresponding pim is already Uninstalled + imgExist, err := uc.tools.ImageExists(version.Image, cli) + + //Check for errors + if err != nil { + return err + } + + //If the image doesn't exist it can't be uninstalled + if !imgExist { + return errors.New("pim " + pim.Name + " with version '" + version.Version + "' is not installed.") + } + + uc.tools.RenderInfoMarkdown(fmt.Sprintf("**Uninstalling**: *%s*", pim.Name+":"+version.Version)) + + //Check for the directories that correspond to this pims volumes + uc.tools.RenderInfoMarkdown("- *Removing pim directories*") + + //Check the volumes and remove the directories if they exist + for _, vol := range version.Volumes { + //Make sure that a path is given. + if vol.Path != "" { + err = uc.tools.RemoveDir(pimDir + vol.Path) + + if err != nil { + return err + } + } + } + + if err != nil { + return err + } + + uc.tools.RenderInfoMarkdown("- *Removing image*") + + //Remove the image + err = uc.tools.RemoveImage(version.Image, cli) + + //Check for errors + if err != nil { + return err + } + + //get the executable directory for removing the aliases + ex, err := os.Executable() + + if err != nil { + return err + } + + executableDir := filepath.Dir(ex) + + if uc.config.Alias { + //Remove aliases + uc.tools.RenderInfoMarkdown("- *Removing Alias*") + + if runtime.GOOS == "windows" { + if version.Version != "latest" { + err = uc.tools.RemoveAliasWin(pim.Name+":"+version.Version, executableDir) + } else { + err = uc.tools.RemoveAliasWin(pim.Name, executableDir) + } + } else { + if version.Version != "latest" { + err = uc.tools.RemoveAliasUnix(pim.Name+":"+version.Version, executableDir) + } else { + err = uc.tools.RemoveAliasUnix(pim.Name, executableDir) + } + } + + if err != nil { + return err + } + } + + uc.tools.RenderInfoMarkdown("***") + uc.tools.RenderInfoMarkdown(fmt.Sprintf("*%s* **successfully uninstalled**", pim.Name)) + + return nil +} diff --git a/subcommands/uninstall_sc_test.go b/subcommands/uninstall_sc_test.go index b629397..1c98319 100644 --- a/subcommands/uninstall_sc_test.go +++ b/subcommands/uninstall_sc_test.go @@ -1,759 +1,779 @@ -package subcommands - -import ( - "reflect" - "testing" - - "github.com/everettraven/packageless/utils" -) - -//Test to make sure the uninstall subcommand has the proper name upon creation -func TestUninstallName(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - if uc.Name() != "uninstall" { - t.Fatal("The uninstall subcommand's name should be: uninstall | Subcommand Name: " + uc.Name()) - } -} - -//Test to make sure the uninstall subcommand initializes correctly -func TestUninstallInit(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - if uc.name != args[0] { - t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + uc.name) - } -} - -//Test the uninstall subcommand with no pim specified -func TestUninstallNoPackage(t *testing.T) { - mu := utils.NewMockUtility() - - expectedErr := "No pim name was found. You must include the name of the pim you wish to uninstall." - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{} - - err := uc.Init(args) - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } -} - -//Test the uninstall subcommand with a non existent pim specified -func TestUninstallNonExistPackage(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"nonexistent"} - - expectedErr := "Could not find pim nonexistent with version 'latest' in the pim configuration" - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Tests the uninstall subcommand if the image does not exist -func TestUninstallImageNotExist(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = false - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - expectedErr := "pim python with version 'latest' is not installed." - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Tests the flow of a correctly ran uninstall subcommand -func TestUninstallFlow(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "RemoveDir", - "RemoveImage", - "RemoveAlias", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been removed and make sure it matches from the MockUtility - var images []string - - //directories to be removed - var rmdirs []string - - //commands that should have had their aliases removed - var aliasCmds []string - - pimDir := config.BaseDir + config.PimsDir - - //Fill lists - for _, pim := range mu.Pim.Pims { - //Just use the first version - version := pim.Versions[0] - images = append(images, version.Image) - aliasCmds = append(aliasCmds, pim.Name) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - rmdirs = append(rmdirs, pimDir+vol.Path) - } - - //Just use the first pim for the test - break - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.RemovedImgs) { - t.Fatalf("Removed Images does not match the expected Removed Images. Removed Images: %v | Expected Removed Images: %v", mu.RemovedImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(rmdirs, mu.RemovedDirs) { - t.Fatalf("Removed directories does not match the expected directories. Removed Directories: %v | Expected Removed Directories: %v", mu.RemovedDirs, rmdirs) - } - - //Make sure that the commands being passed to the alias functions is correct - if !reflect.DeepEqual(mu.CmdToAlias, aliasCmds) { - t.Fatalf("AddAlias Alias Commands does not match the expected Alias Commands. Alias Commands: %v | Expected Alias Commands: %v", mu.CmdToAlias, aliasCmds) - } -} - -//Test if an error happens at the GetHCLBody function -func TestUninstallErrorAtGetHCLBody(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "GetHCLBody" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test if there is an error from the ParseBody function -func TestUninstallErrorAtParseBody(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "ParseBody" - - config := utils.Config{ - BaseDir: "./", - PortInc: 1, - StartPort: 5000, - Alias: false, - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test if there is an error from the ImageExists function -func TestUninstallErrorAtImageExists(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "ImageExists" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test if there is an error from the RemoveDir function -func TestUninstallErrorAtRemoveDir(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "RemoveDir" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "RemoveDir", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test if there is an error from the RemoveAlias function -func TestUninstallErrorAtRemoveAlias(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "RemoveAlias" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "RemoveDir", - "RemoveImage", - "RemoveAlias", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test if there is an error from the RemoveImage function -func TestUninstallErrorAtRemoveImage(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "RemoveImage" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "RemoveDir", - "RemoveImage", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test uninstall when config Alias attribute is set to false -func TestUninstallAliasFalse(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - config := utils.Config{ - BaseDir: "./", - PortInc: 1, - StartPort: 5000, - Alias: false, - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python"} - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "RemoveDir", - "RemoveImage", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been removed and make sure it matches from the MockUtility - var images []string - - //directories to be removed - var rmdirs []string - - pimDir := config.BaseDir + config.PimsDir - - //Fill lists - for _, pim := range mu.Pim.Pims { - //Just use the first version - version := pim.Versions[0] - images = append(images, version.Image) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - rmdirs = append(rmdirs, pimDir+vol.Path) - } - - //Just use the first pim - break - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.RemovedImgs) { - t.Fatalf("Removed Images does not match the expected Removed Images. Removed Images: %v | Expected Removed Images: %v", mu.RemovedImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(rmdirs, mu.RemovedDirs) { - t.Fatalf("Removed directories does not match the expected directories. Removed Directories: %v | Expected Removed Directories: %v", mu.RemovedDirs, rmdirs) - } -} - -//Test the uninstall subcommand with a pim with a nonexistent version specified -func TestUninstallNonExistVersion(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - uc := NewUninstallCommand(mu, config) - - args := []string{"python:idontexist"} - - expectedErr := "Could not find pim python with version 'idontexist' in the pim configuration" - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -func TestUninstallPimConfigFileNotExist(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.PimConfigShouldExist = false - - uc := NewUninstallCommand(mu, config) - - args := []string{"python:idontexist"} - - expectedErr := "configuration for pim: python could not be found. Have you installed python?" - - err := uc.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = uc.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} +package subcommands + +import ( + "reflect" + "testing" + + "github.com/everettraven/packageless/utils" +) + +//Test to make sure the uninstall subcommand has the proper name upon creation +func TestUninstallName(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + if uc.Name() != "uninstall" { + t.Fatal("The uninstall subcommand's name should be: uninstall | Subcommand Name: " + uc.Name()) + } +} + +//Test to make sure the uninstall subcommand initializes correctly +func TestUninstallInit(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + if uc.name != args[0] { + t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + uc.name) + } +} + +//Test the uninstall subcommand with no pim specified +func TestUninstallNoPackage(t *testing.T) { + mu := utils.NewMockUtility() + + expectedErr := "No pim name was found. You must include the name of the pim you wish to uninstall." + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{} + + err := uc.Init(args) + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } +} + +//Test the uninstall subcommand with a non existent pim specified +func TestUninstallNonExistPackage(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"nonexistent"} + + expectedErr := "Could not find pim nonexistent with version 'latest' in the pim configuration" + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Tests the uninstall subcommand if the image does not exist +func TestUninstallImageNotExist(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = false + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + expectedErr := "pim python with version 'latest' is not installed." + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Tests the flow of a correctly ran uninstall subcommand +func TestUninstallFlow(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "RemoveDir", + "RenderInfoMarkdown", + "RemoveImage", + "RenderInfoMarkdown", + "RemoveAlias", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been removed and make sure it matches from the MockUtility + var images []string + + //directories to be removed + var rmdirs []string + + //commands that should have had their aliases removed + var aliasCmds []string + + pimDir := config.BaseDir + config.PimsDir + + //Fill lists + for _, pim := range mu.Pim.Pims { + //Just use the first version + version := pim.Versions[0] + images = append(images, version.Image) + aliasCmds = append(aliasCmds, pim.Name) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + rmdirs = append(rmdirs, pimDir+vol.Path) + } + + //Just use the first pim for the test + break + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.RemovedImgs) { + t.Fatalf("Removed Images does not match the expected Removed Images. Removed Images: %v | Expected Removed Images: %v", mu.RemovedImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(rmdirs, mu.RemovedDirs) { + t.Fatalf("Removed directories does not match the expected directories. Removed Directories: %v | Expected Removed Directories: %v", mu.RemovedDirs, rmdirs) + } + + //Make sure that the commands being passed to the alias functions is correct + if !reflect.DeepEqual(mu.CmdToAlias, aliasCmds) { + t.Fatalf("AddAlias Alias Commands does not match the expected Alias Commands. Alias Commands: %v | Expected Alias Commands: %v", mu.CmdToAlias, aliasCmds) + } +} + +//Test if an error happens at the GetHCLBody function +func TestUninstallErrorAtGetHCLBody(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "GetHCLBody" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test if there is an error from the ParseBody function +func TestUninstallErrorAtParseBody(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "ParseBody" + + config := utils.Config{ + BaseDir: "./", + PortInc: 1, + StartPort: 5000, + Alias: false, + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test if there is an error from the ImageExists function +func TestUninstallErrorAtImageExists(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "ImageExists" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test if there is an error from the RemoveDir function +func TestUninstallErrorAtRemoveDir(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "RemoveDir" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "RemoveDir", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test if there is an error from the RemoveAlias function +func TestUninstallErrorAtRemoveAlias(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "RemoveAlias" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "RemoveDir", + "RenderInfoMarkdown", + "RemoveImage", + "RenderInfoMarkdown", + "RemoveAlias", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test if there is an error from the RemoveImage function +func TestUninstallErrorAtRemoveImage(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "RemoveImage" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "RemoveDir", + "RenderInfoMarkdown", + "RemoveImage", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test uninstall when config Alias attribute is set to false +func TestUninstallAliasFalse(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + config := utils.Config{ + BaseDir: "./", + PortInc: 1, + StartPort: 5000, + Alias: false, + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python"} + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "RemoveDir", + "RenderInfoMarkdown", + "RemoveImage", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been removed and make sure it matches from the MockUtility + var images []string + + //directories to be removed + var rmdirs []string + + pimDir := config.BaseDir + config.PimsDir + + //Fill lists + for _, pim := range mu.Pim.Pims { + //Just use the first version + version := pim.Versions[0] + images = append(images, version.Image) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + rmdirs = append(rmdirs, pimDir+vol.Path) + } + + //Just use the first pim + break + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.RemovedImgs) { + t.Fatalf("Removed Images does not match the expected Removed Images. Removed Images: %v | Expected Removed Images: %v", mu.RemovedImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(rmdirs, mu.RemovedDirs) { + t.Fatalf("Removed directories does not match the expected directories. Removed Directories: %v | Expected Removed Directories: %v", mu.RemovedDirs, rmdirs) + } +} + +//Test the uninstall subcommand with a pim with a nonexistent version specified +func TestUninstallNonExistVersion(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + uc := NewUninstallCommand(mu, config) + + args := []string{"python:idontexist"} + + expectedErr := "Could not find pim python with version 'idontexist' in the pim configuration" + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +func TestUninstallPimConfigFileNotExist(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.PimConfigShouldExist = false + + uc := NewUninstallCommand(mu, config) + + args := []string{"python:idontexist"} + + expectedErr := "configuration for pim: python could not be found. Have you installed python?" + + err := uc.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = uc.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} diff --git a/subcommands/update_sc.go b/subcommands/update_sc.go index f4ae898..cf5cf07 100644 --- a/subcommands/update_sc.go +++ b/subcommands/update_sc.go @@ -1,78 +1,78 @@ -package subcommands - -import ( - "errors" - "flag" - "fmt" - - "github.com/everettraven/packageless/utils" -) - -type UpdateCommand struct { - //FlagSet for the Update command - fs *flag.FlagSet - //Name of the pim to be updated - name string - - tools utils.Tools - - config utils.Config -} - -//Instantiation method for a new UpdateCommand -func NewUpdateCommand(tools utils.Tools, config utils.Config) *UpdateCommand { - //Create a new InstallCommand and set the FlagSet - uc := &UpdateCommand{ - fs: flag.NewFlagSet("update", flag.ContinueOnError), - tools: tools, - config: config, - } - - return uc -} - -//Name - Gets the name of the Sub-Command -func (uc *UpdateCommand) Name() string { - return uc.fs.Name() -} - -//Initialize the command, for this particular subcommand we should just do nothing -func (uc *UpdateCommand) Init(args []string) error { - if len(args) <= 0 { - fmt.Println("No pim specified, updating all currently installed pim configurations.") - } else { - uc.name = args[0] - } - return nil -} - -//Run the command, this command should fetch the pim config for -//either the specified package or all currently installed packages -func (uc *UpdateCommand) Run() error { - pimConfigDir := uc.config.BaseDir + uc.config.PimsConfigDir - //Get list of installed pims - pims, err := uc.tools.GetListOfInstalledPimConfigs(pimConfigDir) - - if err != nil { - return errors.New("Encountered an error while trying to fetch list of installed pim configuration files: " + err.Error()) - } - - //Loop and download most recent pim configuration for pims - for _, pim := range pims { - - //If a specific package name is specified, skip over all the installed pims that are not the specified one - if uc.name != "" { - if pim != uc.name { - continue - } - } - - fmt.Println("Updating pim: " + pim) - err = uc.tools.FetchPimConfig(uc.config.RepositoryHost, pim, pimConfigDir) - if err != nil { - return errors.New("Encountered an error while trying to fetch the latest pim configuration file for pim '" + pim + "': " + err.Error()) - } - } - - return nil -} +package subcommands + +import ( + "errors" + "flag" + "fmt" + + "github.com/everettraven/packageless/utils" +) + +type UpdateCommand struct { + //FlagSet for the Update command + fs *flag.FlagSet + //Name of the pim to be updated + name string + + tools utils.Tools + + config utils.Config +} + +//Instantiation method for a new UpdateCommand +func NewUpdateCommand(tools utils.Tools, config utils.Config) *UpdateCommand { + //Create a new InstallCommand and set the FlagSet + uc := &UpdateCommand{ + fs: flag.NewFlagSet("update", flag.ContinueOnError), + tools: tools, + config: config, + } + + return uc +} + +//Name - Gets the name of the Sub-Command +func (uc *UpdateCommand) Name() string { + return uc.fs.Name() +} + +//Initialize the command, for this particular subcommand we should just do nothing +func (uc *UpdateCommand) Init(args []string) error { + if len(args) <= 0 { + uc.tools.RenderInfoMarkdown("*No pim specified, updating all currently installed pim configurations*") + } else { + uc.name = args[0] + } + return nil +} + +//Run the command, this command should fetch the pim config for +//either the specified package or all currently installed packages +func (uc *UpdateCommand) Run() error { + pimConfigDir := uc.config.BaseDir + uc.config.PimsConfigDir + //Get list of installed pims + pims, err := uc.tools.GetListOfInstalledPimConfigs(pimConfigDir) + + if err != nil { + return errors.New("Encountered an error while trying to fetch list of installed pim configuration files: " + err.Error()) + } + + //Loop and download most recent pim configuration for pims + for _, pim := range pims { + + //If a specific package name is specified, skip over all the installed pims that are not the specified one + if uc.name != "" { + if pim != uc.name { + continue + } + } + + uc.tools.RenderInfoMarkdown(fmt.Sprintf("**Updating pim**: *%s*", pim)) + err = uc.tools.FetchPimConfig(uc.config.RepositoryHost, pim, pimConfigDir) + if err != nil { + return errors.New("Encountered an error while trying to fetch the latest pim configuration file for pim '" + pim + "': " + err.Error()) + } + } + + return nil +} diff --git a/subcommands/update_sc_test.go b/subcommands/update_sc_test.go index f5bbafd..3f6093d 100644 --- a/subcommands/update_sc_test.go +++ b/subcommands/update_sc_test.go @@ -1,303 +1,255 @@ -package subcommands - -import ( - "reflect" - "testing" - - "github.com/everettraven/packageless/utils" -) - -//Test to make sure the update subcommand has the proper name upon creation -func TestUpdateName(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - updateCommand := NewUpdateCommand(mu, config) - - if updateCommand.Name() != "update" { - t.Fatal("The update subcommand's name should be: update | Subcommand Name: " + updateCommand.Name()) - } -} - -//Test to make sure the update subcommand initializes correctly -func TestUpdateInit(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - updateCommand := NewUpdateCommand(mu, config) - - args := []string{"python"} - - err := updateCommand.Init(args) - - if err != nil { - t.Fatal(err) - } - - if updateCommand.name != args[0] { - t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + updateCommand.name) - } -} - -func TestUpdateNoArgsFlow(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.InstalledPims = []string{"python", "another"} - - updateCommand := NewUpdateCommand(mu, config) - - args := []string{} - - err := updateCommand.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = updateCommand.Run() - - if err != nil { - t.Fatal(err) - } - - callStack := []string{ - "GetListOfInstalledPimConfigs", - "FetchPimConfig", - "FetchPimConfig", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - if !reflect.DeepEqual(mu.FetchedPims, mu.InstalledPims) { - t.Fatalf("The fetched pims should be the same as the installed pims, but it is not. Fetched Pims: %v | Installed Pims: %v", mu.FetchedPims, mu.InstalledPims) - } -} - -func TestUpdateArgsFlow(t *testing.T) { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.InstalledPims = []string{"python", "another"} - - updateCommand := NewUpdateCommand(mu, config) - - args := []string{"python"} - expectedFetchedPims := []string{"python"} - - err := updateCommand.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = updateCommand.Run() - - if err != nil { - t.Fatal(err) - } - - callStack := []string{ - "GetListOfInstalledPimConfigs", - "FetchPimConfig", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - if !reflect.DeepEqual(mu.FetchedPims, expectedFetchedPims) { - t.Fatalf("The fetched pims does not match the expected. Fetched Pims: %v | Expected: %v", mu.FetchedPims, expectedFetchedPims) - } -} - -func TestUpdateErrorAtGetListOfInstalledPimConfigs(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "GetListOfInstalledPimConfigs" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - updateCommand := NewUpdateCommand(mu, config) - - expectedErr := "Encountered an error while trying to fetch list of installed pim configuration files: " + mu.ErrorMsg - - args := []string{"python"} - - err := updateCommand.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = updateCommand.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "GetListOfInstalledPimConfigs", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -func TestUpdateErrorAtFetchPimConfig(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ErrorAt = "FetchPimConfig" - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.InstalledPims = []string{"python"} - - updateCommand := NewUpdateCommand(mu, config) - - expectedErr := "Encountered an error while trying to fetch the latest pim configuration file for pim 'python': " + mu.ErrorMsg - - args := []string{"python"} - - err := updateCommand.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = updateCommand.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "GetListOfInstalledPimConfigs", - "FetchPimConfig", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -func ExampleUpdateNoArgs() { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.InstalledPims = append(mu.InstalledPims, "python") - - updateCommand := NewUpdateCommand(mu, config) - - args := []string{} - - updateCommand.Init(args) - updateCommand.Run() - - // Output: - // No pim specified, updating all currently installed pim configurations. - // Updating pim: python -} - -func ExampleUpdateArgs() { - mu := utils.NewMockUtility() - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.InstalledPims = []string{"python", "another"} - - updateCommand := NewUpdateCommand(mu, config) - - args := []string{"python"} - - updateCommand.Init(args) - updateCommand.Run() - - // Output: - // Updating pim: python -} +package subcommands + +import ( + "reflect" + "testing" + + "github.com/everettraven/packageless/utils" +) + +//Test to make sure the update subcommand has the proper name upon creation +func TestUpdateName(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + updateCommand := NewUpdateCommand(mu, config) + + if updateCommand.Name() != "update" { + t.Fatal("The update subcommand's name should be: update | Subcommand Name: " + updateCommand.Name()) + } +} + +//Test to make sure the update subcommand initializes correctly +func TestUpdateInit(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + updateCommand := NewUpdateCommand(mu, config) + + args := []string{"python"} + + err := updateCommand.Init(args) + + if err != nil { + t.Fatal(err) + } + + if updateCommand.name != args[0] { + t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + updateCommand.name) + } +} + +func TestUpdateNoArgsFlow(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.InstalledPims = []string{"python", "another"} + + updateCommand := NewUpdateCommand(mu, config) + + args := []string{} + + err := updateCommand.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = updateCommand.Run() + + if err != nil { + t.Fatal(err) + } + + callStack := []string{ + "RenderInfoMarkdown", + "GetListOfInstalledPimConfigs", + "RenderInfoMarkdown", + "FetchPimConfig", + "RenderInfoMarkdown", + "FetchPimConfig", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + if !reflect.DeepEqual(mu.FetchedPims, mu.InstalledPims) { + t.Fatalf("The fetched pims should be the same as the installed pims, but it is not. Fetched Pims: %v | Installed Pims: %v", mu.FetchedPims, mu.InstalledPims) + } +} + +func TestUpdateArgsFlow(t *testing.T) { + mu := utils.NewMockUtility() + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.InstalledPims = []string{"python", "another"} + + updateCommand := NewUpdateCommand(mu, config) + + args := []string{"python"} + expectedFetchedPims := []string{"python"} + + err := updateCommand.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = updateCommand.Run() + + if err != nil { + t.Fatal(err) + } + + callStack := []string{ + "GetListOfInstalledPimConfigs", + "RenderInfoMarkdown", + "FetchPimConfig", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + if !reflect.DeepEqual(mu.FetchedPims, expectedFetchedPims) { + t.Fatalf("The fetched pims does not match the expected. Fetched Pims: %v | Expected: %v", mu.FetchedPims, expectedFetchedPims) + } +} + +func TestUpdateErrorAtGetListOfInstalledPimConfigs(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "GetListOfInstalledPimConfigs" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + updateCommand := NewUpdateCommand(mu, config) + + expectedErr := "Encountered an error while trying to fetch list of installed pim configuration files: " + mu.ErrorMsg + + args := []string{"python"} + + err := updateCommand.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = updateCommand.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "GetListOfInstalledPimConfigs", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +func TestUpdateErrorAtFetchPimConfig(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ErrorAt = "FetchPimConfig" + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.InstalledPims = []string{"python"} + + updateCommand := NewUpdateCommand(mu, config) + + expectedErr := "Encountered an error while trying to fetch the latest pim configuration file for pim 'python': " + mu.ErrorMsg + + args := []string{"python"} + + err := updateCommand.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = updateCommand.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "GetListOfInstalledPimConfigs", + "RenderInfoMarkdown", + "FetchPimConfig", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} diff --git a/subcommands/upgrade_sc.go b/subcommands/upgrade_sc.go index 96ab080..e3e0667 100644 --- a/subcommands/upgrade_sc.go +++ b/subcommands/upgrade_sc.go @@ -1,304 +1,308 @@ -package subcommands - -import ( - "errors" - "flag" - "fmt" - "strings" - - "github.com/docker/docker/client" - "github.com/everettraven/packageless/utils" -) - -//Upgrade Sub-Command Object -type UpgradeCommand struct { - //FlagSet so that we can create a custom flag - fs *flag.FlagSet - - //String for the name of the pim to upgrade - name string - - tools utils.Tools - - cp utils.Copier - - config utils.Config -} - -//Instantiation method for a new UpgradeCommand -func NewUpgradeCommand(tools utils.Tools, cp utils.Copier, config utils.Config) *UpgradeCommand { - //Create a new UpgradeCommand and set the FlagSet - ic := &UpgradeCommand{ - fs: flag.NewFlagSet("upgrade", flag.ContinueOnError), - tools: tools, - cp: cp, - config: config, - } - - return ic -} - -//Name - Gets the name of the Sub-Command -func (ic *UpgradeCommand) Name() string { - return ic.fs.Name() -} - -//Init - Parses and Populates values of the Upgrade subcommand -func (ic *UpgradeCommand) Init(args []string) error { - if len(args) <= 0 { - fmt.Println("No pim specified, upgrading all currently installed pims.") - } else { - ic.name = args[0] - } - return nil -} - -//Run - Runs the Upgrade subcommand -func (ic *UpgradeCommand) Run() error { - //Create variables to use later - var found bool - var pim utils.PackageImage - var version utils.Version - - var pimName string - var pimVersion string - - if strings.Contains(ic.name, ":") { - split := strings.Split(ic.name, ":") - pimName = split[0] - pimVersion = split[1] - } else { - pimName = ic.name - pimVersion = "latest" - } - - //Create the Docker client - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return err - } - - pimConfigDir := ic.config.BaseDir + ic.config.PimsConfigDir - pimDir := ic.config.BaseDir + ic.config.PimsDir - - if pimName != "" { - - pimPath := pimConfigDir + pimName + ".hcl" - - //Check if pim config already exists - if !ic.tools.FileExists(pimPath) { - return errors.New("Could not find pim configuration for: " + pimName + " has it been installed?") - } - - pimListBody, err := ic.tools.GetHCLBody(pimPath) - - if err != nil { - return err - } - - //Parse the pim list - parseOut, err := ic.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) - - pims := parseOut.(utils.PimHCLUtil) - - //Check for errors - if err != nil { - return err - } - - //Look for the pim we want in the pim list - for _, pimItem := range pims.Pims { - //If we find it, set some variables and break - if pimItem.Name == pimName { - pim = pimItem - - for _, ver := range pim.Versions { - if ver.Version == pimVersion { - found = true - version = ver - break - } - } - } - } - - //Make sure we have found the pim in the pim list - if !found { - return errors.New("Could not find pim " + pimName + " with version '" + pimVersion + "' in the pim list") - } - - //Check if the corresponding pim image is already installed - imgExist, err := ic.tools.ImageExists(version.Image, cli) - - //Check for errors - if err != nil { - return err - } - - //If the image exists the pim is already installed - if !imgExist { - return errors.New("pim: " + pim.Name + " with version '" + version.Version + "' is not installed. It must be installed before it can be upgraded.") - } - - fmt.Println("Upgrading", pim.Name+":"+version.Version) - //Pull the image down from Docker Hub - err = ic.tools.PullImage(version.Image, cli) - - if err != nil { - return err - } - - fmt.Println("Updating pim directories") - - //Check the volumes and create the directories for them if they don't already exist - for _, vol := range version.Volumes { - //Make sure that a path is given. If not we already assume that the working directory will be mounted - if vol.Path != "" { - err = ic.tools.UpgradeDir(pimDir + vol.Path) - - if err != nil { - return err - } - } - } - - //Check and see if any files need to be copied from the container to one of the volumes on the host. - if len(version.Copies) > 0 { - - fmt.Println("Copying necessary files 1/3") - //Create the container so that we can copy the files over to the right places - containerID, err := ic.tools.CreateContainer(version.Image, cli) - - if err != nil { - return err - } - - fmt.Println("Copying necessary files 2/3") - //Copy the files from the container to the locations - for _, copy := range version.Copies { - err = ic.tools.CopyFromContainer(copy.Source, pimDir+copy.Dest, containerID, cli, ic.cp) - - if err != nil { - return err - } - } - - fmt.Println("Copying necessary files 3/3") - //Remove the Container - err = ic.tools.RemoveContainer(containerID, cli) - - if err != nil { - return err - } - - fmt.Println(pim.Name, "successfully upgraded") - - } - } else { - - //Get list of installed pims - pimNames, err := ic.tools.GetListOfInstalledPimConfigs(pimConfigDir) - - if err != nil { - return errors.New("Encountered an error while trying to fetch list of installed pim configuration files: " + err.Error()) - } - - for _, pimName := range pimNames { - pimPath := pimConfigDir + pimName + ".hcl" - - pimListBody, err := ic.tools.GetHCLBody(pimPath) - - if err != nil { - return err - } - - //Parse the pim list - parseOut, err := ic.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) - - pims := parseOut.(utils.PimHCLUtil) - - if err != nil { - return err - } - - //Loop through the pims in the pim list - for _, pim := range pims.Pims { - - for _, ver := range pim.Versions { - //Check if the corresponding pim image is already installed - imgExist, err := ic.tools.ImageExists(ver.Image, cli) - - //Check for errors - if err != nil { - return err - } - - //If the image does not exist, then this version for the pim is not installed - //and therefore does not need to be upgraded - if !imgExist { - continue - } - - fmt.Println("Upgrading", pim.Name+":"+ver.Version) - //Pull the image down from Docker Hub - err = ic.tools.PullImage(ver.Image, cli) - - if err != nil { - return err - } - - fmt.Println("Updating pim directories") - - //Check the volumes and create the directories for them if they don't already exist - for _, vol := range ver.Volumes { - //Make sure that a path is given. If not we already assume that the working directory will be mounted - if vol.Path != "" { - err = ic.tools.UpgradeDir(pimDir + vol.Path) - - if err != nil { - return err - } - } - } - - //Check and see if any files need to be copied from the container to one of the volumes on the host. - if len(ver.Copies) > 0 { - - fmt.Println("Copying necessary files 1/3") - //Create the container so that we can copy the files over to the right places - containerID, err := ic.tools.CreateContainer(ver.Image, cli) - - if err != nil { - return err - } - - fmt.Println("Copying necessary files 2/3") - //Copy the files from the container to the locations - for _, copy := range ver.Copies { - err = ic.tools.CopyFromContainer(copy.Source, pimDir+copy.Dest, containerID, cli, ic.cp) - - if err != nil { - return err - } - } - - fmt.Println("Copying necessary files 3/3") - //Remove the Container - err = ic.tools.RemoveContainer(containerID, cli) - - if err != nil { - return err - } - - fmt.Println(pim.Name, "successfully upgraded") - } - - } - - } - } - - } - - return nil -} +package subcommands + +import ( + "errors" + "flag" + "fmt" + "strings" + + "github.com/docker/docker/client" + "github.com/everettraven/packageless/utils" +) + +//Upgrade Sub-Command Object +type UpgradeCommand struct { + //FlagSet so that we can create a custom flag + fs *flag.FlagSet + + //String for the name of the pim to upgrade + name string + + tools utils.Tools + + cp utils.Copier + + config utils.Config +} + +//Instantiation method for a new UpgradeCommand +func NewUpgradeCommand(tools utils.Tools, cp utils.Copier, config utils.Config) *UpgradeCommand { + //Create a new UpgradeCommand and set the FlagSet + ic := &UpgradeCommand{ + fs: flag.NewFlagSet("upgrade", flag.ContinueOnError), + tools: tools, + cp: cp, + config: config, + } + + return ic +} + +//Name - Gets the name of the Sub-Command +func (ic *UpgradeCommand) Name() string { + return ic.fs.Name() +} + +//Init - Parses and Populates values of the Upgrade subcommand +func (ic *UpgradeCommand) Init(args []string) error { + if len(args) <= 0 { + ic.tools.RenderInfoMarkdown("*No pim specified, upgrading all currently installed pims*") + } else { + ic.name = args[0] + } + return nil +} + +//Run - Runs the Upgrade subcommand +func (ic *UpgradeCommand) Run() error { + //Create variables to use later + var found bool + var pim utils.PackageImage + var version utils.Version + + var pimName string + var pimVersion string + + if strings.Contains(ic.name, ":") { + split := strings.Split(ic.name, ":") + pimName = split[0] + pimVersion = split[1] + } else { + pimName = ic.name + pimVersion = "latest" + } + + //Create the Docker client + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return err + } + + pimConfigDir := ic.config.BaseDir + ic.config.PimsConfigDir + pimDir := ic.config.BaseDir + ic.config.PimsDir + + if pimName != "" { + + pimPath := pimConfigDir + pimName + ".hcl" + + //Check if pim config already exists + if !ic.tools.FileExists(pimPath) { + return errors.New("Could not find pim configuration for: " + pimName + " has it been installed?") + } + + pimListBody, err := ic.tools.GetHCLBody(pimPath) + + if err != nil { + return err + } + + //Parse the pim list + parseOut, err := ic.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) + + pims := parseOut.(utils.PimHCLUtil) + + //Check for errors + if err != nil { + return err + } + + //Look for the pim we want in the pim list + for _, pimItem := range pims.Pims { + //If we find it, set some variables and break + if pimItem.Name == pimName { + pim = pimItem + + for _, ver := range pim.Versions { + if ver.Version == pimVersion { + found = true + version = ver + break + } + } + } + } + + //Make sure we have found the pim in the pim list + if !found { + return errors.New("Could not find pim " + pimName + " with version '" + pimVersion + "' in the pim list") + } + + //Check if the corresponding pim image is already installed + imgExist, err := ic.tools.ImageExists(version.Image, cli) + + //Check for errors + if err != nil { + return err + } + + //If the image exists the pim is already installed + if !imgExist { + return errors.New("pim: " + pim.Name + " with version '" + version.Version + "' is not installed. It must be installed before it can be upgraded.") + } + + ic.tools.RenderInfoMarkdown(fmt.Sprintf("**Upgrading**: *%s*", pim.Name+":"+version.Version)) + //Pull the image down from Docker Hub + ic.tools.RenderInfoMarkdown(fmt.Sprintf("- *Pulling image %s*", version.Image)) + err = ic.tools.PullImage(version.Image, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("- *Updating pim directories*") + + //Check the volumes and create the directories for them if they don't already exist + for _, vol := range version.Volumes { + //Make sure that a path is given. If not we already assume that the working directory will be mounted + if vol.Path != "" { + err = ic.tools.UpgradeDir(pimDir + vol.Path) + + if err != nil { + return err + } + } + } + + //Check and see if any files need to be copied from the container to one of the volumes on the host. + if len(version.Copies) > 0 { + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (create container)*") + //Create the container so that we can copy the files over to the right places + containerID, err := ic.tools.CreateContainer(version.Image, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (copy files from container)*") + //Copy the files from the container to the locations + for _, copy := range version.Copies { + err = ic.tools.CopyFromContainer(copy.Source, pimDir+copy.Dest, containerID, cli, ic.cp) + + if err != nil { + return err + } + } + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (remove container)*") + //Remove the Container + err = ic.tools.RemoveContainer(containerID, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("***") + ic.tools.RenderInfoMarkdown(fmt.Sprintf("*%s* **successfully upgraded**", pim.Name)) + + } + } else { + + //Get list of installed pims + pimNames, err := ic.tools.GetListOfInstalledPimConfigs(pimConfigDir) + + if err != nil { + return errors.New("Encountered an error while trying to fetch list of installed pim configuration files: " + err.Error()) + } + + for _, pimName := range pimNames { + pimPath := pimConfigDir + pimName + ".hcl" + + pimListBody, err := ic.tools.GetHCLBody(pimPath) + + if err != nil { + return err + } + + //Parse the pim list + parseOut, err := ic.tools.ParseBody(pimListBody, utils.PimHCLUtil{}) + + pims := parseOut.(utils.PimHCLUtil) + + if err != nil { + return err + } + + //Loop through the pims in the pim list + for _, pim := range pims.Pims { + + for _, ver := range pim.Versions { + //Check if the corresponding pim image is already installed + imgExist, err := ic.tools.ImageExists(ver.Image, cli) + + //Check for errors + if err != nil { + return err + } + + //If the image does not exist, then this version for the pim is not installed + //and therefore does not need to be upgraded + if !imgExist { + continue + } + + ic.tools.RenderInfoMarkdown(fmt.Sprintf("**Upgrading**: *%s* ", pim.Name+":"+ver.Version)) + ic.tools.RenderInfoMarkdown(fmt.Sprintf("- *Pulling image %s*", version.Image)) + //Pull the image down from Docker Hub + err = ic.tools.PullImage(ver.Image, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("- *Updating pim directories*") + + //Check the volumes and create the directories for them if they don't already exist + for _, vol := range ver.Volumes { + //Make sure that a path is given. If not we already assume that the working directory will be mounted + if vol.Path != "" { + err = ic.tools.UpgradeDir(pimDir + vol.Path) + + if err != nil { + return err + } + } + } + + //Check and see if any files need to be copied from the container to one of the volumes on the host. + if len(ver.Copies) > 0 { + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (create container)*") + //Create the container so that we can copy the files over to the right places + containerID, err := ic.tools.CreateContainer(ver.Image, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (copy files from container)*") + //Copy the files from the container to the locations + for _, copy := range ver.Copies { + err = ic.tools.CopyFromContainer(copy.Source, pimDir+copy.Dest, containerID, cli, ic.cp) + + if err != nil { + return err + } + } + + ic.tools.RenderInfoMarkdown("- *Copying necessary files (remove container)*") + //Remove the Container + err = ic.tools.RemoveContainer(containerID, cli) + + if err != nil { + return err + } + + ic.tools.RenderInfoMarkdown("***") + ic.tools.RenderInfoMarkdown(fmt.Sprintf("*%s* - **successfully upgraded**", pim.Name)) + } + + } + + } + } + + } + + return nil +} diff --git a/subcommands/upgrade_sc_test.go b/subcommands/upgrade_sc_test.go index ad312f4..f316c75 100644 --- a/subcommands/upgrade_sc_test.go +++ b/subcommands/upgrade_sc_test.go @@ -1,1013 +1,1059 @@ -package subcommands - -import ( - "reflect" - "testing" - - "github.com/everettraven/packageless/utils" -) - -//Test to make sure the Upgrade subcommand has the proper name upon creation -func TestUpgradeName(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - if ic.Name() != "upgrade" { - t.Fatal("The Upgrade subcommand's name should be: upgrade | Subcommand Name: " + ic.Name()) - } -} - -//Test to make sure the Upgrade subcommand initializes correctly -func TestUpgradeInit(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - if ic.name != args[0] { - t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + ic.name) - } -} - -//Tests the flow of a correctly ran Upgrade subcommand -func TestUpgradeFlow(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been pulled and make sure it matches from the MockUtility - var images []string - - //Lists of copy data - var copySources []string - var copyDests []string - - //directories to be created - var updirs []string - - pimDir := config.BaseDir + config.PimsDir - - //Fill lists - for _, pim := range mu.Pim.Pims { - //Just use the first version - version := pim.Versions[0] - images = append(images, version.Image) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - updirs = append(updirs, pimDir+vol.Path) - } - - //Loop through the copies in the pim - for _, copy := range version.Copies { - copySources = append(copySources, copy.Source) - copyDests = append(copyDests, pimDir+copy.Dest) - } - - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.PulledImgs) { - t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(updirs, mu.UpgradedDirs) { - t.Fatalf("Upgraded directories does not match the expected directories. Upgraded Directories: %v | Expected Upgraded Directories: %v", mu.MadeDirs, updirs) - } - - //Make sure that the image passed into the CreateContainer function is correct - if !reflect.DeepEqual(mu.CreateImages, images) { - t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) - } - - //Make sure the proper ContainerID is being passed into the CopyFromContainer function - if mu.CopyContainerID != mu.ContainerID { - t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) - } - - //Ensure that the Copy sources are correct - if !reflect.DeepEqual(mu.CopySources, copySources) { - t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) - } - - //Ensure that the Copy destinations are correct - if !reflect.DeepEqual(mu.CopyDests, copyDests) { - t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) - } - - //Ensure that the ContainerID is passed correctly to the RemoveContainer function - if mu.RemoveContainerID != mu.ContainerID { - t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) - } - -} - -//Test the Upgrade subcommand getting an error after calling the GetHCLBody function -func TestUpgradeErrorAtGetHCLBody(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "GetHCLBody" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the ParseBody function -func TestUpgradeErrorAtParseBody(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "ParseBody" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the ImageExists function -func TestUpgradeErrorAtImageExists(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "ImageExists" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the PullImage function -func TestUpgradeErrorAtPullImage(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "PullImage" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the MakeDir function -func TestUpgradeErrorAtUpgradeDir(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "UpgradeDir" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the CreateContainer function -func TestUpgradeErrorAtCreateContainer(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "CreateContainer" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - "CreateContainer", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the CopyFromContainer function -func TestUpgradeErrorAtCopyFromContainer(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "CopyFromContainer" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - "CreateContainer", - "CopyFromContainer", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand getting an error after calling the RemoveContainer function -func TestUpgradeErrorAtRemoveContainer(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mu.ErrorAt = "RemoveContainer" - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") - } - - if err.Error() != mu.ErrorMsg { - t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} - -//Test the Upgrade subcommand when ImageExists function returns true -func TestUpgradeImageNotExists(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = false - - args := []string{"python"} - expectedErr := "pim: python with version 'latest' is not installed. It must be installed before it can be upgraded." - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - "ImageExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test the Upgrade subcommand with no arguments passed and 2 packages in the pim list -func TestUpgradeNoPackageWithTwoPacks(t *testing.T) { - mu := utils.NewMockUtility() - - mu.ImgExist = true - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.InstalledPims = []string{"python", "second"} - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err != nil { - t.Fatal(err) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "GetListOfInstalledPimConfigs", - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - //Repeat the cycle from GetHCLBody since we should be reading a new pim file - "GetHCLBody", - "ParseBody", - "ImageExists", - "PullImage", - "UpgradeDir", - "CreateContainer", - "CopyFromContainer", - "RemoveContainer", - } - - //If the call stack doesn't match the test fails - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - - //Make a list of images that should have been pulled and make sure it matches from the MockUtility - var images []string - - //Lists of copy data - var copySources []string - var copyDests []string - - //directories to be created - var updirs []string - - //Fill lists - for _, pim := range mu.Pim.Pims { - //Since we are doing two packages we need to repeat this loop a second time - for i := 0; i < 2; i++ { - //Just get the first version - version := pim.Versions[0] - images = append(images, version.Image) - - //Loop through volumes in the pim - for _, vol := range version.Volumes { - updirs = append(updirs, config.BaseDir+config.PimsDir+vol.Path) - } - - //Loop through the copies in the pim - for _, copy := range version.Copies { - copySources = append(copySources, copy.Source) - copyDests = append(copyDests, config.BaseDir+config.PimsDir+copy.Dest) - } - } - } - - //If the pulled images doesn't match the test fails - if !reflect.DeepEqual(images, mu.PulledImgs) { - t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) - } - - //If the directories made don't match, the test fails - if !reflect.DeepEqual(updirs, mu.UpgradedDirs) { - t.Fatalf("Upgraded directories does not match the expected directories. Upgraded Directories: %v | Expected Upgraded Directories: %v", mu.MadeDirs, updirs) - } - - //Make sure that the image passed into the CreateContainer function is correct - if !reflect.DeepEqual(mu.CreateImages, images) { - t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) - } - - //Make sure the proper ContainerID is being passed into the CopyFromContainer function - if mu.CopyContainerID != mu.ContainerID { - t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) - } - - //Ensure that the Copy sources are correct - if !reflect.DeepEqual(mu.CopySources, copySources) { - t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) - } - - //Ensure that the Copy destinations are correct - if !reflect.DeepEqual(mu.CopyDests, copyDests) { - t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) - } - - //Ensure that the ContainerID is passed correctly to the RemoveContainer function - if mu.RemoveContainerID != mu.ContainerID { - t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) - } - - pimConfigDir := config.BaseDir + config.PimsConfigDir - //Make sure we are getting the correct pim config dir passed in - if mu.PimConfigDir != pimConfigDir { - t.Fatalf("The pim configuration directory was: %s | Expected: %s", mu.PimConfigDir, pimConfigDir) - } - -} - -//Test the Upgrade subcommand if the passed in pim does not exist -func TestUpgradeNonExistPackage(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"nonexistent"} - - expectedErr := "Could not find pim nonexistent with version 'latest' in the pim list" - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -//Test the Upgrade subcommand if the passed in pim version does not exist -func TestUpgradeNonExistVersion(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python:idontexist"} - - expectedErr := "Could not find pim python with version 'idontexist' in the pim list" - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - "GetHCLBody", - "ParseBody", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -func TestUpgradeErrorAtGetListOfInstalledPimConfigs(t *testing.T) { - mu := utils.NewMockUtility() - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - mu.ErrorAt = "GetListOfInstalledPimConfigs" - mu.ErrorMsg = "error message" - - ic := NewUpgradeCommand(mu, mcp, config) - - expectedErr := "Encountered an error while trying to fetch list of installed pim configuration files: " + mu.ErrorMsg - - err := ic.Init([]string{}) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "GetListOfInstalledPimConfigs", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } -} - -func TestUpgradeErrorAtPimConfigurationFileNotFound(t *testing.T) { - mu := utils.NewMockUtility() - - mu.PimConfigShouldExist = false - - mcp := &utils.MockCopyTool{} - - config := utils.Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - } - - ic := NewUpgradeCommand(mu, mcp, config) - - args := []string{"python"} - - err := ic.Init(args) - - if err != nil { - t.Fatal(err) - } - - err = ic.Run() - - expectedErr := "Could not find pim configuration for: " + ic.name + " has it been installed?" - - if err == nil { - t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") - } - - if err.Error() != expectedErr { - t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) - } - - //Set a variable with the proper call stack and see if the call stack matches - callStack := []string{ - "FileExists", - } - - if !reflect.DeepEqual(callStack, mu.Calls) { - t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) - } - -} +package subcommands + +import ( + "reflect" + "testing" + + "github.com/everettraven/packageless/utils" +) + +//Test to make sure the Upgrade subcommand has the proper name upon creation +func TestUpgradeName(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + if ic.Name() != "upgrade" { + t.Fatal("The Upgrade subcommand's name should be: upgrade | Subcommand Name: " + ic.Name()) + } +} + +//Test to make sure the Upgrade subcommand initializes correctly +func TestUpgradeInit(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + if ic.name != args[0] { + t.Fatal("pim Name should have been initialized as: " + args[0] + " but is: " + ic.name) + } +} + +//Tests the flow of a correctly ran Upgrade subcommand +func TestUpgradeFlow(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been pulled and make sure it matches from the MockUtility + var images []string + + //Lists of copy data + var copySources []string + var copyDests []string + + //directories to be created + var updirs []string + + pimDir := config.BaseDir + config.PimsDir + + //Fill lists + for _, pim := range mu.Pim.Pims { + //Just use the first version + version := pim.Versions[0] + images = append(images, version.Image) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + updirs = append(updirs, pimDir+vol.Path) + } + + //Loop through the copies in the pim + for _, copy := range version.Copies { + copySources = append(copySources, copy.Source) + copyDests = append(copyDests, pimDir+copy.Dest) + } + + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.PulledImgs) { + t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(updirs, mu.UpgradedDirs) { + t.Fatalf("Upgraded directories does not match the expected directories. Upgraded Directories: %v | Expected Upgraded Directories: %v", mu.MadeDirs, updirs) + } + + //Make sure that the image passed into the CreateContainer function is correct + if !reflect.DeepEqual(mu.CreateImages, images) { + t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) + } + + //Make sure the proper ContainerID is being passed into the CopyFromContainer function + if mu.CopyContainerID != mu.ContainerID { + t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) + } + + //Ensure that the Copy sources are correct + if !reflect.DeepEqual(mu.CopySources, copySources) { + t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) + } + + //Ensure that the Copy destinations are correct + if !reflect.DeepEqual(mu.CopyDests, copyDests) { + t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) + } + + //Ensure that the ContainerID is passed correctly to the RemoveContainer function + if mu.RemoveContainerID != mu.ContainerID { + t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) + } + +} + +//Test the Upgrade subcommand getting an error after calling the GetHCLBody function +func TestUpgradeErrorAtGetHCLBody(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "GetHCLBody" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the ParseBody function +func TestUpgradeErrorAtParseBody(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "ParseBody" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the ImageExists function +func TestUpgradeErrorAtImageExists(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "ImageExists" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the PullImage function +func TestUpgradeErrorAtPullImage(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "PullImage" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the MakeDir function +func TestUpgradeErrorAtUpgradeDir(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "UpgradeDir" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the CreateContainer function +func TestUpgradeErrorAtCreateContainer(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "CreateContainer" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + "RenderInfoMarkdown", + "CreateContainer", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the CopyFromContainer function +func TestUpgradeErrorAtCopyFromContainer(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "CopyFromContainer" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand getting an error after calling the RemoveContainer function +func TestUpgradeErrorAtRemoveContainer(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mu.ErrorAt = "RemoveContainer" + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: " + mu.ErrorMsg + " but did not receive an error") + } + + if err.Error() != mu.ErrorMsg { + t.Fatal("Expected the following error: " + mu.ErrorMsg + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} + +//Test the Upgrade subcommand when ImageExists function returns true +func TestUpgradeImageNotExists(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = false + + args := []string{"python"} + expectedErr := "pim: python with version 'latest' is not installed. It must be installed before it can be upgraded." + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + "ImageExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test the Upgrade subcommand with no arguments passed and 2 packages in the pim list +func TestUpgradeNoPackageWithTwoPacks(t *testing.T) { + mu := utils.NewMockUtility() + + mu.ImgExist = true + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.InstalledPims = []string{"python", "second"} + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err != nil { + t.Fatal(err) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "RenderInfoMarkdown", + "GetListOfInstalledPimConfigs", + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + //Repeat the cycle from GetHCLBody since we should be reading a new pim file + "GetHCLBody", + "ParseBody", + "ImageExists", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + "PullImage", + "RenderInfoMarkdown", + "UpgradeDir", + "RenderInfoMarkdown", + "CreateContainer", + "RenderInfoMarkdown", + "CopyFromContainer", + "RenderInfoMarkdown", + "RemoveContainer", + "RenderInfoMarkdown", + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + + //Make a list of images that should have been pulled and make sure it matches from the MockUtility + var images []string + + //Lists of copy data + var copySources []string + var copyDests []string + + //directories to be created + var updirs []string + + //Fill lists + for _, pim := range mu.Pim.Pims { + //Since we are doing two packages we need to repeat this loop a second time + for i := 0; i < 2; i++ { + //Just get the first version + version := pim.Versions[0] + images = append(images, version.Image) + + //Loop through volumes in the pim + for _, vol := range version.Volumes { + updirs = append(updirs, config.BaseDir+config.PimsDir+vol.Path) + } + + //Loop through the copies in the pim + for _, copy := range version.Copies { + copySources = append(copySources, copy.Source) + copyDests = append(copyDests, config.BaseDir+config.PimsDir+copy.Dest) + } + } + } + + //If the pulled images doesn't match the test fails + if !reflect.DeepEqual(images, mu.PulledImgs) { + t.Fatalf("Pulled Images does not match the expected Pulled Images. Pulled Images: %v | Expected Pulled Images: %v", mu.PulledImgs, images) + } + + //If the directories made don't match, the test fails + if !reflect.DeepEqual(updirs, mu.UpgradedDirs) { + t.Fatalf("Upgraded directories does not match the expected directories. Upgraded Directories: %v | Expected Upgraded Directories: %v", mu.MadeDirs, updirs) + } + + //Make sure that the image passed into the CreateContainer function is correct + if !reflect.DeepEqual(mu.CreateImages, images) { + t.Fatalf("CreateContainer images does not match the expected images. Images: %v | Expected Images: %v", mu.CreateImages, images) + } + + //Make sure the proper ContainerID is being passed into the CopyFromContainer function + if mu.CopyContainerID != mu.ContainerID { + t.Fatalf("CopyFromContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.CopyContainerID, mu.ContainerID) + } + + //Ensure that the Copy sources are correct + if !reflect.DeepEqual(mu.CopySources, copySources) { + t.Fatalf("CopyFromContainer Copy Sources does not match the expected Copy Sources. Copy Sources: %v | Expected Copy Sources: %v", mu.CopySources, copySources) + } + + //Ensure that the Copy destinations are correct + if !reflect.DeepEqual(mu.CopyDests, copyDests) { + t.Fatalf("CopyFromContainer Copy Destinations does not match the expected Copy Destinations. Copy Destinations: %v | Expected Copy Destinations: %v", mu.CopyDests, copyDests) + } + + //Ensure that the ContainerID is passed correctly to the RemoveContainer function + if mu.RemoveContainerID != mu.ContainerID { + t.Fatalf("RemoveContainer ContainerID does not match the expected ContainerID. ContainerID: %s | Expected ContainerID: %s", mu.RemoveContainerID, mu.ContainerID) + } + + pimConfigDir := config.BaseDir + config.PimsConfigDir + //Make sure we are getting the correct pim config dir passed in + if mu.PimConfigDir != pimConfigDir { + t.Fatalf("The pim configuration directory was: %s | Expected: %s", mu.PimConfigDir, pimConfigDir) + } + +} + +//Test the Upgrade subcommand if the passed in pim does not exist +func TestUpgradeNonExistPackage(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"nonexistent"} + + expectedErr := "Could not find pim nonexistent with version 'latest' in the pim list" + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +//Test the Upgrade subcommand if the passed in pim version does not exist +func TestUpgradeNonExistVersion(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python:idontexist"} + + expectedErr := "Could not find pim python with version 'idontexist' in the pim list" + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + "GetHCLBody", + "ParseBody", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +func TestUpgradeErrorAtGetListOfInstalledPimConfigs(t *testing.T) { + mu := utils.NewMockUtility() + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + mu.ErrorAt = "GetListOfInstalledPimConfigs" + mu.ErrorMsg = "error message" + + ic := NewUpgradeCommand(mu, mcp, config) + + expectedErr := "Encountered an error while trying to fetch list of installed pim configuration files: " + mu.ErrorMsg + + err := ic.Init([]string{}) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "RenderInfoMarkdown", + "GetListOfInstalledPimConfigs", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } +} + +func TestUpgradeErrorAtPimConfigurationFileNotFound(t *testing.T) { + mu := utils.NewMockUtility() + + mu.PimConfigShouldExist = false + + mcp := &utils.MockCopyTool{} + + config := utils.Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + } + + ic := NewUpgradeCommand(mu, mcp, config) + + args := []string{"python"} + + err := ic.Init(args) + + if err != nil { + t.Fatal(err) + } + + err = ic.Run() + + expectedErr := "Could not find pim configuration for: " + ic.name + " has it been installed?" + + if err == nil { + t.Fatal("Expected the following error: '" + expectedErr + "' but did not receive an error") + } + + if err.Error() != expectedErr { + t.Fatal("Expected the following error: " + expectedErr + "| Received: " + err.Error()) + } + + //Set a variable with the proper call stack and see if the call stack matches + callStack := []string{ + "FileExists", + } + + if !reflect.DeepEqual(callStack, mu.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mu.Calls, callStack) + } + +} diff --git a/subcommands/version_sc.go b/subcommands/version_sc.go index 8c47610..39801fd 100644 --- a/subcommands/version_sc.go +++ b/subcommands/version_sc.go @@ -1,40 +1,45 @@ -package subcommands - -import ( - "flag" - "fmt" -) - -var version = "v0.0.0" - -type VersionCommand struct { - //FlagSet for the version command - fs *flag.FlagSet -} - -//Instantiation method for a new VersionCommand -func NewVersionCommand() *VersionCommand { - //Create a new InstallCommand and set the FlagSet - vc := &VersionCommand{ - fs: flag.NewFlagSet("version", flag.ContinueOnError), - } - - return vc -} - -//Name - Gets the name of the Sub-Command -func (vc *VersionCommand) Name() string { - return vc.fs.Name() -} - -//Initialize the command, for this particular subcommand we should just do nothing -func (vc *VersionCommand) Init(args []string) error { - return nil -} - -//Run the command, this particular command should be a -//simple print of the value of the version variable -func (vc *VersionCommand) Run() error { - fmt.Println("Packageless Version: " + version) - return nil -} +package subcommands + +import ( + "flag" + "fmt" + + "github.com/everettraven/packageless/utils" +) + +var version = "v0.0.0" + +type VersionCommand struct { + //FlagSet for the version command + fs *flag.FlagSet + + tools utils.Tools +} + +//Instantiation method for a new VersionCommand +func NewVersionCommand(tools utils.Tools) *VersionCommand { + //Create a new InstallCommand and set the FlagSet + vc := &VersionCommand{ + fs: flag.NewFlagSet("version", flag.ContinueOnError), + tools: tools, + } + + return vc +} + +//Name - Gets the name of the Sub-Command +func (vc *VersionCommand) Name() string { + return vc.fs.Name() +} + +//Initialize the command, for this particular subcommand we should just do nothing +func (vc *VersionCommand) Init(args []string) error { + return nil +} + +//Run the command, this particular command should be a +//simple print of the value of the version variable +func (vc *VersionCommand) Run() error { + vc.tools.RenderInfoMarkdown(fmt.Sprintf("**Packageless Version**: *%s*", version)) + return nil +} diff --git a/subcommands/version_sc_test.go b/subcommands/version_sc_test.go index a559da4..a841584 100644 --- a/subcommands/version_sc_test.go +++ b/subcommands/version_sc_test.go @@ -1,32 +1,58 @@ -package subcommands - -import "testing" - -func TestVersionName(t *testing.T) { - expected := "version" - vc := NewVersionCommand() - - if vc.Name() != expected { - t.Fatalf("The version subcommand's name should be: '%s' but was '%s'", expected, vc.Name()) - } -} - -func TestVersionInit(t *testing.T) { - vc := NewVersionCommand() - - err := vc.Init([]string{}) - - if err != nil { - t.Fatalf("This method should do nothing except return nil | Received: %s", err) - } -} - -func ExampleVersion() { - vc := NewVersionCommand() - - vc.Run() - - // Output: - // Packageless Version: v0.0.0 - -} +package subcommands + +import ( + "reflect" + "testing" + + "github.com/everettraven/packageless/utils" +) + +func TestVersionName(t *testing.T) { + mockUtility := utils.NewMockUtility() + + expected := "version" + vc := NewVersionCommand(mockUtility) + + if vc.Name() != expected { + t.Fatalf("The version subcommand's name should be: '%s' but was '%s'", expected, vc.Name()) + } +} + +func TestVersionInit(t *testing.T) { + mockUtility := utils.NewMockUtility() + + vc := NewVersionCommand(mockUtility) + + err := vc.Init([]string{}) + + if err != nil { + t.Fatalf("This method should do nothing except return nil | Received: %s", err) + } +} + +func TestVersionRun(t *testing.T) { + mockUtility := utils.NewMockUtility() + + vc := NewVersionCommand(mockUtility) + + err := vc.Init([]string{}) + + if err != nil { + t.Fatalf("This method should do nothing except return nil | Received: %s", err) + } + + err = vc.Run() + + if err != nil { + t.Fatalf("This subcommand should not return an error | Received: %s", err) + } + + callStack := []string{ + "RenderInfoMarkdown", + } + + //If the call stack doesn't match the test fails + if !reflect.DeepEqual(callStack, mockUtility.Calls) { + t.Fatalf("Call Stack does not match the expected call stack. Call Stack: %v | Expected Call Stack: %v", mockUtility.Calls, callStack) + } +} diff --git a/utils/docker.go b/utils/docker.go index e286c0d..d871d8e 100644 --- a/utils/docker.go +++ b/utils/docker.go @@ -1,223 +1,226 @@ -package utils - -import ( - "context" - "fmt" - "io" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" -) - -//PullImage - This function pulls a Docker Image from the packageless organization in Docker Hub -func (u *Utility) PullImage(name string, cli Client) error { - //Set the context - ctx := context.Background() - - //Begin pulling the image - out, err := cli.ImagePull(ctx, name, types.ImagePullOptions{}) - - //Check for errors - if err != nil { - return err - } - - //Close the output buffer after the function exits - defer out.Close() - - //Copy the output to the screen - io.Copy(os.Stdout, out) - - //No errors - return nil -} - -//ImageExists - Function to check and see if Docker has the image downloaded -func (u *Utility) ImageExists(imageID string, cli Client) (bool, error) { - //Create a context and get a list of images on the system - ctx := context.Background() - images, err := cli.ImageList(ctx, types.ImageListOptions{}) - - //Check for errors - if err != nil { - return false, err - } - - //Loop through all the images and check if a match is found - for _, image := range images { - - // If RepoTags returned isnt populated then skip to the next image - if len(image.RepoTags) < 1 { - continue - } - - if image.RepoTags[0] == imageID { - return true, nil - } - } - - //No match found - return false, nil -} - -//CreateContainer - Create a Docker Container from a Docker Image. Returns the containerID and any errors -func (u *Utility) CreateContainer(image string, cli Client) (string, error) { - //Create the context and create the container - ctx := context.Background() - container, err := cli.ContainerCreate(ctx, &container.Config{Image: image, Cmd: []string{"bash"}}, nil, nil, nil, "") - - //Check for errors - if err != nil { - return "", err - } - - //No errors - return container.ID, err -} - -//CopyFromContainer will copy files from within a Docker Container to the source location on the host -func (u *Utility) CopyFromContainer(source string, dest string, containerID string, cli Client, cp Copier) error { - //Set the context and begin copying from the container - ctx := context.Background() - reader, _, err := cli.CopyFromContainer(ctx, containerID, source) - - //Check for errors - if err != nil { - return err - } - - //Close the reader after the function ends - defer reader.Close() - - //Copy the files over - err = cp.CopyFiles(reader, dest, source) - - if err != nil { - return err - } - - return nil -} - -//RemoveContainer is used to remove a container Docker given the container ID -func (u *Utility) RemoveContainer(containerID string, cli Client) error { - - //Create the context and remove the container - ctx := context.Background() - err := cli.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{Force: true}) - - //Check for errors - if err != nil { - return err - } - - //No Errors - return nil -} - -//RunContainer - Runs a container for the specified package -func (u *Utility) RunContainer(image string, ports []string, volumes []string, containerName string, args []string) (string, error) { - // Build the command to run the docker container - var cmdStr string - var cmd *exec.Cmd - - cmdStr += "docker " - - // add the base docker command details - cmdStr += "run -it --rm --name " + containerName + " " - - // add the ports to the command - for _, port := range ports { - cmdStr += "-p " + port + " " - } - - // add the volumes to the command - for _, vol := range volumes { - if vErr := u.validateRunContainerVolume(vol); vErr != nil { - return "", vErr - } - - splitVol := strings.Split(vol, ":") - var source string - var target string - - if len(splitVol) == 3 { - source = strings.Join(splitVol[:2], ":") - target = splitVol[2] - } else { - source = splitVol[0] - target = splitVol[1] - } - - source, err := filepath.Abs(source) - - if err != nil { - return "", err - } - - cmdStr += "-v " + source + ":" + target + " " - } - - // add the image name and the arguments - cmdStr += image + " " - - //Combine the arguments into one string - argStr := strings.Join(args, " ") - - //add the arguments - cmdStr += argStr - - //Instantiate the command based on OS - if runtime.GOOS == "windows" { - cmd = exec.Command("powershell", cmdStr) - } else { - cmd = exec.Command("bash", "-c", cmdStr) - } - - //Connect the command stderr, stdout, and stdin to the OS stderr, stdout, stdin - cmd.Stderr = os.Stderr - cmd.Stdout = os.Stdout - cmd.Stdin = os.Stdin - - //Run the command - err := cmd.Run() - - //Check for errors - if err != nil { - return cmdStr, err - } - - return cmdStr, nil - -} - -func (u *Utility) validateRunContainerVolume(volume string) error { - splitVolume := strings.Split(volume, ":") - - if len(splitVolume) != 2 && len(splitVolume) != 3 { - return fmt.Errorf("utils: Invalid split volume of length %d", len(splitVolume)) - } - - return nil -} - -//RemoveImage removes the image with the given name from local Docker -func (u *Utility) RemoveImage(image string, cli Client) error { - //Create the context and search for the image in the list of images - ctx := context.Background() - - //Remove the image - _, err := cli.ImageRemove(ctx, image, types.ImageRemoveOptions{Force: true}) - - //Check for errors - if err != nil { - return err - } - - return nil -} +package utils + +import ( + "context" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" +) + +//PullImage - This function pulls a Docker Image from the packageless organization in Docker Hub +func (u *Utility) PullImage(name string, cli Client) error { + //Set the context + ctx := context.Background() + + //Begin pulling the image + out, err := cli.ImagePull(ctx, name, types.ImagePullOptions{}) + + //Check for errors + if err != nil { + return err + } + + //Close the output buffer after the function exits + defer out.Close() + + //Copy the output of the command to nothing + //This seems to be the best way of ensuring that the image is pulled before exiting the function + io.Copy(io.Discard, out) + + //No errors + return nil +} + +//ImageExists - Function to check and see if Docker has the image downloaded +func (u *Utility) ImageExists(imageID string, cli Client) (bool, error) { + //Create a context and get a list of images on the system + ctx := context.Background() + images, err := cli.ImageList(ctx, types.ImageListOptions{}) + + //Check for errors + if err != nil { + return false, err + } + + //Loop through all the images and check if a match is found + for _, image := range images { + + // If RepoTags returned isnt populated then skip to the next image + if len(image.RepoTags) < 1 { + continue + } + + if image.RepoTags[0] == imageID { + return true, nil + } + } + + //No match found + return false, nil +} + +//CreateContainer - Create a Docker Container from a Docker Image. Returns the containerID and any errors +func (u *Utility) CreateContainer(image string, cli Client) (string, error) { + //Create the context and create the container + ctx := context.Background() + container, err := cli.ContainerCreate(ctx, &container.Config{Image: image, Cmd: []string{"bash"}}, nil, nil, nil, "") + + //Check for errors + if err != nil { + return "", err + } + + //No errors + return container.ID, err +} + +//CopyFromContainer will copy files from within a Docker Container to the source location on the host +func (u *Utility) CopyFromContainer(source string, dest string, containerID string, cli Client, cp Copier) error { + //Set the context and begin copying from the container + ctx := context.Background() + reader, _, err := cli.CopyFromContainer(ctx, containerID, source) + + //Check for errors + if err != nil { + return err + } + + //Close the reader after the function ends + defer reader.Close() + + //Copy the files over + err = cp.CopyFiles(reader, dest, source) + + if err != nil { + return err + } + + return nil +} + +//RemoveContainer is used to remove a container Docker given the container ID +func (u *Utility) RemoveContainer(containerID string, cli Client) error { + + //Create the context and remove the container + ctx := context.Background() + err := cli.ContainerRemove(ctx, containerID, types.ContainerRemoveOptions{Force: true}) + + //Check for errors + if err != nil { + return err + } + + //No Errors + return nil +} + +//RunContainer - Runs a container for the specified package +func (u *Utility) RunContainer(image string, ports []string, volumes []string, containerName string, args []string) (string, error) { + // Build the command to run the docker container + var cmdStr string + var cmd *exec.Cmd + + cmdStr += "docker " + + // add the base docker command details + cmdStr += "run -it --rm --name " + containerName + " " + + // add the ports to the command + for _, port := range ports { + cmdStr += "-p " + port + " " + } + + // add the volumes to the command + for _, vol := range volumes { + if vErr := u.validateRunContainerVolume(vol); vErr != nil { + return "", vErr + } + + splitVol := strings.Split(vol, ":") + var source string + var target string + var options string + + if len(splitVol) == 3 { + source = splitVol[0] + target = splitVol[1] + options = ":" + splitVol[2] + } else { + source = splitVol[0] + target = splitVol[1] + } + + source, err := filepath.Abs(source) + + if err != nil { + return "", err + } + + cmdStr += "-v " + source + ":" + target + options + " " + } + + // add the image name and the arguments + cmdStr += image + " " + + //Combine the arguments into one string + argStr := strings.Join(args, " ") + + //add the arguments + cmdStr += argStr + + //Instantiate the command based on OS + if runtime.GOOS == "windows" { + cmd = exec.Command("powershell", cmdStr) + } else { + cmd = exec.Command("bash", "-c", cmdStr) + } + + //Connect the command stderr, stdout, and stdin to the OS stderr, stdout, stdin + cmd.Stderr = os.Stderr + cmd.Stdout = os.Stdout + cmd.Stdin = os.Stdin + + //Run the command + err := cmd.Run() + + //Check for errors + if err != nil { + return cmdStr, err + } + + return cmdStr, nil + +} + +func (u *Utility) validateRunContainerVolume(volume string) error { + splitVolume := strings.Split(volume, ":") + + if len(splitVolume) != 2 && len(splitVolume) != 3 { + return fmt.Errorf("utils: Invalid split volume of length %d", len(splitVolume)) + } + + return nil +} + +//RemoveImage removes the image with the given name from local Docker +func (u *Utility) RemoveImage(image string, cli Client) error { + //Create the context and search for the image in the list of images + ctx := context.Background() + + //Remove the image + _, err := cli.ImageRemove(ctx, image, types.ImageRemoveOptions{Force: true}) + + //Check for errors + if err != nil { + return err + } + + return nil +} diff --git a/utils/docker_test.go b/utils/docker_test.go index 7a99c57..0c34fb4 100644 --- a/utils/docker_test.go +++ b/utils/docker_test.go @@ -1,881 +1,908 @@ -package utils - -import ( - "context" - "errors" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/client" -) - -//Unit Tests -//------------------------------------------------------------------------ - -//Test PullImage Function -func TestPullImage(t *testing.T) { - //Create a new Docker Client Mock - dm := NewDockMock() - - //Create a new utility object - util := NewUtility() - - //Set the image name to test - img := "image" - - //Pull the image - err := util.PullImage(img, dm) - - //If error occurs the test fails - if err != nil { - t.Fatal(err) - } - - //Check that the proper image was passed to the docker client - if dm.IPRefStr != img { - t.Fatal("PullImage: Image passed into the Docker SDK ImagePull Function should be '" + img + "' | Received: " + dm.IPRefStr) - } - -} - -//Test PullImage Function when it returns an error -func TestPullImageError(t *testing.T) { - //Create a new Docker Client Mock - dm := NewDockMock() - - //Create a new utility object - util := NewUtility() - - //Set the image name to test - img := "image" - - //Set the error at and error message - dm.ErrorAt = "ImagePull" - dm.ErrorMsg = "Testing error at ImagePull()" - - //Pull the image - err := util.PullImage(img, dm) - - //Error should occur - if err == nil { - t.Fatal("PullImage: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != dm.ErrorMsg { - t.Fatal("PullImage: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) - } - } - -} - -//Test ImageExists Function when the image does exist -func TestImageExistsDoesExist(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image for testing - img := "image:faketag" - - //Set the return images array in the Mock Docker Client - dm.ILRet = []types.ImageSummary{ - { - RepoTags: []string{"image:faketag"}, - }, - } - - //Create a new utility - util := NewUtility() - - //Check for the image - exists, err := util.ImageExists(img, dm) - - //If an error occurs, the test fails - if err != nil { - t.Fatal(err) - } - - //The image should exist in this case - if !exists { - t.Fatal("ImageExists: Image should exist, but it does not.") - } - -} - -//Test ImageExists Function when the image does not exist -func TestImageExistsDoesNotExist(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image for testing - img := "image" - - //Set the return images array in the Mock Docker Client - dm.ILRet = []types.ImageSummary{} - - //Create a new utility - util := NewUtility() - - //Check for the image - exists, err := util.ImageExists(img, dm) - - //If an error occurs, the test fails - if err != nil { - t.Fatal(err) - } - - //The image should not exist in this case - if exists { - t.Fatal("ImageExists: Image should not exist, but it does.") - } -} - -//Test the ImageExists Function when an error occurs -func TestImageExistError(t *testing.T) { - //Create a new Docker Client Mock - dm := NewDockMock() - - //Create a new utility object - util := NewUtility() - - //Set the image name to test - img := "image" - - //Set the error at and error message - dm.ErrorAt = "ImageList" - dm.ErrorMsg = "Testing error at ImageList()" - - //Set the return images array in the Mock Docker Client - dm.ILRet = []types.ImageSummary{} - - //Check for the image - _, err := util.ImageExists(img, dm) - - //Error should occur - if err == nil { - t.Fatal("ImageExists: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != dm.ErrorMsg { - t.Fatal("ImageExists: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) - } - } -} - -//Test ImageExists function when RepoTags is not populated -func TestImageExistContinueOnEmptyRepoTag(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image for testing - img := "image:faketag" - - //Set the return images array in the Mock Docker Client - dm.ILRet = []types.ImageSummary{ - // First one should be an empty RepoTags - { - RepoTags: []string{}, - }, - // When image loop continues it should find this one. - { - RepoTags: []string{"image:faketag"}, - }, - } - - //Create a new utility - util := NewUtility() - - //Check for the image - exists, err := util.ImageExists(img, dm) - - //If an error occurs, the test fails - if err != nil { - t.Fatal(err) - } - - //The image should exist in this case - if !exists { - t.Fatal("ImageExists: Image should exist, but it does not.") - } - -} - -//Test CreateContainer Function -func TestCreateContainer(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image that should be used - img := "image" - - //Set what the containerID should be - containerID := "testcontainer" - - //Set what the create container cmd should be - cmd := []string{"bash"} - - dm.CCRet = container.ContainerCreateCreatedBody{ - ID: containerID, - } - - //Create the util - util := NewUtility() - - //Test creating the container - cID, err := util.CreateContainer(img, dm) - - //If there is an error then the test fails - if err != nil { - t.Fatal(err) - } - - //Make sure the containerID matches - if cID != containerID { - t.Fatal("CreateContainer: Expected ContainerID: " + containerID + " | Received: " + cID) - } - - //Make sure the proper config settings were set when running the container - if dm.CCConfig.Image != img { - t.Fatal("CreateContainer: Expected Container Config Image: " + img + " | Received: " + cID) - } - - if dm.CCConfig.Cmd[0] != cmd[0] { - t.Fatalf("CreateContainer: Expected Container Config Cmd: %v | Received: %v", cmd, dm.CCConfig.Cmd) - } -} - -//Test CreateContainer Function with an error -func TestCreateContainerError(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image that should be used - img := "image" - - //Set what the containerID should be - containerID := "testcontainer" - - //Set the error at and error message - dm.ErrorAt = "ContainerCreate" - dm.ErrorMsg = "Testing error at ContainerCreate()" - - dm.CCRet = container.ContainerCreateCreatedBody{ - ID: containerID, - } - - //Create the util - util := NewUtility() - - //Test creating the container - _, err := util.CreateContainer(img, dm) - - //Error should occur - if err == nil { - t.Fatal("CreateContainer: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != dm.ErrorMsg { - t.Fatal("CreateContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) - } - } -} - -//Test CopyFromContainer Function -func TestCopyFromContainer(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the containerID to be used - cID := "fake" - - //Set what the source should be - source := "/fake/source" - - //Set what the destination should be - dest := "/fake/dest" - - //Create the util - util := NewUtility() - - //Create the mock copy tool - mcp := &MockCopyTool{} - - //Test creating the container - err := util.CopyFromContainer(source, dest, cID, dm, mcp) - - //If error occurs the test fails - if err != nil { - t.Fatal(err) - } - - //Make sure the containerID was passed in successfully - if dm.CFCID != cID { - t.Fatalf("CopyFromContainer: Expected ContainerID: %s | Received: %s", cID, dm.CFCID) - } - - //Make sure the source was passed in correctly - if dm.CFCSource != source { - t.Fatalf("CopyFromContainer: Expected Source: %s | Received: %s", source, dm.CFCSource) - } - - //Make sure the destination gets passed to the copy tool correctly - if mcp.Dest != dest { - t.Fatalf("CopyFromContainer -> CopyFiles: Expected Dest: %s | Received: %s", dest, mcp.Dest) - } - -} - -//Test CopyFromContainer Function with an error -func TestCopyFromContainerError(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the containerID to be used - cID := "fake" - - //Set what the source should be - source := "/fake/source" - - //Set what the destination should be - dest := "/fake/destination" - - //Set the error at and error message - dm.ErrorAt = "CopyFromContainer" - dm.ErrorMsg = "Testing error at CopyFromContainer()" - - //Create the util - util := NewUtility() - - //Create the mock copy tool - mcp := &MockCopyTool{} - - //Test creating the container - err := util.CopyFromContainer(source, dest, cID, dm, mcp) - - //If error occurs the test fails - if err == nil { - t.Fatal("CopyFromContainer: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != dm.ErrorMsg { - t.Fatal("CopyFromContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) - } - } -} - -//Test CopyFromContainer Function with an error in the CopyFiles Function -func TestCopyFromContainerErrorCopyFiles(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the containerID to be used - cID := "fake" - - //Set what the source should be - source := "/fake/source" - - //Set what the destination should be - dest := "/fake/destination" - - dm.ErrorMsg = "Testing error at CopyFromContainer()" - - //Create the util - util := NewUtility() - - //Create the mock copy tool with error sets - mcp := &MockCopyTool{ - Error: true, - ErrorMsg: "Testing error at CopyFiles()", - } - - //Test creating the container - err := util.CopyFromContainer(source, dest, cID, dm, mcp) - - //If error occurs the test fails - if err == nil { - t.Fatal("CopyFromContainer: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != mcp.ErrorMsg { - t.Fatal("CopyFromContainer: Expected Error: " + mcp.ErrorMsg + " | Received Error: " + err.Error()) - } - } -} - -//Test RemoveContainer Function -func TestRemoveContainer(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the containerID to be used - cID := "fake" - - //Create the util - util := NewUtility() - - //Test creating the container - err := util.RemoveContainer(cID, dm) - - //If error occurs the test fails - if err != nil { - t.Fatal(err) - } - - //Make sure the RemoveContainer options are correct - if !dm.CROptions.Force { - t.Fatal("RemoveContainer: Expected the ContainerRemoveOptions Force field to be set to true but it is set to false") - } - - //Make sure the containerID is correct - if dm.CRContainer != cID { - t.Fatalf("RemoveContainer: Expected Container ID: %s | Received: %s", cID, dm.CRContainer) - } -} - -//Test RemoveContainer Function with an error -func TestRemoveContainerError(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the containerID to be used - cID := "fake" - - //Set the error at and error message - dm.ErrorAt = "ContainerRemove" - dm.ErrorMsg = "Testing error at ContainerRemove()" - - //Create the util - util := NewUtility() - - //Test creating the container - err := util.RemoveContainer(cID, dm) - - //Error should occur - if err == nil { - t.Fatal("RemoveContainer: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != dm.ErrorMsg { - t.Fatal("RemoveContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) - } - } -} - -//Test RunContainer Function without arguments -func TestRunContainerNoArgs(t *testing.T) { - //Ser the image to be run - image := "image" - - //Set the ports - ports := []string{"3000:3000"} - - //Get absolute path for beginning of volume - absPath, err := filepath.Abs("/a/path/") - - //Should not be an error here - if err != nil { - t.Fatal(err) - } - - //Set the volumes - volumes := []string{absPath + ":/another/path"} - - //Set the container name - cName := "test" - - //Set the empty args - args := []string{} - - //Set the expected command - exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + volumes[0] + " " + image + " " - - //Create the util tool - util := NewUtility() - - //Run the RunContainer function and ignore any errors since we just want to make sure the cmd is built properly - cmd, _ := util.RunContainer(image, ports, volumes, cName, args) - - //Returned cmd should equal the expected one - if cmd != exCmd { - t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmd) - } - -} - -//Test RunContainer Function with arguments -func TestRunContainerWithArgs(t *testing.T) { - //Ser the image to be run - image := "image" - - //Set the ports - ports := []string{"3000:3000"} - - //Get absolute path for beginning of volume - absPath, err := filepath.Abs("/a/path/") - - //Should not be an error here - if err != nil { - t.Fatal(err) - } - - //Set the volumes - volumes := []string{absPath + ":/another/path"} - - //Set the container name - cName := "test" - - //Set the empty args - args := []string{"some", "arguments"} - - argStr := strings.Join(args, " ") - - //Set the expected command - exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + volumes[0] + " " + image + " " + argStr - - //Create the util tool - util := NewUtility() - - //Run the RunContainer function and ignore any errors since we just want to make sure the cmd is built properly - cmd, _ := util.RunContainer(image, ports, volumes, cName, args) - - //Returned cmd should equal the expected one - if cmd != exCmd { - t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmd) - } - -} - -func TestRunContainerReturnErrorWhenSplitVolumeIs1(t *testing.T) { - //Set the image to be run - image := "image" - - //Set the ports - ports := []string{"3000:3000"} - - //Set the volumes - volumes := []string{"/path1"} - - //Set the container name - cName := "test" - - //Set the empty args - args := []string{} - - //Create the util tool - util := NewUtility() - - //Run the RunContainer function and assert the error - exErr := errors.New("utils: Invalid split volume of length 1") - _, err := util.RunContainer(image, ports, volumes, cName, args) - if err.Error() != exErr.Error() { - t.Fatalf("RunContainer: Expected err: %s | Received err: %s", exErr.Error(), err.Error()) - } -} - -func TestRunContainerReturnCorrectCmdStrWhenSplitVolumeIs2(t *testing.T) { - //Set the image to be run - image := "image" - - //Set the ports - ports := []string{"3000:3000"} - - //Set the volumes - volumes := []string{"/path1:/path2"} - - //Set the container name - cName := "test" - - //Set the empty args - args := []string{} - - //Set the expected command - exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + volumes[0] + " " + image + " " - - //Create the util tool - util := NewUtility() - - //Run the RunContainer function and ignore any errors since we just want to make sure the cmdStr is built properly - cmdStr, _ := util.RunContainer(image, ports, volumes, cName, args) - if cmdStr != exCmd { - t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmdStr) - } -} - -func TestRunContainerReturnCorrectCmdStrWhenSplitVolumeIs3(t *testing.T) { - //Set the image to be run - image := "image" - - //Set the ports - ports := []string{"3000:3000"} - - //Set the volumes - volumes := []string{"/path1:/path2:/path3"} - - //Set the container name - cName := "test" - - //Set the empty args - args := []string{} - - //Set the expected command - exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + volumes[0] + " " + image + " " - - //Create the util tool - util := NewUtility() - - //Run the RunContainer function and ignore any errors since we just want to make sure the cmdStr is built properly - cmdStr, _ := util.RunContainer(image, ports, volumes, cName, args) - if cmdStr != exCmd { - t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmdStr) - } -} - -func TestRunContainerReturnErrorWhenSplitVolumeIs4(t *testing.T) { - //Set the image to be run - image := "image" - - //Set the ports - ports := []string{"3000:3000"} - - //Set the volumes - volumes := []string{"/path1:/path2:/path3:/path4"} - - //Set the container name - cName := "test" - - //Set the empty args - args := []string{} - - //Create the util tool - util := NewUtility() - - //Run the RunContainer function and assert the error - exErr := errors.New("utils: Invalid split volume of length 4") - _, err := util.RunContainer(image, ports, volumes, cName, args) - if err.Error() != exErr.Error() { - t.Fatalf("RunContainer: Expected err: %s | Received err: %s", exErr.Error(), err.Error()) - } -} - -//Test RemoveImage Function -func TestRemoveImage(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image to be used - img := "image" - - //Set the image ID - imgID := "fakeImgID" - - dm.ILRet = []types.ImageSummary{ - { - ID: imgID, - RepoTags: []string{img + ":faketag"}, - }, - } - - //Create the util - util := NewUtility() - - //Test creating the container - err := util.RemoveImage(img, dm) - - //Error shouldn't occur - if err != nil { - t.Fatal(err) - } - - //Check and make sure the image passed in is correct - if dm.IRImgID != img { - t.Fatalf("RemoveImage: Expected ImageID: %s | Received: %s", img, dm.IRImgID) - } - - //Check and make sure the image removal options passed in is correct - if !dm.IROptions.Force { - t.Fatal("RemoveImage: Expected the ImageRemovalOptions Force field to be set to 'true' but it is not") - } -} - -//Test RemoveImage Function when it encounters an error -func TestRemoveImageError(t *testing.T) { - //Create the Mock Docker Client - dm := NewDockMock() - - //Set the image to be used - img := "image" - - //Set the error at and error message - dm.ErrorAt = "ImageRemove" - dm.ErrorMsg = "Testing error at ImageRemove()" - - //Create the util - util := NewUtility() - - //Test creating the container - err := util.RemoveImage(img, dm) - - //Error should occur - if err == nil { - t.Fatal("RemoveContainer: Expected to receive an error, but did not receive one.") - } - - if err != nil { - if err.Error() != dm.ErrorMsg { - t.Fatal("RemoveContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) - } - } -} - -//Integration Tests -//------------------------------------------------------------------------ - -//Integration test for the pull image function -func TestDocker_Integration(t *testing.T) { - //if we want to run short tests then skip this - if testing.Short() { - t.Skip("skipping test, short tests specified") - } - - //Create the util object - util := NewUtility() - - //Create the Docker CLI - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - - //Shouldn't be any errors - if err != nil { - t.Fatal(err) - } - - //set the image to pull - img := "bpalmer/alpine-base-ssh" - - //pull the image - err = util.PullImage(img, cli) - - //shouldn't have any errors - if err != nil { - t.Fatal(err) - } - - //make sure the image exists after being pulled - imgExist, err := util.ImageExists(img+":latest", cli) - - if err != nil { - t.Fatal(err) - } - - if !imgExist { - t.Fatalf("Docker Integration: Expected Image %s to exist but it did not", img) - } - - //Try creating the container - cont, err := util.CreateContainer(img, cli) - - if err != nil { - t.Fatal(err) - } - - //Make sure the container exists - contExist := false - containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) - - if err != nil { - t.Fatal(err) - } - - for _, c := range containers { - if c.ID == cont { - contExist = true - } - } - - if !contExist { - t.Fatalf("Docker Integration: Expected Container %s to exist but it did not", cont) - } - - //Test getting a file from the container - file := "hostname" - source := "/etc/" + file - - //get the executing directory - - //set the destination - dest, err := filepath.Abs("../testing/") - - if err != nil { - t.Fatal(err) - } - - // runtime.Breakpoint() - - err = util.CopyFromContainer(source, dest, cont, cli, &CopyTool{}) - - if err != nil { - t.Fatal(err) - } - - //See if the file exists in the destination directory - if _, err = os.Stat(dest + "/" + file); err != nil { - if os.IsNotExist(err) { - t.Fatalf("Docker Integration: File %s was not copied from the container to host destination %s", file, dest) - } - } - - //Remove the file now - err = os.Remove(dest + "/" + file) - - if err != nil { - t.Fatal(err) - } - - //Test removing the container - err = util.RemoveContainer(cont, cli) - - if err != nil { - t.Fatal(err) - } - - //Make sure the container no longer exists - - contExist = false - containers, err = cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) - - if err != nil { - t.Fatal(err) - } - - for _, c := range containers { - if c.ID == cont { - contExist = true - } - } - - if contExist { - t.Fatalf("Docker Integration: Expected Container %s to not exist but it does exist", cont) - } - - //Now test removing the image - err = util.RemoveImage(img, cli) - - if err != nil { - t.Fatal(err) - } - - //Check if it exists again - imgExist, err = util.ImageExists(img, cli) - - if imgExist { - t.Fatalf("Docker: Expected Image %s to not exist but it did", img) - } -} +package utils + +import ( + "context" + "errors" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" +) + +//Unit Tests +//------------------------------------------------------------------------ + +//Test PullImage Function +func TestPullImage(t *testing.T) { + //Create a new Docker Client Mock + dm := NewDockMock() + + //Create a new utility object + util := NewUtility() + + //Set the image name to test + img := "image" + + //Pull the image + err := util.PullImage(img, dm) + + //If error occurs the test fails + if err != nil { + t.Fatal(err) + } + + //Check that the proper image was passed to the docker client + if dm.IPRefStr != img { + t.Fatal("PullImage: Image passed into the Docker SDK ImagePull Function should be '" + img + "' | Received: " + dm.IPRefStr) + } + +} + +//Test PullImage Function when it returns an error +func TestPullImageError(t *testing.T) { + //Create a new Docker Client Mock + dm := NewDockMock() + + //Create a new utility object + util := NewUtility() + + //Set the image name to test + img := "image" + + //Set the error at and error message + dm.ErrorAt = "ImagePull" + dm.ErrorMsg = "Testing error at ImagePull()" + + //Pull the image + err := util.PullImage(img, dm) + + //Error should occur + if err == nil { + t.Fatal("PullImage: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != dm.ErrorMsg { + t.Fatal("PullImage: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) + } + } + +} + +//Test ImageExists Function when the image does exist +func TestImageExistsDoesExist(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image for testing + img := "image:faketag" + + //Set the return images array in the Mock Docker Client + dm.ILRet = []types.ImageSummary{ + { + RepoTags: []string{"image:faketag"}, + }, + } + + //Create a new utility + util := NewUtility() + + //Check for the image + exists, err := util.ImageExists(img, dm) + + //If an error occurs, the test fails + if err != nil { + t.Fatal(err) + } + + //The image should exist in this case + if !exists { + t.Fatal("ImageExists: Image should exist, but it does not.") + } + +} + +//Test ImageExists Function when the image does not exist +func TestImageExistsDoesNotExist(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image for testing + img := "image" + + //Set the return images array in the Mock Docker Client + dm.ILRet = []types.ImageSummary{} + + //Create a new utility + util := NewUtility() + + //Check for the image + exists, err := util.ImageExists(img, dm) + + //If an error occurs, the test fails + if err != nil { + t.Fatal(err) + } + + //The image should not exist in this case + if exists { + t.Fatal("ImageExists: Image should not exist, but it does.") + } +} + +//Test the ImageExists Function when an error occurs +func TestImageExistError(t *testing.T) { + //Create a new Docker Client Mock + dm := NewDockMock() + + //Create a new utility object + util := NewUtility() + + //Set the image name to test + img := "image" + + //Set the error at and error message + dm.ErrorAt = "ImageList" + dm.ErrorMsg = "Testing error at ImageList()" + + //Set the return images array in the Mock Docker Client + dm.ILRet = []types.ImageSummary{} + + //Check for the image + _, err := util.ImageExists(img, dm) + + //Error should occur + if err == nil { + t.Fatal("ImageExists: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != dm.ErrorMsg { + t.Fatal("ImageExists: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) + } + } +} + +//Test ImageExists function when RepoTags is not populated +func TestImageExistContinueOnEmptyRepoTag(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image for testing + img := "image:faketag" + + //Set the return images array in the Mock Docker Client + dm.ILRet = []types.ImageSummary{ + // First one should be an empty RepoTags + { + RepoTags: []string{}, + }, + // When image loop continues it should find this one. + { + RepoTags: []string{"image:faketag"}, + }, + } + + //Create a new utility + util := NewUtility() + + //Check for the image + exists, err := util.ImageExists(img, dm) + + //If an error occurs, the test fails + if err != nil { + t.Fatal(err) + } + + //The image should exist in this case + if !exists { + t.Fatal("ImageExists: Image should exist, but it does not.") + } + +} + +//Test CreateContainer Function +func TestCreateContainer(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image that should be used + img := "image" + + //Set what the containerID should be + containerID := "testcontainer" + + //Set what the create container cmd should be + cmd := []string{"bash"} + + dm.CCRet = container.ContainerCreateCreatedBody{ + ID: containerID, + } + + //Create the util + util := NewUtility() + + //Test creating the container + cID, err := util.CreateContainer(img, dm) + + //If there is an error then the test fails + if err != nil { + t.Fatal(err) + } + + //Make sure the containerID matches + if cID != containerID { + t.Fatal("CreateContainer: Expected ContainerID: " + containerID + " | Received: " + cID) + } + + //Make sure the proper config settings were set when running the container + if dm.CCConfig.Image != img { + t.Fatal("CreateContainer: Expected Container Config Image: " + img + " | Received: " + cID) + } + + if dm.CCConfig.Cmd[0] != cmd[0] { + t.Fatalf("CreateContainer: Expected Container Config Cmd: %v | Received: %v", cmd, dm.CCConfig.Cmd) + } +} + +//Test CreateContainer Function with an error +func TestCreateContainerError(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image that should be used + img := "image" + + //Set what the containerID should be + containerID := "testcontainer" + + //Set the error at and error message + dm.ErrorAt = "ContainerCreate" + dm.ErrorMsg = "Testing error at ContainerCreate()" + + dm.CCRet = container.ContainerCreateCreatedBody{ + ID: containerID, + } + + //Create the util + util := NewUtility() + + //Test creating the container + _, err := util.CreateContainer(img, dm) + + //Error should occur + if err == nil { + t.Fatal("CreateContainer: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != dm.ErrorMsg { + t.Fatal("CreateContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) + } + } +} + +//Test CopyFromContainer Function +func TestCopyFromContainer(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the containerID to be used + cID := "fake" + + //Set what the source should be + source := "/fake/source" + + //Set what the destination should be + dest := "/fake/dest" + + //Create the util + util := NewUtility() + + //Create the mock copy tool + mcp := &MockCopyTool{} + + //Test creating the container + err := util.CopyFromContainer(source, dest, cID, dm, mcp) + + //If error occurs the test fails + if err != nil { + t.Fatal(err) + } + + //Make sure the containerID was passed in successfully + if dm.CFCID != cID { + t.Fatalf("CopyFromContainer: Expected ContainerID: %s | Received: %s", cID, dm.CFCID) + } + + //Make sure the source was passed in correctly + if dm.CFCSource != source { + t.Fatalf("CopyFromContainer: Expected Source: %s | Received: %s", source, dm.CFCSource) + } + + //Make sure the destination gets passed to the copy tool correctly + if mcp.Dest != dest { + t.Fatalf("CopyFromContainer -> CopyFiles: Expected Dest: %s | Received: %s", dest, mcp.Dest) + } + +} + +//Test CopyFromContainer Function with an error +func TestCopyFromContainerError(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the containerID to be used + cID := "fake" + + //Set what the source should be + source := "/fake/source" + + //Set what the destination should be + dest := "/fake/destination" + + //Set the error at and error message + dm.ErrorAt = "CopyFromContainer" + dm.ErrorMsg = "Testing error at CopyFromContainer()" + + //Create the util + util := NewUtility() + + //Create the mock copy tool + mcp := &MockCopyTool{} + + //Test creating the container + err := util.CopyFromContainer(source, dest, cID, dm, mcp) + + //If error occurs the test fails + if err == nil { + t.Fatal("CopyFromContainer: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != dm.ErrorMsg { + t.Fatal("CopyFromContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) + } + } +} + +//Test CopyFromContainer Function with an error in the CopyFiles Function +func TestCopyFromContainerErrorCopyFiles(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the containerID to be used + cID := "fake" + + //Set what the source should be + source := "/fake/source" + + //Set what the destination should be + dest := "/fake/destination" + + dm.ErrorMsg = "Testing error at CopyFromContainer()" + + //Create the util + util := NewUtility() + + //Create the mock copy tool with error sets + mcp := &MockCopyTool{ + Error: true, + ErrorMsg: "Testing error at CopyFiles()", + } + + //Test creating the container + err := util.CopyFromContainer(source, dest, cID, dm, mcp) + + //If error occurs the test fails + if err == nil { + t.Fatal("CopyFromContainer: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != mcp.ErrorMsg { + t.Fatal("CopyFromContainer: Expected Error: " + mcp.ErrorMsg + " | Received Error: " + err.Error()) + } + } +} + +//Test RemoveContainer Function +func TestRemoveContainer(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the containerID to be used + cID := "fake" + + //Create the util + util := NewUtility() + + //Test creating the container + err := util.RemoveContainer(cID, dm) + + //If error occurs the test fails + if err != nil { + t.Fatal(err) + } + + //Make sure the RemoveContainer options are correct + if !dm.CROptions.Force { + t.Fatal("RemoveContainer: Expected the ContainerRemoveOptions Force field to be set to true but it is set to false") + } + + //Make sure the containerID is correct + if dm.CRContainer != cID { + t.Fatalf("RemoveContainer: Expected Container ID: %s | Received: %s", cID, dm.CRContainer) + } +} + +//Test RemoveContainer Function with an error +func TestRemoveContainerError(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the containerID to be used + cID := "fake" + + //Set the error at and error message + dm.ErrorAt = "ContainerRemove" + dm.ErrorMsg = "Testing error at ContainerRemove()" + + //Create the util + util := NewUtility() + + //Test creating the container + err := util.RemoveContainer(cID, dm) + + //Error should occur + if err == nil { + t.Fatal("RemoveContainer: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != dm.ErrorMsg { + t.Fatal("RemoveContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) + } + } +} + +//Test RunContainer Function without arguments +func TestRunContainerNoArgs(t *testing.T) { + //Ser the image to be run + image := "image" + + //Set the ports + ports := []string{"3000:3000"} + + //Get absolute path for beginning of volume + absPath, err := filepath.Abs("/a/path/") + + //Should not be an error here + if err != nil { + t.Fatal(err) + } + + //Set the volumes + volumes := []string{"/a/path:/another/path"} + + //Set the container name + cName := "test" + + //Set the empty args + args := []string{} + + //Set the expected command + paths := strings.Split(volumes[0], ":") + exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + absPath + ":" + paths[1] + " " + image + " " + + //Create the util tool + util := NewUtility() + + //Run the RunContainer function and ignore any errors since we just want to make sure the cmd is built properly + cmd, _ := util.RunContainer(image, ports, volumes, cName, args) + + //Returned cmd should equal the expected one + if cmd != exCmd { + t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmd) + } + +} + +//Test RunContainer Function with arguments +func TestRunContainerWithArgs(t *testing.T) { + //Ser the image to be run + image := "image" + + //Set the ports + ports := []string{"3000:3000"} + + //Get absolute path for beginning of volume + absPath, err := filepath.Abs("/a/path/") + + //Should not be an error here + if err != nil { + t.Fatal(err) + } + + //Set the volumes + volumes := []string{"/a/path:/another/path"} + + //Set the container name + cName := "test" + + //Set the empty args + args := []string{"some", "arguments"} + + argStr := strings.Join(args, " ") + + //Set the expected command + paths := strings.Split(volumes[0], ":") + exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + absPath + ":" + paths[1] + " " + image + " " + argStr + + //Create the util tool + util := NewUtility() + + //Run the RunContainer function and ignore any errors since we just want to make sure the cmd is built properly + cmd, _ := util.RunContainer(image, ports, volumes, cName, args) + + //Returned cmd should equal the expected one + if cmd != exCmd { + t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmd) + } + +} + +func TestRunContainerReturnErrorWhenSplitVolumeIs1(t *testing.T) { + //Set the image to be run + image := "image" + + //Set the ports + ports := []string{"3000:3000"} + + //Set the volumes + volumes := []string{"/path1"} + + //Set the container name + cName := "test" + + //Set the empty args + args := []string{} + + //Create the util tool + util := NewUtility() + + //Run the RunContainer function and assert the error + exErr := errors.New("utils: Invalid split volume of length 1") + _, err := util.RunContainer(image, ports, volumes, cName, args) + if err.Error() != exErr.Error() { + t.Fatalf("RunContainer: Expected err: %s | Received err: %s", exErr.Error(), err.Error()) + } +} + +func TestRunContainerReturnCorrectCmdStrWhenSplitVolumeIs2(t *testing.T) { + //Set the image to be run + image := "image" + + //Set the ports + ports := []string{"3000:3000"} + + //Set the volumes + path, err := filepath.Abs("/path1") + + if err != nil { + t.Fatal(err) + } + + volumes := []string{"/path1:/path2"} + + //Set the container name + cName := "test" + + //Set the empty args + args := []string{} + + //Set the expected command + paths := strings.Split(volumes[0], ":") + exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + path + ":" + paths[1] + " " + image + " " + + //Create the util tool + util := NewUtility() + + //Run the RunContainer function and ignore any errors since we just want to make sure the cmdStr is built properly + cmdStr, err := util.RunContainer(image, ports, volumes, cName, args) + + //When running the docker command, it is expected to return an + //error of exit status 1 because no TTY was mounted. If a different error occurs, fail. + if err != nil && err.Error() != "exit status 1" { + t.Fatal(err) + } + + if cmdStr != exCmd { + t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmdStr) + } +} + +func TestRunContainerReturnCorrectCmdStrWhenSplitVolumeIs3(t *testing.T) { + //Set the image to be run + image := "image" + + //Set the ports + ports := []string{"3000:3000"} + + //Set the volumes + path, err := filepath.Abs("/path1") + + if err != nil { + t.Fatal(err) + } + + volumes := []string{"/path1:/path2:/path3"} + + //Set the container name + cName := "test" + + //Set the empty args + args := []string{} + + //Set the expected command + paths := strings.Split(volumes[0], ":") + exCmd := "docker run -it --rm --name " + cName + " -p " + ports[0] + " -v " + path + ":" + paths[1] + ":" + paths[2] + " " + image + " " + + //Create the util tool + util := NewUtility() + + //Run the RunContainer function and ignore any errors since we just want to make sure the cmdStr is built properly + cmdStr, err := util.RunContainer(image, ports, volumes, cName, args) + + if err != nil && err.Error() != "exit status 1" { + t.Fatal(err) + } + if cmdStr != exCmd { + t.Fatalf("RunContainer: Expected CMD: %s | Received CMD: %s", exCmd, cmdStr) + } +} + +func TestRunContainerReturnErrorWhenSplitVolumeIs4(t *testing.T) { + //Set the image to be run + image := "image" + + //Set the ports + ports := []string{"3000:3000"} + + //Set the volumes + volumes := []string{"/path1:/path2:/path3:/path4"} + + //Set the container name + cName := "test" + + //Set the empty args + args := []string{} + + //Create the util tool + util := NewUtility() + + //Run the RunContainer function and assert the error + exErr := errors.New("utils: Invalid split volume of length 4") + _, err := util.RunContainer(image, ports, volumes, cName, args) + if err.Error() != exErr.Error() { + t.Fatalf("RunContainer: Expected err: %s | Received err: %s", exErr.Error(), err.Error()) + } +} + +//Test RemoveImage Function +func TestRemoveImage(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image to be used + img := "image" + + //Set the image ID + imgID := "fakeImgID" + + dm.ILRet = []types.ImageSummary{ + { + ID: imgID, + RepoTags: []string{img + ":faketag"}, + }, + } + + //Create the util + util := NewUtility() + + //Test creating the container + err := util.RemoveImage(img, dm) + + //Error shouldn't occur + if err != nil { + t.Fatal(err) + } + + //Check and make sure the image passed in is correct + if dm.IRImgID != img { + t.Fatalf("RemoveImage: Expected ImageID: %s | Received: %s", img, dm.IRImgID) + } + + //Check and make sure the image removal options passed in is correct + if !dm.IROptions.Force { + t.Fatal("RemoveImage: Expected the ImageRemovalOptions Force field to be set to 'true' but it is not") + } +} + +//Test RemoveImage Function when it encounters an error +func TestRemoveImageError(t *testing.T) { + //Create the Mock Docker Client + dm := NewDockMock() + + //Set the image to be used + img := "image" + + //Set the error at and error message + dm.ErrorAt = "ImageRemove" + dm.ErrorMsg = "Testing error at ImageRemove()" + + //Create the util + util := NewUtility() + + //Test creating the container + err := util.RemoveImage(img, dm) + + //Error should occur + if err == nil { + t.Fatal("RemoveContainer: Expected to receive an error, but did not receive one.") + } + + if err != nil { + if err.Error() != dm.ErrorMsg { + t.Fatal("RemoveContainer: Expected Error: " + dm.ErrorMsg + " | Received Error: " + err.Error()) + } + } +} + +//Integration Tests +//------------------------------------------------------------------------ + +//Integration test for the pull image function +func TestDocker_Integration(t *testing.T) { + //if we want to run short tests then skip this + if testing.Short() { + t.Skip("skipping test, short tests specified") + } + + //Create the util object + util := NewUtility() + + //Create the Docker CLI + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + + //Shouldn't be any errors + if err != nil { + t.Fatal(err) + } + + //set the image to pull + img := "bpalmer/alpine-base-ssh" + + //pull the image + err = util.PullImage(img, cli) + + //shouldn't have any errors + if err != nil { + t.Fatal(err) + } + + //make sure the image exists after being pulled + imgExist, err := util.ImageExists(img+":latest", cli) + + if err != nil { + t.Fatal(err) + } + + if !imgExist { + t.Fatalf("Docker Integration: Expected Image %s to exist but it did not", img) + } + + //Try creating the container + cont, err := util.CreateContainer(img, cli) + + if err != nil { + t.Fatal(err) + } + + //Make sure the container exists + contExist := false + containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) + + if err != nil { + t.Fatal(err) + } + + for _, c := range containers { + if c.ID == cont { + contExist = true + } + } + + if !contExist { + t.Fatalf("Docker Integration: Expected Container %s to exist but it did not", cont) + } + + //Test getting a file from the container + file := "hostname" + source := "/etc/" + file + + //get the executing directory + + //set the destination + dest, err := filepath.Abs("../testing/") + + if err != nil { + t.Fatal(err) + } + + // runtime.Breakpoint() + + err = util.CopyFromContainer(source, dest, cont, cli, &CopyTool{}) + + if err != nil { + t.Fatal(err) + } + + //See if the file exists in the destination directory + if _, err = os.Stat(dest + "/" + file); err != nil { + if os.IsNotExist(err) { + t.Fatalf("Docker Integration: File %s was not copied from the container to host destination %s", file, dest) + } + } + + //Remove the file now + err = os.Remove(dest + "/" + file) + + if err != nil { + t.Fatal(err) + } + + //Test removing the container + err = util.RemoveContainer(cont, cli) + + if err != nil { + t.Fatal(err) + } + + //Make sure the container no longer exists + + contExist = false + containers, err = cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) + + if err != nil { + t.Fatal(err) + } + + for _, c := range containers { + if c.ID == cont { + contExist = true + } + } + + if contExist { + t.Fatalf("Docker Integration: Expected Container %s to not exist but it does exist", cont) + } + + //Now test removing the image + err = util.RemoveImage(img, cli) + + if err != nil { + t.Fatal(err) + } + + //Check if it exists again + imgExist, err = util.ImageExists(img, cli) + + if imgExist { + t.Fatalf("Docker: Expected Image %s to not exist but it did", img) + } +} diff --git a/utils/markdown.go b/utils/markdown.go new file mode 100644 index 0000000..151d659 --- /dev/null +++ b/utils/markdown.go @@ -0,0 +1,82 @@ +package utils + +import ( + "fmt" + + "github.com/charmbracelet/glamour" + "github.com/charmbracelet/glamour/ansi" +) + +//RenderMarkdown - renders markdown and outputs it to the console +func RenderMarkdown(input string, colors []string) error { + // Set up the renderer + bold := true + italic := true + var indent uint = 2 + renderer, _ := glamour.NewTermRenderer( + glamour.WithStyles( + ansi.StyleConfig{ + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: &colors[0], + BlockSuffix: "\n", + Bold: &bold, + }, + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: " ", + Suffix: " ", + Color: &colors[1], + BackgroundColor: &colors[0], + Bold: &bold, + }, + }, + Strong: ansi.StylePrimitive{ + Bold: &bold, + }, + Emph: ansi.StylePrimitive{ + Italic: &italic, + Color: &colors[0], + }, + List: ansi.StyleList{ + StyleBlock: ansi.StyleBlock{ + Indent: &indent, + }, + }, + HorizontalRule: ansi.StylePrimitive{ + Color: &colors[2], + Format: "\n=====================\n", + }, + }, + ), + ) + + out, err := renderer.Render(input) + + if err != nil { + return err + } + + fmt.Print(out) + + return nil +} + +//RenderInfoMarkdown - Renders markdown and outputs it with a color scheme specific to info messages +func (u *Utility) RenderInfoMarkdown(input string) { + err := RenderMarkdown(input, []string{"45", "232", "231"}) + + if err != nil { + fmt.Println(input) + } +} + +//RenderErrorMarkdown - Renders markdown and outputs it with a color scheme specific to error messages +func (u *Utility) RenderErrorMarkdown(input string) { + err := RenderMarkdown(input, []string{"88", "255", "231"}) + + if err != nil { + fmt.Println(input) + } +} diff --git a/utils/mocks.go b/utils/mocks.go index 1998fcf..ee6e9c5 100644 --- a/utils/mocks.go +++ b/utils/mocks.go @@ -1,539 +1,549 @@ -package utils - -import ( - "bytes" - "context" - "errors" - "io" - "os" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/hashicorp/hcl2/hcl" - specs "github.com/opencontainers/image-spec/specs-go/v1" -) - -//Mock utility and its functions -type MockUtility struct { - //Keeps track of the function calls that are made during a test - Calls []string - - //Package object that can be changed for different tests - Pim PimHCLUtil - - //Config Object that can be changed for different tests - Conf Config - - //HCL Body that can be changed for different tests - HCLBody hcl.Body - - //Set if the image should exist or not for testing - ImgExist bool - - //Set the function name that should throw an error when called - ErrorAt string - - //Set an error message for the tests - ErrorMsg string - - //Keep track of the directories that are created - MadeDirs []string - - //Keep track of the files that are opened/created - OpenedFiles []string - - //Keep track of the directories that are removed - RemovedDirs []string - - //Keep track of the upgraded directories - UpgradedDirs []string - - //Keep track of the HCLFiles read - HCLFiles []string - - //Keep track of the images that are pulled - PulledImgs []string - - //Use a fake containerID to make sure it is being used correctly - ContainerID string - - //CreateContainer data - CreateImages []string - - //Keep track of the CopyFromContainer data - CopySources []string - CopyDests []string - CopyContainerID string - - //Keep track of the RemoveContainer data - RemoveContainerID string - - //Keep track of the RunContainer data - RunImage string - RunPorts []string - RunVolumes []string - RunContainerName string - RunArgs []string - - //Keep track of the RemoveImage data - RemovedImgs []string - - //Keep track of the alias data - CmdToAlias []string - - //Should the Pim Configuration file exist - PimConfigShouldExist bool - - //Pim Config Directory passed in - PimConfigDir string - - //List of pim names to return - InstalledPims []string - - //List of pims fetched using FetchPimConfigs - FetchedPims []string -} - -//Create a new Mock Utility and set any default variables -func NewMockUtility() *MockUtility { - mu := &MockUtility{ - Pim: PimHCLUtil{ - Pims: []PackageImage{ - { - Name: "python", - BaseDir: "/base", - Versions: []Version{ - { - Version: "latest", - Image: "packageless/python", - - Volumes: []Volume{ - { - Path: "a/path", - Mount: "/another/one", - }, - }, - Copies: []*Copy{ - { - Source: "/source/path", - Dest: "destination", - }, - }, - Port: "3000", - }, - }, - }, - }, - }, - Conf: Config{ - BaseDir: "~/.packageless/", - StartPort: 3000, - PortInc: 1, - Alias: true, - RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", - PimsConfigDir: "pims_config/", - PimsDir: "pims/", - }, - HCLBody: hcl.EmptyBody(), - ErrorMsg: "Testing for error handling", - ContainerID: "FakeContainer123", - PimConfigShouldExist: true, - } - - return mu -} - -//Mock of the MakeDir Utility function -func (mu *MockUtility) MakeDir(path string) error { - mu.Calls = append(mu.Calls, "MakeDir") - mu.MadeDirs = append(mu.MadeDirs, path) - - if mu.ErrorAt == "MakeDir" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the OpenFile Utility function -func (mu *MockUtility) OpenFile(path string) (*os.File, error) { - mu.Calls = append(mu.Calls, "OpenFile") - mu.OpenedFiles = append(mu.OpenedFiles, path) - - if mu.ErrorAt == "OpenFile" { - return nil, errors.New(mu.ErrorMsg) - } - - return &os.File{}, nil -} - -//Mock of the RemoveDir Utility function -func (mu *MockUtility) RemoveDir(path string) error { - mu.Calls = append(mu.Calls, "RemoveDir") - mu.RemovedDirs = append(mu.RemovedDirs, path) - - if mu.ErrorAt == "RemoveDir" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the UpgradeDir Utility function -func (mu *MockUtility) UpgradeDir(path string) error { - mu.Calls = append(mu.Calls, "UpgradeDir") - mu.UpgradedDirs = append(mu.UpgradedDirs, path) - - if mu.ErrorAt == "UpgradeDir" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the ParseBody Utility function -func (mu *MockUtility) ParseBody(body hcl.Body, out interface{}) (interface{}, error) { - mu.Calls = append(mu.Calls, "ParseBody") - - if mu.ErrorAt == "ParseBody" { - return out, errors.New(mu.ErrorMsg) - } - - switch out.(type) { - default: - return nil, errors.New("Unexpected type in parse") - case PimHCLUtil: - return mu.Pim, nil - case Config: - return mu.Conf, nil - } -} - -//Mock of the GetHCLBody Utility function -func (mu *MockUtility) GetHCLBody(filepath string) (hcl.Body, error) { - mu.Calls = append(mu.Calls, "GetHCLBody") - mu.HCLFiles = append(mu.HCLFiles, filepath) - - if mu.ErrorAt == "GetHCLBody" { - return mu.HCLBody, errors.New(mu.ErrorMsg) - } - - return mu.HCLBody, nil -} - -//Mock of the PullImage Utility function -func (mu *MockUtility) PullImage(name string, cli Client) error { - mu.Calls = append(mu.Calls, "PullImage") - mu.PulledImgs = append(mu.PulledImgs, name) - - if mu.ErrorAt == "PullImage" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the ImageExists Utility function -func (mu *MockUtility) ImageExists(imageID string, cli Client) (bool, error) { - mu.Calls = append(mu.Calls, "ImageExists") - - if mu.ErrorAt == "ImageExists" { - return mu.ImgExist, errors.New(mu.ErrorMsg) - } - - return mu.ImgExist, nil -} - -//Mock of the CreateContainer Utility function -func (mu *MockUtility) CreateContainer(image string, cli Client) (string, error) { - mu.Calls = append(mu.Calls, "CreateContainer") - mu.CreateImages = append(mu.CreateImages, image) - - if mu.ErrorAt == "CreateContainer" { - return "", errors.New(mu.ErrorMsg) - } - - return mu.ContainerID, nil -} - -//Mock of the CopyFromContainer Utility function -func (mu *MockUtility) CopyFromContainer(source string, dest string, containerID string, cli Client, cp Copier) error { - mu.Calls = append(mu.Calls, "CopyFromContainer") - mu.CopySources = append(mu.CopySources, source) - mu.CopyDests = append(mu.CopyDests, dest) - mu.CopyContainerID = containerID - - if mu.ErrorAt == "CopyFromContainer" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the RemoveContainer Utility function -func (mu *MockUtility) RemoveContainer(containerID string, cli Client) error { - mu.Calls = append(mu.Calls, "RemoveContainer") - mu.RemoveContainerID = containerID - - if mu.ErrorAt == "RemoveContainer" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the RunContainer Utility function -func (mu *MockUtility) RunContainer(image string, ports []string, volumes []string, containerName string, args []string) (string, error) { - mu.Calls = append(mu.Calls, "RunContainer") - mu.RunImage = image - mu.RunPorts = ports - mu.RunVolumes = volumes - mu.RunContainerName = containerName - mu.RunArgs = args - - if mu.ErrorAt == "RunContainer" { - return "", errors.New(mu.ErrorMsg) - } - - return "", nil -} - -//Mock of the RemoveImage Utility function -func (mu *MockUtility) RemoveImage(image string, cli Client) error { - mu.Calls = append(mu.Calls, "RemoveImage") - mu.RemovedImgs = append(mu.RemovedImgs, image) - - if mu.ErrorAt == "RemoveImage" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the AddAliasWin Utility function -func (mu *MockUtility) AddAliasWin(name string, ed string) error { - mu.Calls = append(mu.Calls, "AddAlias") - mu.CmdToAlias = append(mu.CmdToAlias, name) - - if mu.ErrorAt == "AddAlias" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the RemoveAliasWin Utility function -func (mu *MockUtility) RemoveAliasWin(name string, ed string) error { - mu.Calls = append(mu.Calls, "RemoveAlias") - mu.CmdToAlias = append(mu.CmdToAlias, name) - - if mu.ErrorAt == "RemoveAlias" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the AddAliasUnix Utility function -func (mu *MockUtility) AddAliasUnix(name string, ed string) error { - mu.Calls = append(mu.Calls, "AddAlias") - mu.CmdToAlias = append(mu.CmdToAlias, name) - - if mu.ErrorAt == "AddAlias" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -//Mock of the RemoveAliasUnix Utility function -func (mu *MockUtility) RemoveAliasUnix(name string, ed string) error { - mu.Calls = append(mu.Calls, "RemoveAlias") - mu.CmdToAlias = append(mu.CmdToAlias, name) - - if mu.ErrorAt == "RemoveAlias" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -func (mu *MockUtility) FetchPimConfig(baseUrl string, pimName string, savePath string) error { - mu.Calls = append(mu.Calls, "FetchPimConfig") - - if mu.ErrorAt == "FetchPimConfig" { - return errors.New(mu.ErrorMsg) - } - - mu.FetchedPims = append(mu.FetchedPims, pimName) - - return nil -} - -func (mu *MockUtility) FileExists(path string) bool { - mu.Calls = append(mu.Calls, "FileExists") - - return mu.PimConfigShouldExist -} - -func (mu *MockUtility) RemoveFile(path string) error { - mu.Calls = append(mu.Calls, "RemoveFile") - - if mu.ErrorAt == "RemoveFile" { - return errors.New(mu.ErrorMsg) - } - - return nil -} - -func (mu *MockUtility) GetListOfInstalledPimConfigs(pimConfigDir string) ([]string, error) { - mu.Calls = append(mu.Calls, "GetListOfInstalledPimConfigs") - - if mu.ErrorAt == "GetListOfInstalledPimConfigs" { - return nil, errors.New(mu.ErrorMsg) - } - - mu.PimConfigDir = pimConfigDir - - return mu.InstalledPims, nil -} - -//Mock of the Getwd Utility function -func (mu *MockUtility) Getwd() (dir string, err error) { - mu.Calls = append(mu.Calls, "Getwd") - - if mu.ErrorAt == "Getwd" { - return "", errors.New(mu.ErrorMsg) - } - - return os.Getwd() -} - -//Create a Mock for the Docker client -type DockMock struct { - //Variable to know what function to return an error from - ErrorAt string - - //Variable to store the error message - ErrorMsg string - - //Keep track of the values from ImagePull Function - IPRefStr string - - //Keep track of the values from the ContainerCreate Function - CCConfig *container.Config - CCName string - CCRet container.ContainerCreateCreatedBody - - //Keep track of the values from the CopyFromContainer Function - CFCID string - CFCSource string - - //Keep track of the values from the ContainerRemove Function - CRContainer string - CROptions types.ContainerRemoveOptions - - //Keep track of the values from the ImageRemove Function - IRImgID string - IROptions types.ImageRemoveOptions - - //ImageList return value - ILRet []types.ImageSummary -} - -//Function to create a new DockMock -func NewDockMock() *DockMock { - dm := &DockMock{} - - return dm -} - -//Mock function of the Docker SDK ImagePull function -func (dm *DockMock) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) { - if dm.ErrorAt == "ImagePull" { - return nil, errors.New(dm.ErrorMsg) - } - - dm.IPRefStr = refStr - - //Create the ReadCloser - rc := io.NopCloser(bytes.NewReader([]byte("ImagePull"))) - - return rc, nil -} - -//Mock function of the Docker SDK ImagePull function -func (dm *DockMock) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) { - if dm.ErrorAt == "ImageList" { - return nil, errors.New(dm.ErrorMsg) - } - - return dm.ILRet, nil -} - -//Mock function of the Docker SDK ContainerCreate function -func (dm *DockMock) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { - if dm.ErrorAt == "ContainerCreate" { - return container.ContainerCreateCreatedBody{}, errors.New(dm.ErrorMsg) - } - - dm.CCConfig = config - dm.CCName = containerName - return dm.CCRet, nil -} - -//Mock function of the Docker SDK CopyFromContainer function -func (dm *DockMock) CopyFromContainer(ctx context.Context, containerID string, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { - if dm.ErrorAt == "CopyFromContainer" { - return nil, types.ContainerPathStat{}, errors.New(dm.ErrorMsg) - } - - dm.CFCID = containerID - dm.CFCSource = srcPath - - //Create the ReadCloser - rc := io.NopCloser(bytes.NewReader([]byte(""))) - - return rc, types.ContainerPathStat{}, nil -} - -//Mock function of the Docker SDK ContainerRemove function -func (dm *DockMock) ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error { - if dm.ErrorAt == "ContainerRemove" { - return errors.New(dm.ErrorMsg) - } - - dm.CRContainer = container - dm.CROptions = options - return nil -} - -//Mock function of the Docker SDK ImageRemove function -func (dm *DockMock) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) { - if dm.ErrorAt == "ImageRemove" { - return nil, errors.New(dm.ErrorMsg) - } - - dm.IRImgID = imageID - dm.IROptions = options - return nil, nil -} - -//CopyTool Mock -type MockCopyTool struct { - Error bool - ErrorMsg string - Dest string -} - -//Mock of the CopyFiles Utility function -func (mcp *MockCopyTool) CopyFiles(reader io.ReadCloser, dest string, source string) error { - if mcp.Error { - return errors.New(mcp.ErrorMsg) - } - - mcp.Dest = dest - - return nil -} +package utils + +import ( + "bytes" + "context" + "errors" + "io" + "os" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/hashicorp/hcl2/hcl" + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +//Mock utility and its functions +type MockUtility struct { + //Keeps track of the function calls that are made during a test + Calls []string + + //Package object that can be changed for different tests + Pim PimHCLUtil + + //Config Object that can be changed for different tests + Conf Config + + //HCL Body that can be changed for different tests + HCLBody hcl.Body + + //Set if the image should exist or not for testing + ImgExist bool + + //Set the function name that should throw an error when called + ErrorAt string + + //Set an error message for the tests + ErrorMsg string + + //Keep track of the directories that are created + MadeDirs []string + + //Keep track of the files that are opened/created + OpenedFiles []string + + //Keep track of the directories that are removed + RemovedDirs []string + + //Keep track of the upgraded directories + UpgradedDirs []string + + //Keep track of the HCLFiles read + HCLFiles []string + + //Keep track of the images that are pulled + PulledImgs []string + + //Use a fake containerID to make sure it is being used correctly + ContainerID string + + //CreateContainer data + CreateImages []string + + //Keep track of the CopyFromContainer data + CopySources []string + CopyDests []string + CopyContainerID string + + //Keep track of the RemoveContainer data + RemoveContainerID string + + //Keep track of the RunContainer data + RunImage string + RunPorts []string + RunVolumes []string + RunContainerName string + RunArgs []string + + //Keep track of the RemoveImage data + RemovedImgs []string + + //Keep track of the alias data + CmdToAlias []string + + //Should the Pim Configuration file exist + PimConfigShouldExist bool + + //Pim Config Directory passed in + PimConfigDir string + + //List of pim names to return + InstalledPims []string + + //List of pims fetched using FetchPimConfigs + FetchedPims []string +} + +//Create a new Mock Utility and set any default variables +func NewMockUtility() *MockUtility { + mu := &MockUtility{ + Pim: PimHCLUtil{ + Pims: []PackageImage{ + { + Name: "python", + BaseDir: "/base", + Versions: []Version{ + { + Version: "latest", + Image: "packageless/python", + + Volumes: []Volume{ + { + Path: "a/path", + Mount: "/another/one", + }, + }, + Copies: []*Copy{ + { + Source: "/source/path", + Dest: "destination", + }, + }, + Port: "3000", + }, + }, + }, + }, + }, + Conf: Config{ + BaseDir: "~/.packageless/", + StartPort: 3000, + PortInc: 1, + Alias: true, + RepositoryHost: "https://raw.githubusercontent.com/everettraven/packageless-pims/main/pims/", + PimsConfigDir: "pims_config/", + PimsDir: "pims/", + }, + HCLBody: hcl.EmptyBody(), + ErrorMsg: "Testing for error handling", + ContainerID: "FakeContainer123", + PimConfigShouldExist: true, + } + + return mu +} + +//Mock of the MakeDir Utility function +func (mu *MockUtility) MakeDir(path string) error { + mu.Calls = append(mu.Calls, "MakeDir") + mu.MadeDirs = append(mu.MadeDirs, path) + + if mu.ErrorAt == "MakeDir" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the OpenFile Utility function +func (mu *MockUtility) OpenFile(path string) (*os.File, error) { + mu.Calls = append(mu.Calls, "OpenFile") + mu.OpenedFiles = append(mu.OpenedFiles, path) + + if mu.ErrorAt == "OpenFile" { + return nil, errors.New(mu.ErrorMsg) + } + + return &os.File{}, nil +} + +//Mock of the RemoveDir Utility function +func (mu *MockUtility) RemoveDir(path string) error { + mu.Calls = append(mu.Calls, "RemoveDir") + mu.RemovedDirs = append(mu.RemovedDirs, path) + + if mu.ErrorAt == "RemoveDir" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the UpgradeDir Utility function +func (mu *MockUtility) UpgradeDir(path string) error { + mu.Calls = append(mu.Calls, "UpgradeDir") + mu.UpgradedDirs = append(mu.UpgradedDirs, path) + + if mu.ErrorAt == "UpgradeDir" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the ParseBody Utility function +func (mu *MockUtility) ParseBody(body hcl.Body, out interface{}) (interface{}, error) { + mu.Calls = append(mu.Calls, "ParseBody") + + if mu.ErrorAt == "ParseBody" { + return out, errors.New(mu.ErrorMsg) + } + + switch out.(type) { + default: + return nil, errors.New("Unexpected type in parse") + case PimHCLUtil: + return mu.Pim, nil + case Config: + return mu.Conf, nil + } +} + +//Mock of the GetHCLBody Utility function +func (mu *MockUtility) GetHCLBody(filepath string) (hcl.Body, error) { + mu.Calls = append(mu.Calls, "GetHCLBody") + mu.HCLFiles = append(mu.HCLFiles, filepath) + + if mu.ErrorAt == "GetHCLBody" { + return mu.HCLBody, errors.New(mu.ErrorMsg) + } + + return mu.HCLBody, nil +} + +//Mock of the PullImage Utility function +func (mu *MockUtility) PullImage(name string, cli Client) error { + mu.Calls = append(mu.Calls, "PullImage") + mu.PulledImgs = append(mu.PulledImgs, name) + + if mu.ErrorAt == "PullImage" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the ImageExists Utility function +func (mu *MockUtility) ImageExists(imageID string, cli Client) (bool, error) { + mu.Calls = append(mu.Calls, "ImageExists") + + if mu.ErrorAt == "ImageExists" { + return mu.ImgExist, errors.New(mu.ErrorMsg) + } + + return mu.ImgExist, nil +} + +//Mock of the CreateContainer Utility function +func (mu *MockUtility) CreateContainer(image string, cli Client) (string, error) { + mu.Calls = append(mu.Calls, "CreateContainer") + mu.CreateImages = append(mu.CreateImages, image) + + if mu.ErrorAt == "CreateContainer" { + return "", errors.New(mu.ErrorMsg) + } + + return mu.ContainerID, nil +} + +//Mock of the CopyFromContainer Utility function +func (mu *MockUtility) CopyFromContainer(source string, dest string, containerID string, cli Client, cp Copier) error { + mu.Calls = append(mu.Calls, "CopyFromContainer") + mu.CopySources = append(mu.CopySources, source) + mu.CopyDests = append(mu.CopyDests, dest) + mu.CopyContainerID = containerID + + if mu.ErrorAt == "CopyFromContainer" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the RemoveContainer Utility function +func (mu *MockUtility) RemoveContainer(containerID string, cli Client) error { + mu.Calls = append(mu.Calls, "RemoveContainer") + mu.RemoveContainerID = containerID + + if mu.ErrorAt == "RemoveContainer" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the RunContainer Utility function +func (mu *MockUtility) RunContainer(image string, ports []string, volumes []string, containerName string, args []string) (string, error) { + mu.Calls = append(mu.Calls, "RunContainer") + mu.RunImage = image + mu.RunPorts = ports + mu.RunVolumes = volumes + mu.RunContainerName = containerName + mu.RunArgs = args + + if mu.ErrorAt == "RunContainer" { + return "", errors.New(mu.ErrorMsg) + } + + return "", nil +} + +//Mock of the RemoveImage Utility function +func (mu *MockUtility) RemoveImage(image string, cli Client) error { + mu.Calls = append(mu.Calls, "RemoveImage") + mu.RemovedImgs = append(mu.RemovedImgs, image) + + if mu.ErrorAt == "RemoveImage" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the AddAliasWin Utility function +func (mu *MockUtility) AddAliasWin(name string, ed string) error { + mu.Calls = append(mu.Calls, "AddAlias") + mu.CmdToAlias = append(mu.CmdToAlias, name) + + if mu.ErrorAt == "AddAlias" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the RemoveAliasWin Utility function +func (mu *MockUtility) RemoveAliasWin(name string, ed string) error { + mu.Calls = append(mu.Calls, "RemoveAlias") + mu.CmdToAlias = append(mu.CmdToAlias, name) + + if mu.ErrorAt == "RemoveAlias" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the AddAliasUnix Utility function +func (mu *MockUtility) AddAliasUnix(name string, ed string) error { + mu.Calls = append(mu.Calls, "AddAlias") + mu.CmdToAlias = append(mu.CmdToAlias, name) + + if mu.ErrorAt == "AddAlias" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +//Mock of the RemoveAliasUnix Utility function +func (mu *MockUtility) RemoveAliasUnix(name string, ed string) error { + mu.Calls = append(mu.Calls, "RemoveAlias") + mu.CmdToAlias = append(mu.CmdToAlias, name) + + if mu.ErrorAt == "RemoveAlias" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +func (mu *MockUtility) FetchPimConfig(baseUrl string, pimName string, savePath string) error { + mu.Calls = append(mu.Calls, "FetchPimConfig") + + if mu.ErrorAt == "FetchPimConfig" { + return errors.New(mu.ErrorMsg) + } + + mu.FetchedPims = append(mu.FetchedPims, pimName) + + return nil +} + +func (mu *MockUtility) FileExists(path string) bool { + mu.Calls = append(mu.Calls, "FileExists") + + return mu.PimConfigShouldExist +} + +func (mu *MockUtility) RemoveFile(path string) error { + mu.Calls = append(mu.Calls, "RemoveFile") + + if mu.ErrorAt == "RemoveFile" { + return errors.New(mu.ErrorMsg) + } + + return nil +} + +func (mu *MockUtility) GetListOfInstalledPimConfigs(pimConfigDir string) ([]string, error) { + mu.Calls = append(mu.Calls, "GetListOfInstalledPimConfigs") + + if mu.ErrorAt == "GetListOfInstalledPimConfigs" { + return nil, errors.New(mu.ErrorMsg) + } + + mu.PimConfigDir = pimConfigDir + + return mu.InstalledPims, nil +} + +//Mock of the Getwd Utility function +func (mu *MockUtility) Getwd() (dir string, err error) { + mu.Calls = append(mu.Calls, "Getwd") + + if mu.ErrorAt == "Getwd" { + return "", errors.New(mu.ErrorMsg) + } + + return os.Getwd() +} + +//Mock of the RenderInfoMarkdown utility function +func (mu *MockUtility) RenderInfoMarkdown(input string) { + mu.Calls = append(mu.Calls, "RenderInfoMarkdown") +} + +//Mock of the RenderErrorMarkdown utility function +func (mu *MockUtility) RenderErrorMarkdown(input string) { + mu.Calls = append(mu.Calls, "RenderErrorMarkdown") +} + +//Create a Mock for the Docker client +type DockMock struct { + //Variable to know what function to return an error from + ErrorAt string + + //Variable to store the error message + ErrorMsg string + + //Keep track of the values from ImagePull Function + IPRefStr string + + //Keep track of the values from the ContainerCreate Function + CCConfig *container.Config + CCName string + CCRet container.ContainerCreateCreatedBody + + //Keep track of the values from the CopyFromContainer Function + CFCID string + CFCSource string + + //Keep track of the values from the ContainerRemove Function + CRContainer string + CROptions types.ContainerRemoveOptions + + //Keep track of the values from the ImageRemove Function + IRImgID string + IROptions types.ImageRemoveOptions + + //ImageList return value + ILRet []types.ImageSummary +} + +//Function to create a new DockMock +func NewDockMock() *DockMock { + dm := &DockMock{} + + return dm +} + +//Mock function of the Docker SDK ImagePull function +func (dm *DockMock) ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) { + if dm.ErrorAt == "ImagePull" { + return nil, errors.New(dm.ErrorMsg) + } + + dm.IPRefStr = refStr + + //Create the ReadCloser + rc := io.NopCloser(bytes.NewReader([]byte("ImagePull"))) + + return rc, nil +} + +//Mock function of the Docker SDK ImagePull function +func (dm *DockMock) ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) { + if dm.ErrorAt == "ImageList" { + return nil, errors.New(dm.ErrorMsg) + } + + return dm.ILRet, nil +} + +//Mock function of the Docker SDK ContainerCreate function +func (dm *DockMock) ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) { + if dm.ErrorAt == "ContainerCreate" { + return container.ContainerCreateCreatedBody{}, errors.New(dm.ErrorMsg) + } + + dm.CCConfig = config + dm.CCName = containerName + return dm.CCRet, nil +} + +//Mock function of the Docker SDK CopyFromContainer function +func (dm *DockMock) CopyFromContainer(ctx context.Context, containerID string, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) { + if dm.ErrorAt == "CopyFromContainer" { + return nil, types.ContainerPathStat{}, errors.New(dm.ErrorMsg) + } + + dm.CFCID = containerID + dm.CFCSource = srcPath + + //Create the ReadCloser + rc := io.NopCloser(bytes.NewReader([]byte(""))) + + return rc, types.ContainerPathStat{}, nil +} + +//Mock function of the Docker SDK ContainerRemove function +func (dm *DockMock) ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error { + if dm.ErrorAt == "ContainerRemove" { + return errors.New(dm.ErrorMsg) + } + + dm.CRContainer = container + dm.CROptions = options + return nil +} + +//Mock function of the Docker SDK ImageRemove function +func (dm *DockMock) ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) { + if dm.ErrorAt == "ImageRemove" { + return nil, errors.New(dm.ErrorMsg) + } + + dm.IRImgID = imageID + dm.IROptions = options + return nil, nil +} + +//CopyTool Mock +type MockCopyTool struct { + Error bool + ErrorMsg string + Dest string +} + +//Mock of the CopyFiles Utility function +func (mcp *MockCopyTool) CopyFiles(reader io.ReadCloser, dest string, source string) error { + if mcp.Error { + return errors.New(mcp.ErrorMsg) + } + + mcp.Dest = dest + + return nil +} diff --git a/utils/utils.go b/utils/utils.go index bdff0fe..50f1f49 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1,358 +1,360 @@ -package utils - -import ( - "archive/tar" - "context" - "errors" - "io" - "io/ioutil" - "net/http" - "os" - "path/filepath" - "strings" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/network" - "github.com/hashicorp/hcl2/hcl" - specs "github.com/opencontainers/image-spec/specs-go/v1" -) - -//Client interface so that we can create a mock of the docker SDK interactions in our unit tests -type Client interface { - ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) - ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) - ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) - CopyFromContainer(ctx context.Context, containerID string, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) - ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error - ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) -} - -//Tools interface so that we can create a mock of our utility functions in our unit tests -type Tools interface { - MakeDir(path string) error - OpenFile(path string) (*os.File, error) - RemoveDir(path string) error - UpgradeDir(path string) error - ParseBody(body hcl.Body, out interface{}) (interface{}, error) - GetHCLBody(filepath string) (hcl.Body, error) - PullImage(name string, cli Client) error - ImageExists(imageID string, cli Client) (bool, error) - CreateContainer(image string, cli Client) (string, error) - CopyFromContainer(source string, dest string, containerID string, cli Client, cp Copier) error - RemoveContainer(containerID string, cli Client) error - RunContainer(image string, ports []string, volumes []string, containerName string, args []string) (string, error) - RemoveImage(image string, cli Client) error - AddAliasWin(name string, ed string) error - RemoveAliasWin(name string, ed string) error - AddAliasUnix(name string, ed string) error - RemoveAliasUnix(name string, ed string) error - FetchPimConfig(baseUrl string, pimName string, savePath string) error - FileExists(path string) bool - RemoveFile(path string) error - GetListOfInstalledPimConfigs(pimConfigDir string) ([]string, error) - Getwd() (string, error) -} - -//Utility Tool struct with its functions -type Utility struct{} - -func NewUtility() *Utility { - util := &Utility{} - return util -} - -//MakeDir makes a directory if it does not exist -func (u *Utility) MakeDir(path string) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - err = os.MkdirAll(path, 0765) - - if err != nil { - return err - } - } else { - return err - } - } - return nil -} - -//OpenFile opens the specified file, creating it if it does not exist -func (u *Utility) OpenFile(path string) (*os.File, error) { - var file *os.File - //Check if the path exists - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - //Create the file - file, err = os.Create(path) - - if err != nil { - return nil, err - } - } - } else { - //Open the file - file, err = os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0755) - if err != nil { - return nil, err - } - } - - return file, nil -} - -//OverwriteFile opens the specified file with overwrite mode, creating it if it does not exist -func (u *Utility) OverwriteFile(path string) (*os.File, error) { - var file *os.File - //Check if the path exists - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - //Create the file - file, err = os.Create(path) - - if err != nil { - return nil, err - } - } - } else { - //Open the file - file, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) - if err != nil { - return nil, err - } - } - - return file, nil -} - -//RemoveDir removes the specified directory -func (u *Utility) RemoveDir(path string) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return nil - } else { - return err - } - } else { - err = os.RemoveAll(path) - - if err != nil { - return err - } - } - - return nil -} - -//UpgradeDir resets the directory by removing it if it exists and then recreating it -func (u *Utility) UpgradeDir(path string) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - err = os.MkdirAll(path, 0765) - - if err != nil { - return err - } - } else { - return err - } - } else { - //Remove the directory if it already exists - err = os.RemoveAll(path) - - if err != nil { - return err - } - - //Recreate the directory - err = os.MkdirAll(path, 0765) - - if err != nil { - return err - } - } - return nil -} - -//FetchPimConfig will get download the latest pim configuration for specified pim -func (u *Utility) FetchPimConfig(baseUrl string, pimName string, savePath string) error { - pimFile := pimName + ".hcl" - url := baseUrl + pimFile - resp, err := http.Get(url) - - if err != nil { - return err - } - - if resp.StatusCode != 200 { - return errors.New("Could not find pim configuration for pim: " + pimName) - } - - defer resp.Body.Close() - - file, err := u.OverwriteFile(savePath + pimFile) - - if err != nil { - return err - } - - _, err = io.Copy(file, resp.Body) - - if err != nil { - return err - } - - return nil -} - -//FileExists - checks to see if a file exists -func (u *Utility) FileExists(path string) bool { - returnVal := false - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - returnVal = false - } - } else { - returnVal = true - } - - return returnVal -} - -//RemoveFile - will delete the file at the specified path -func (u *Utility) RemoveFile(path string) error { - if _, err := os.Stat(path); err != nil { - if os.IsNotExist(err) { - return nil - } else { - return err - } - } else { - err = os.Remove(path) - - if err != nil { - return err - } - } - - return nil -} - -//GetListOfInstalledPimConfigs - returns a string array of the names of the pims -//with configuration files currently in the pim configuration directory -func (u *Utility) GetListOfInstalledPimConfigs(pimConfigDir string) ([]string, error) { - var pimNames []string - fileInfo, err := ioutil.ReadDir(pimConfigDir) - - if err != nil { - return pimNames, err - } - - for _, file := range fileInfo { - pimNames = append(pimNames, strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))) - } - - return pimNames, nil -} - -//Getwd returns a rooted path name corresponding to the current directory -func (u *Utility) Getwd() (string, error) { - return os.Getwd() -} - -//Create an interface to house the CopyFiles implementation. This will allow us to make a mock of the CopyFiles Function. -type Copier interface { - CopyFiles(reader io.ReadCloser, dest string, source string) error -} - -//Create the real copy struct -type CopyTool struct{} - -//CopyFiles implements a tar reader to copy files from the ReadCloser that the docker sdk CopyFromContainer function returns to the specified destination -func (cp *CopyTool) CopyFiles(reader io.ReadCloser, dest string, source string) error { - - dir := false - if source[len(source)-1] == '/' { - dir = true - } - //Create a tar Reader - tarReader := tar.NewReader(reader) - - var header *tar.Header - var err error - - if dir { - //Skip the first header as it is the source folder name - header, err = tarReader.Next() - - if err == io.EOF { - return nil - } else if err != nil { - return err - } - } - - //Loop through the reader and write the files - for { - //Get the tar header - header, err = tarReader.Next() - //Make sure we havent reached the end of the tar - if err == io.EOF { - break - } else if err != nil { - return err - } - - var newHeaderPath []string - - //if the source is a directory split on the forward slash otherwise don't split it - if dir { - newHeaderPath = strings.Split(header.Name, "/")[1:] - } else { - newHeaderPath = []string{header.Name} - } - - joinPath := strings.Join(newHeaderPath[:], "/") - - //Create the destination file path on the host - path := filepath.Join(dest, joinPath) - //Get the file info from the header - info := header.FileInfo() - - //Check if the current file is a directory - if info.IsDir() { - - //Check if the directory exists - if _, err = os.Stat(path); err != nil { - if os.IsNotExist(err) { - //Make the directory - err = os.MkdirAll(path, 0765) - } - } - - } else { - //Create the file and open it in the destination path on the host - file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0765) - - //Check for errors - if err != nil { - return err - } - - //Copy the contents of the tar reader to the file - _, err = io.Copy(file, tarReader) - - //Check for errors - if err != nil { - return err - } - - //Close the file when all the writing is finished - file.Close() - } - - } - - return nil -} +package utils + +import ( + "archive/tar" + "context" + "errors" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/network" + "github.com/hashicorp/hcl2/hcl" + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +//Client interface so that we can create a mock of the docker SDK interactions in our unit tests +type Client interface { + ImagePull(ctx context.Context, refStr string, options types.ImagePullOptions) (io.ReadCloser, error) + ImageList(ctx context.Context, options types.ImageListOptions) ([]types.ImageSummary, error) + ContainerCreate(ctx context.Context, config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, platform *specs.Platform, containerName string) (container.ContainerCreateCreatedBody, error) + CopyFromContainer(ctx context.Context, containerID string, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) + ContainerRemove(ctx context.Context, container string, options types.ContainerRemoveOptions) error + ImageRemove(ctx context.Context, imageID string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) +} + +//Tools interface so that we can create a mock of our utility functions in our unit tests +type Tools interface { + MakeDir(path string) error + OpenFile(path string) (*os.File, error) + RemoveDir(path string) error + UpgradeDir(path string) error + ParseBody(body hcl.Body, out interface{}) (interface{}, error) + GetHCLBody(filepath string) (hcl.Body, error) + PullImage(name string, cli Client) error + ImageExists(imageID string, cli Client) (bool, error) + CreateContainer(image string, cli Client) (string, error) + CopyFromContainer(source string, dest string, containerID string, cli Client, cp Copier) error + RemoveContainer(containerID string, cli Client) error + RunContainer(image string, ports []string, volumes []string, containerName string, args []string) (string, error) + RemoveImage(image string, cli Client) error + AddAliasWin(name string, ed string) error + RemoveAliasWin(name string, ed string) error + AddAliasUnix(name string, ed string) error + RemoveAliasUnix(name string, ed string) error + FetchPimConfig(baseUrl string, pimName string, savePath string) error + FileExists(path string) bool + RemoveFile(path string) error + GetListOfInstalledPimConfigs(pimConfigDir string) ([]string, error) + Getwd() (string, error) + RenderInfoMarkdown(input string) + RenderErrorMarkdown(input string) +} + +//Utility Tool struct with its functions +type Utility struct{} + +func NewUtility() *Utility { + util := &Utility{} + return util +} + +//MakeDir makes a directory if it does not exist +func (u *Utility) MakeDir(path string) error { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + err = os.MkdirAll(path, 0765) + + if err != nil { + return err + } + } else { + return err + } + } + return nil +} + +//OpenFile opens the specified file, creating it if it does not exist +func (u *Utility) OpenFile(path string) (*os.File, error) { + var file *os.File + //Check if the path exists + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + //Create the file + file, err = os.Create(path) + + if err != nil { + return nil, err + } + } + } else { + //Open the file + file, err = os.OpenFile(path, os.O_RDWR|os.O_APPEND, 0755) + if err != nil { + return nil, err + } + } + + return file, nil +} + +//OverwriteFile opens the specified file with overwrite mode, creating it if it does not exist +func (u *Utility) OverwriteFile(path string) (*os.File, error) { + var file *os.File + //Check if the path exists + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + //Create the file + file, err = os.Create(path) + + if err != nil { + return nil, err + } + } + } else { + //Open the file + file, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0755) + if err != nil { + return nil, err + } + } + + return file, nil +} + +//RemoveDir removes the specified directory +func (u *Utility) RemoveDir(path string) error { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return nil + } else { + return err + } + } else { + err = os.RemoveAll(path) + + if err != nil { + return err + } + } + + return nil +} + +//UpgradeDir resets the directory by removing it if it exists and then recreating it +func (u *Utility) UpgradeDir(path string) error { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + err = os.MkdirAll(path, 0765) + + if err != nil { + return err + } + } else { + return err + } + } else { + //Remove the directory if it already exists + err = os.RemoveAll(path) + + if err != nil { + return err + } + + //Recreate the directory + err = os.MkdirAll(path, 0765) + + if err != nil { + return err + } + } + return nil +} + +//FetchPimConfig will get download the latest pim configuration for specified pim +func (u *Utility) FetchPimConfig(baseUrl string, pimName string, savePath string) error { + pimFile := pimName + ".hcl" + url := baseUrl + pimFile + resp, err := http.Get(url) + + if err != nil { + return err + } + + if resp.StatusCode != 200 { + return errors.New("Could not find pim configuration for pim: " + pimName) + } + + defer resp.Body.Close() + + file, err := u.OverwriteFile(savePath + pimFile) + + if err != nil { + return err + } + + _, err = io.Copy(file, resp.Body) + + if err != nil { + return err + } + + return nil +} + +//FileExists - checks to see if a file exists +func (u *Utility) FileExists(path string) bool { + returnVal := false + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + returnVal = false + } + } else { + returnVal = true + } + + return returnVal +} + +//RemoveFile - will delete the file at the specified path +func (u *Utility) RemoveFile(path string) error { + if _, err := os.Stat(path); err != nil { + if os.IsNotExist(err) { + return nil + } else { + return err + } + } else { + err = os.Remove(path) + + if err != nil { + return err + } + } + + return nil +} + +//GetListOfInstalledPimConfigs - returns a string array of the names of the pims +//with configuration files currently in the pim configuration directory +func (u *Utility) GetListOfInstalledPimConfigs(pimConfigDir string) ([]string, error) { + var pimNames []string + fileInfo, err := ioutil.ReadDir(pimConfigDir) + + if err != nil { + return pimNames, err + } + + for _, file := range fileInfo { + pimNames = append(pimNames, strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))) + } + + return pimNames, nil +} + +//Getwd returns a rooted path name corresponding to the current directory +func (u *Utility) Getwd() (string, error) { + return os.Getwd() +} + +//Create an interface to house the CopyFiles implementation. This will allow us to make a mock of the CopyFiles Function. +type Copier interface { + CopyFiles(reader io.ReadCloser, dest string, source string) error +} + +//Create the real copy struct +type CopyTool struct{} + +//CopyFiles implements a tar reader to copy files from the ReadCloser that the docker sdk CopyFromContainer function returns to the specified destination +func (cp *CopyTool) CopyFiles(reader io.ReadCloser, dest string, source string) error { + + dir := false + if source[len(source)-1] == '/' { + dir = true + } + //Create a tar Reader + tarReader := tar.NewReader(reader) + + var header *tar.Header + var err error + + if dir { + //Skip the first header as it is the source folder name + header, err = tarReader.Next() + + if err == io.EOF { + return nil + } else if err != nil { + return err + } + } + + //Loop through the reader and write the files + for { + //Get the tar header + header, err = tarReader.Next() + //Make sure we havent reached the end of the tar + if err == io.EOF { + break + } else if err != nil { + return err + } + + var newHeaderPath []string + + //if the source is a directory split on the forward slash otherwise don't split it + if dir { + newHeaderPath = strings.Split(header.Name, "/")[1:] + } else { + newHeaderPath = []string{header.Name} + } + + joinPath := strings.Join(newHeaderPath[:], "/") + + //Create the destination file path on the host + path := filepath.Join(dest, joinPath) + //Get the file info from the header + info := header.FileInfo() + + //Check if the current file is a directory + if info.IsDir() { + + //Check if the directory exists + if _, err = os.Stat(path); err != nil { + if os.IsNotExist(err) { + //Make the directory + err = os.MkdirAll(path, 0765) + } + } + + } else { + //Create the file and open it in the destination path on the host + file, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0765) + + //Check for errors + if err != nil { + return err + } + + //Copy the contents of the tar reader to the file + _, err = io.Copy(file, tarReader) + + //Check for errors + if err != nil { + return err + } + + //Close the file when all the writing is finished + file.Close() + } + + } + + return nil +}